多因子策略-回测阶段应用

Posted 黑马程序员官方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多因子策略-回测阶段应用相关的知识,希望对你有一定的参考价值。

学习目标

  • 目标
    • 知道多因子策略的购买资金比例以及调仓周期
    • 定义回测区间
  • 定义筛选出的因子以及因子的方向
  • 比较股票分组和因子方向得出因子打分
  • 应用因子得分的方向实现股票选择
  • 定义筛选的因子
  • 说明回归法因子权重的建立公式
  • 应用回归法训练实现历史测试区间因子的权重系数计算
  • 应用因子权重系数实现回测区间的股票收益率预测

一、回测内容确定

在多因子策略当中,选股通过因子选择,那么怎么结合筛选出来的多个因子来去选择某些股票

二、回测选股方法

多因子选股最常用的方法就是打分法和回归法,但是至于这两种哪个效果更好需要进行实际的模拟交易或者实盘交易之后才能确定。通常模拟交易测试结果来说,打分法效果更好。

  • 打分法选股
  • 回归法选股

这两种方法都是在确定因子的权重来选择固定的股票。什么是确定权重再去选股呢?

三、多因子打分法选股-经过筛选的因子

因子方向因子说明
因子升序因子值越小越好,市值、市盈率、市净率
因子降序因子值越大越好,ROIC、inc_revenue营业总收入 和inc_profit_before_tax利润增长率

四、打分法选股流程确定

4.1 分析

  • 1、回测区间:
    • 2010-01-01 ~ 2018-01-01
  • 2、选股:
    • 选股因子:6个已知方向的因子
    • 选股权重:
      • 因子升序从小到大分10组,第几组为所在组得分
      • 因子降序从大到小分10组,第几组为所在组得分
    • 数据处理:处理缺失值

  • 3、调仓周期:
    • 调仓:每月进行一次调仓
    • 交易规则:卖出已持有的股票
    • 买入新的股票池当中的股票

4.2 代码

# 可以自己import我们平台支持的第三方python模块,比如pandas、numpy等。
# 打分法选股
# 回测:2010-01-01~2018-01-01
# 调仓:按月
# 选股因子:市值、市盈率、市净率、ROIC、inc_revenue营业总收入
# 和inc_profit_before_tax利润增长率
# 选股的指数、模块:全A股
import pandas as pd


# 在这个方法中编写任何的初始化逻辑。context对象将会在你的算法策略的任何方法之间做传递。
def init(context):
    context.group_number = 10
    context.stocknum = 20

    # 定义调仓频率函数
    scheduler.run_monthly(score_select, tradingday=1)


def score_select(context, bar_dict):
    """调仓函数
    """

    # 选股逻辑,获取数据、数据处理、选股方法

    # 1、获取选股的因子数据
    q = query(fundamentals.eod_derivative_indicator.market_cap,
              fundamentals.eod_derivative_indicator.pe_ratio,
              fundamentals.eod_derivative_indicator.pb_ratio,
              fundamentals.financial_indicator.return_on_invested_capital,
              fundamentals.financial_indicator.inc_revenue,
              fundamentals.financial_indicator.inc_profit_before_tax
              )

    fund = get_fundamentals(q)

    # 通过转置将股票索引变成行索引、指标变成列索引
    factors_data = fund.T

    # 数据处理
    factors_data = factors_data.dropna()

    # 对每个因子进行打分估计,得出综合评分
    get_score(context, factors_data)

    # # 进行调仓逻辑,买卖
    rebalance(context)


def get_score(context, factors_data):
    """
    对因子选股数据打分
    因子升序:市值、市盈率、市净率
    因子降序:ROIC、inc_revenue营业总收入和inc_profit_before_tax利润增长率
    """
    # logger.info(factors_data)
    for factorname in factors_data.columns:

        if factorname in ['pe_ratio', 'pb_ratio', 'market_cap']:
            # 单独取出每一个因子去进行处理分组打分
            factor = factors_data.sort_values(by=factorname)[factorname]
        else:

            factor = factors_data.sort_values(by=factorname, ascending=False)[factorname]
            # logger.info(factor)

        # 对于每个因子去进行分组打分
        # 求出所有的股票个数
        single_groupnum = len(factor) // 10

        # 对于factor转换成dataframe,为了新增列分数
        factor = pd.DataFrame(factor)

        factor[factorname + "score"] = 0

        # logger.info(factor)

        for i in range(10):

            if i == 9:
                factor[factorname + "score"][i * single_groupnum:] = i + 1

            factor[factorname + "score"][i * single_groupnum:(i + 1) * single_groupnum] = i + 1

        # 打印分数
        # logger.info(factor)
        # 拼接每个因子的分数到原始的factors_data数据当中
        factors_data = pd.concat([factors_data, factor[factorname + "score"]], axis=1)

    # logger.info(factors_data)

    # 求出总分
    # 先取出这几列分数
    # 求和分数
    sum_score = factors_data[
        ["market_capscore", "pe_ratioscore", "pb_ratioscore", "return_on_invested_capitalscore", "inc_revenuescore",
         "inc_profit_before_taxscore"]].sum(1).sort_values()

    # 拼接到factors_data
    # logger.info(sum_score)
    context.stocklist = sum_score[:context.stocknum].index


def rebalance(context):
    # 卖出
    for stock in context.portfolio.positions.keys():

        if context.portfolio.positions[stock].quantity > 0:

            if stock not in context.stocklist:
                order_target_percent(stock, 0)

    # 买入
    for stock in context.stocklist:
        order_target_percent(stock, 1.0 / 20)


# before_trading此函数会在每天策略交易开始前被调用,当天只会被调用一次
def before_trading(context):
    pass


# 你选择的证券的数据更新将会触发此段逻辑,例如日或分钟历史数据切片或者是实时数据切片更新
def handle_bar(context, bar_dict):
    # 开始编写你的主要的算法逻辑
    pass


# after_trading函数会在每天交易结束后被调用,当天只会被调用一次
def after_trading(context):
    pass

五、回归法选股步骤

  • 选股因子回归系数确定
  • 利用回归系数计算结果选股

六、选股因子回归系数确定

6.1步骤分析

  • 1、回归训练区间

​ 2012-01-01 ~ 2014-01-01

  • 2、回归股票池

​ 股票池(HS300指数)

  • 3、回归因子数据准备、收益率计算

​ 因子数据:横截面数据拼接,添加日期数据、去除空值 收益率计算: 所有样本的收益率计算

  • 4、目标值特征值提取进行回归估计

​ 数据处理:去除收益为0(价格数据不存在)、去极值、标准化处理

6.2 代码

  • 因子数据准备
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression, Ridge


# 获取股票列表数据、训练时间
stocks = index_components("000300.XSHG")
date = get_trading_dates(start_date="2012-01-01", end_date="2013-01-01")
all_data = pd.DataFrame()
for i in range(len(date)):

  q = query(fundamentals.eod_derivative_indicator.pe_ratio,
           fundamentals.eod_derivative_indicator.pb_ratio,
           fundamentals.eod_derivative_indicator.market_cap,
           fundamentals.financial_indicator.ev,
           fundamentals.financial_indicator.return_on_asset_net_profit,
           fundamentals.financial_indicator.du_return_on_equity,
           fundamentals.financial_indicator.earnings_per_share,
           fundamentals.income_statement.revenue,
           fundamentals.income_statement.total_expense).filter(fundamentals.stockcode.in_(stocks))

fund = get_fundamentals(q, entry_date=date[i])[:, 0, :]

# 添加日期
fund['date'] = date[i]

all_data = pd.concat([all_data, fund])

# 为了回归计算,先去除空值
all_data = all_data.dropna()
# 计算收益率,需要用到索引的股票代码
all_data = all_data.reset_index()
  • 计算每个样本的收益率
# 增加return收益率列
all_data['return'] = 0.0

for i in range(20):

  # 取出每个样本的股票代号和当天的日期
  stock_code = all_data.ix[i, 'index']

  # 取出当天日期
  now_day = all_data.ix[i, 'date']

  # 得到下一天的日期,一定要从交易日里面找到下一期
  for time in range(len(date)):
    if date[time] == now_day:
      next_day = date[time + 1]

  # 根据日期,取的当天的价格和下一天的价格计算收益率
  price_now = get_price(stock_code, start_date=now_day, end_date=now_day, fields='close')

  price_next = get_price(stock_code, start_date=next_day, end_date=next_day, fields='close')

  # 做出价格为空的判断
  if price_now.values.size == 0 or price_next.values.size == 0: 
    continue

  # 计算收益率
  return_price = price_next.values/price_now.values - 1

  all_data['return'][i] = return_price
  • 利用回归确定系数
# 取出特征值
x = all_data[["pe_ratio","pb_ratio","market_cap","return_on_asset_net_profit","du_return_on_equity","ev","earnings_per_share","revenue","total_expense"]]

# 取出目标值
y = all_data[['return']]
def mad(factor):
  """3倍中位数去极值
  """
  # 求出因子值的中位数
  med = np.median(factor)

  # 求出因子值与中位数的差值,进行绝对值
  mad = np.median(np.abs(factor - med))

  # 定义几倍的中位数上下限
  high = med + (3 * 1.4826 * mad)
  low = med - (3 * 1.4826 * mad)

  # 替换上下限以外的值
  factor = np.where(factor > high, high, factor)
  factor = np.where(factor < low, low, factor)
  return factor
# 对特征值进行处理
for name in x.columns:

  x[name] = mad(x[name])

# 特征值标准化操作
std = StandardScaler()
x = std.fit_transform(x)
std_y = StandardScaler()
y = std_y.fit_transform(y)

# 通过线性回归得出系数
lr = LinearRegression()
lr.fit(x, y)

print(lr.coef_)

rd = Ridge()
rd.fit(x, y)

print(rd.coef_)

我们可以得出线性回归的系数,如下

array([[-0.12496874,  0.03845575, -0.99038983, -0.39055527,  0.68191808,
         0.85971852,  0.14719819,  0.18936681,  0.11412645]])

七、利用回归系数计算结果选股

# 可以自己import我们平台支持的第三方python模块,比如pandas、numpy等。
# 回测区间:2014-01-01~2018-01-01
# 选股:
# 范围:沪深300
# 因子:
# 方法:回归法,利用系数相乘(矩阵相乘运算)得出结果排序

import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression, Ridge


# 在这个方法中编写任何的初始化逻辑。context对象将会在你的算法策略的任何方法之间做传递。
def init(context):
    context.stock_num = 20

    # 定义沪深300指数股
    context.hs300 = index_components("000300.XSHG")

    scheduler.run_monthly(regression_select, tradingday=1)


def regression_select(context, bar_dict):
    """回归法进行选择股票
    准备因子数据、数据处理(缺失值、去极值、标准化、中性化)
    预测每个股票对应这一天的结果,然后排序选出前20只股票
    """
    # 1、准备因子数据
    q = query(fundamentals.eod_derivative_indicator.pe_ratio,
              fundamentals.eod_derivative_indicator.pb_ratio,
              fundamentals.eod_derivative_indicator.market_cap,
              fundamentals.financial_indicator.ev,
              fundamentals.financial_indicator.return_on_asset_net_profit,
              fundamentals.financial_indicator.du_return_on_equity,
              fundamentals.financial_indicator.earnings_per_share,
              fundamentals.income_statement.revenue,
              fundamentals.income_statement.total_expense).filter(fundamentals.stockcode.in_(context.hs300))

    fund = get_fundamentals(q)

    factors_data = fund.T

    # 2、处理数据
    # 缺失值
    factors_data = factors_data.dropna()
    # 保留原来的数据,后续处理
    factors1 = pd.DataFrame()
    # 去极值
    for name in factors_data.columns:
        factors1[name] = mad(factors_data[name])

    # logger.info(factors1)

    # 标准化
    std = StandardScaler()

    # factors1 dataframe  --->
    factors1 = std.fit_transform(factors1)

    # 将factors1还原成dataframe,方便后面取数据处理
    factors1 = pd.DataFrame(factors1, index=factors_data.index, columns=factors_data.columns)

    # 给中心化处理
    # 确定中性化:特征值:原始的市值,目标值:处理过后的因子数据
    x = factors_data['market_cap']
    for name in factors1.columns:
        # 跳过market_cap
        if name == "market_cap":
            continue
        # 取出因子作为目标值
        y = factors1[name]

        # 建立回归方程,得出预测结果
        # 用真是结果-预测结果得到残差,即为新的因子值
        lr = LinearRegression()

        lr.fit(x.values.reshape(-1, 1), y.values)

        # 预测结果
        y_predict = lr.predict(x.values.reshape(-1, 1))

        # 得出没有相关性的残差部分
        res = y - y_predict

        # 将残差部分作为新的因子值
        factors1[name] = res

    # 处理结束,factors1即为我们最终需要的数据结果

    # 3、选股
    # 建立回归方程,得出预测结果,然后排序选出30个股票
    # 特征值:factors1:9个因子特征值
    #  训练的权重系数为:9个权重
    # 假如5月1日,
    # 得出的结果:相当于预测接下来的5月份收益率,哪个收益率高选谁

    weights = np.array(
        [-0.01864979, -0.04537212, -0.18487143, -0.06092573, 0.18599453, -0.02088234, 0.03341527, 0.91743347,
         -0.8066782])

    # 进行特征值与权重之间的矩阵运算
    # (m行,n列) *(n行,l列) = (m行,l列)
    # (300, 9) * (9, 1) = (300, 1)
    return_ = np.matmul(factors1, weights.reshape(-1, 1))

    # logger.info(stock_return)
    #  根据收益率的大小排序去选股
    # 将股票的代码和收益率绑定一起排序
    stock_return = dict(zip(factors1.index, return_.reshape(-1)))

    # logger.info(stock_return)
    # 对字典进行排序
    score = sorted(stock_return.items(), key=lambda x: x[1], reverse=True)[:20]

    # 取出score的股票代码
    context.stocklist = [x[0] for x in score]

    # logger.info(context.stocklist)
    rebalance(context)


def rebalance(context):
    # 卖出
    for stock in context.portfolio.positions.keys():

        if context.portfolio.positions[stock].quantity > 0:

            if stock not in context.stocklist:
                order_target_percent(stock, 0)

    weight = 1.0 / len(context.stocklist)
    # 买入
    for stock in context.stocklist:
        order_target_percent(stock, weight)


# before_trading此函数会在每天策略交易开始前被调用,当天只会被调用一次
def before_trading(context):
    pass


# 你选择的证券的数据更新将会触发此段逻辑,例如日或分钟历史数据切片或者是实时数据切片更新
def handle_bar(context, bar_dict):
    pass


# after_trading函数会在每天交易结束后被调用,当天只会被调用一次
def after_trading(context):
    pass


def mad(factor):
    """3倍中位数去极值
    """
    # 求出因子值的中位数
    med = np.median(factor)

    # 求出因子值与中位数的差值,进行绝对值
    mad = np.median(np.abs(factor - med))

    # 定义几倍的中位数上下限
    high = med + (3 * 1.4826 * mad)
    low = med - (3 * 1.4826 * mad)

    # 替换上下限以外的值
    factor = np.where(factor > high, high, factor)
    factor = np.where(factor < low, low, factor)

    return factor

 

以上是关于多因子策略-回测阶段应用的主要内容,如果未能解决你的问题,请参考以下文章

如何看待量化交易的回测?

股票量化交易策略:多因子筛选练习

股票量化交易策略之选股模拟交易过程

单因子测试框架和单因子回测框架搭建

量化交易多因子策略与理论介绍

量化交易多因子的筛选阶段介绍