单因子回测

1 概要

单因子回测是量化投资领域中常用的一种策略评估方法,用于评估某一特定因子(例如市盈率、市净率、动量等)对股票或资产价格变动的预测能力以及其在投资组合中的有效性。
本文基于优矿平台详细介绍了如何进行单因子分析,包括寻找因子、构建因子、因子分析、组合构建、回测分析和附加值分析。
文中代码可以在优矿环境中复现。

2 市场中性Alpha策略介绍

  • 市场中性的 Alpha 策略旨在利用资产的价格波动,而不受整个市场的整体涨跌影响
  • 核心思想是通过同时买入和卖出相关联的资产,以达到对冲市场风险的目的,从而专注于捕捉资产之间的价差或其他价格不一致性所产生的 Alpha

2.1 理论基础

  • 假设市场完全有效,根据CAPM模型有,Rs=Rf+βs∗(Rm−Rf)
  • 市场并非完全有效,Jensen’s alpha:αs=Rs−[Rf+βs∗(Rm−Rf)]
  • 股票的收益是受多方面因素影响的,经典的Fama French三因素就告诉我们,市值大小、估值水平、以及市场因子就能解释股票收益,而且低市值、低估值能够获取超额收益
  • 假设我们已经知道了哪些因子能够获取超额收益,那么我们根据这些因子构建股票组合(比如持有低市值、低估值的股票)
  • 持有组合,做空基准,对冲获取稳定的差额收益(alpha收益)

2.2 实例展示

# 假设构建的多头组合每天跑赢基准0.1%
data = DataAPI.MktIdxdGet(ticker='000300', beginDate='20130101', field='tradeDate,CHGPct', pandas='1').set_index('tradeDate').rename(columns={'CHGPct':'benchmark'})
data['portfolio'] = data['benchmark'] + 0.001  
data.cumsum().plot(figsize=(12,5))

在这里插入图片描述

3 单因子回测流程

  • 策略的核心在于找到如上图一样能够稳健跑赢基准指数的多头组合
  • 寻找组合的核心在于找到驱使股票获得超额收益的因子
  • 本篇从单因子角度详细讲解研究步骤与细节

3.1 寻找因子

  • 符合经济、金融投资逻辑
  • 数据来源:基本面、行情、分析师预期、大数据分析
  • 作为例子:以市盈率PE为例

3.2 构建因子

  • 公式:PE = 每股价格/每股收益,将上式同时乘以总股本数就是,PE = 总市值/净利润
  • 合理性:总市值是日度变化数据,净利润则是季度数据,为了对比的一致性以及结合企业经营的实际情况,一般采用TTM值(Trailing
    twelve months)
  • 投资理念:由于我们假定是低估值会获得超额收益,所以将因子调整为 EP = 净利润/总市值,这样因子值越大就代表越高的持仓权重
# 优矿里提供了400多个因子数据,将诸如TTM类似的标准化算法工程化,可利用DataAPI直接获取
pe = DataAPI.MktStockFactorsOneDayGet(secID=set_universe('HS300'),tradeDate=u"20160922",field=u"secID,tradeDate,PE",pandas="1").set_index('secID')
pe.head()
# pe.plot(figsize=(14,5))

在这里插入图片描述

3.3 因子分析

  • 构建好EP因子之后并非直接可用,还需要将其转化为实际可用的信号(signal)
  • 常见的因子处理:去极值winsorize、中性化neutralize、标准化standardize
    在这里插入图片描述
  • 通过上述处理过的因子,得到的信号分布更平滑、更合理

3.3.1 去极值

  • 正态分布去极值:3σ原则
  • 分位数去极值
import numpy as np
# 去极值winsorize
after_winsorize = winsorize(pe['PE'].to_dict())
pe['winsorized PE'] = np.nan
pe.loc[after_winsorize.keys(),'winsorized PE'] = after_winsorize.values() 
pe.plot(figsize=(14,5)).legend(fontsize=14)

在这里插入图片描述

3.3.2 标准化

  • 普通标准化:z-score
  • 风格标准化:行业内的普通标准化
# 标准化standardize
after_standardize = standardize(pe['winsorized PE'].to_dict())
pe['standardized PE'] = np.nan
pe.loc[after_standardize.keys(),'standardized PE'] = after_standardize.values()
pe['standardized PE'].plot(figsize=(14,5))

在这里插入图片描述

3.4 组合构建

  • 选股

  • 整体分位数:从所有备选股中选择因子得分大于某个分位数

  • 风格分位数:以行业为例,在同一行业中选择因子得分大于某个分位数

  • 权重

  • 等权重:最常见也最通用

  • 市值加权:中证指数编制时的权重考虑因素就是市值

  • 风格中性权重:权重由市值和股票所属行业在指数中的权重决定

3.5 回测分析

  • 回测区间:2010年1月1日~2016年9月1日,基准为沪深300,中证800成分股,策略每20天换仓一次
  • 因子选取:市盈率PE
  • 因子处理:用到了去极值(winsorize)、标准化(standardize)处理
  • 组合构建:整体分位+风格中性权重
import pandas as pd
import numpy as np

start = '2022-03-22'
end = '2024-03-22'
benchmark = 'HS300'                       # 策略参考标准
universe = DynamicUniverse('HS300') + DynamicUniverse('ZZ500')
capital_base = 10000000                     # 起始资金
freq = 'd'                              # 策略类型,'d'表示日间策略使用日线回测,'m'表示日内策略使用分钟线回测
refresh_rate = Monthly(1)                          # 调仓频率

accounts = {
    'fantasy_account': AccountConfig(account_type='security', capital_base=10000000)
}

def initialize(context):                   # 初始化虚拟账户状态
    pass
    
def handle_data(context):                  # 每个交易日的买入卖出指令   
    universe = context.get_universe()
    yesterday = context.previous_date.strftime('%Y-%m-%d')  # 向前移动一个工作日

    data = context.history(universe, ['PE'], time_range=1, style='tas')
    data = data[yesterday]
    
    factor = data['PE'].dropna()
    
    factor = pd.Series(winsorize(factor, win_type='QuantileDraw', pvalue=0.05))  # 去极值
    factor = 1.0 / factor
    factor = factor.replace([np.inf, -np.inf], 0.0)
    signal = standardize(dict(factor))  # 标准化

    # 组合构建                
    wts = simple_long_only(signal, yesterday)

    # 交易部分
    account = context.get_account('fantasy_account')    
    current_position = account.get_positions(exclude_halt=True)    
    target_position = wts.keys()
    
    # 卖出当前持有,但目标持仓没有的部分
    for stock in set(current_position).difference(target_position):
        account.order_to(stock, 0)
    
    # 根据目标持仓权重,逐一委托下单
    for stock in target_position:
        account.order_pct_to(stock, wts[stock])

在这里插入图片描述

3.6 附加值分析

  • 找到效果比较好的单因子之后,就需要考虑单因子对已有模型的贡献
  • 从本质上来说还是多因子合成,构建多因子来看整体效果是否有提高
  • 下面的例子将市盈率和对数流通市值等权合成后的整体效果
start = '2022-03-22'
end = '2024-03-22'
benchmark = 'HS300'                       # 策略参考标准
universe = DynamicUniverse('HS300') + DynamicUniverse('ZZ500')
capital_base = 10000000                     # 起始资金
freq = 'd'                              # 策略类型,'d'表示日间策略使用日线回测,'m'表示日内策略使用分钟线回测
refresh_rate = Monthly(1)                          # 调仓频率

accounts = {
    'fantasy_account': AccountConfig(account_type='security', capital_base=10000000)
}

def initialize(context):                   # 初始化虚拟账户状态
    context.signal_generator = SignalGenerator(Signal('PE'),
                                               Signal('LCAP'))
    
def handle_data(context):                  # 每个交易日的买入卖出指令    
    universe = context.get_universe() 
    yesterday = context.previous_date.strftime('%Y-%m-%d')  # 向前移动一个工作日

    data = context.history(universe, ['PE', 'LCAP'], time_range=1, style='tas')
    data = data[yesterday]
    
    factor = data['PE']
    factor = pd.Series(winsorize(factor, win_type='QuantileDraw', pvalue=0.05))  # 去极值
    factor = 1.0 / factor
    # factor = factor.replace([np.inf, -np.inf], 0.0)
    signal_pe = standardize(dict(factor))  # 标准化
    
    factor = data['LCAP']
    factor = pd.Series(winsorize(factor, win_type='QuantileDraw', pvalue=0.05))  # 去极值
    factor = 1.0 / factor
    # factor = factor.replace([np.inf, -np.inf], 0.0)
    signal_lcap = standardize(dict(factor))  # 标准化
    
    # 信号合成
    signal = (0.5*pd.Series(signal_pe)).add(0.5*pd.Series(signal_lcap), fill_value=0.0)

    # 组合构建                
    wts = simple_long_only(dict(signal), yesterday)

    # 交易部分
    account = context.get_account('fantasy_account')    
    current_position = account.get_positions(exclude_halt=True)    
    target_position = wts.keys()
    
    # 卖出当前持有,但目标持仓没有的部分
    for stock in set(current_position).difference(target_position):
        account.order_to(stock, 0)
    
    # 根据目标持仓权重,逐一委托下单
    for stock in target_position:
        account.order_pct_to(stock, wts[stock])

在这里插入图片描述

可以看到,估值因子PE叠加市值因子LCAP后,组合alpha从6.3%降低到了6.0%,组合收益波动率从15.5%提高到了16.5%,IR从1.61降到了1.24,两种因子叠加后组合的预期收益略微降低,并且增加了组合的收益波动率,同时IR的下降表明投资组合的超额收益相对于风险的表现有所下降,这意味着估值因子PE叠加市值因子LCAP后并未产生更大的增量效应,因子是否具有附加值还依赖于投资者的主观判断和偏好。

参考文献

《多因子策略白皮书》——Datayes优矿

  • 52
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Felix.Chan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值