附录:因子检查策略代码¶
用于因子策略回测效果检查
'''
根据聚宽高频因子挖掘大赛比赛专用模板修改
初始资金:2000000
建议回测时间范围
每日调仓 一年 回测日期:20180720-20190720
每周调仓 三年 回测日期:20160720-20190720
每月调仓 五年 回测日期:20140720-20190720
股票池:中证500
每日持仓:数量固定为股票池的20只,持仓均为等权重持有
换仓时间:默认14:50
交易成本:不考虑滑点,印花税1‰,佣金2.5‱、最低5元
'''
# 导入函数库
from jqdata import *
import numpy as np
import pandas as pd
import jqfactor
################################################# 以下内容根据研究因子内容定义 ########################################################
# 定义因子
def calc_factor(context):
'''
用户自定义因子,要求返回一个 Series,index为股票code,value为因子值
我们会买入「因子值最小」的20只,如果您想使用买入「因子值最大」的20只股票,只需将您的结果「乘以-1.0」即可,详见函数下方 return 部分
'''
# 获取股票池,g.stock_pool为因子挖掘的对象股票池,用户不可对此股票池进行二次筛选
stocks = g.stock_pool
# 获取当前时间
now = context.current_dt
# 获取数据
df = get_price(stocks,end_date=context.previous_date,count=21,fields=['close'])['close']
far = df.iloc[-1,:]/df.iloc[0,:] - 1
###### 中性化等数据处理模块,用户根据需要决定是否使用 ######
# 中位数去极值
# far = jqfactor.winsorize_med(far, scale=3, inclusive=True, inf2nan=True)
# # 行业市值对数中性化
far = jqfactor.neutralize(far, how=['market_cap'], date=g.d)
# # zscore标准化
# far = jqfactor.standardlize(far, inf2nan=True)
# 去除 nan 值
# far = far.dropna()
return far
#如想选择因子值最大的20只股票,请注释上方`return far`。使用下方的return:
# return far * -1.0
# 开盘前运行函数
def before_market_open(context):
'''
盘后运行函数,可选实现
'''
pass
## 收盘后运行函数
def after_market_close(context):
'''
盘后运行函数,可选实现
'''
pass
################################################# 以下内容除设置运行周期,其他地方不用修改 ########################################################
# 初始化函数,设定基准等等
def initialize(context):
# 设定500等权作为基准
g.benchmark = '000982.XSHG'
set_benchmark(g.benchmark)
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
### 股票相关设定 ###
# 股票类每笔交易时的手续费
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.00025, close_commission=0.00025, min_commission=5),type='stock')
# 滑点
set_slippage(FixedSlippage(0.0))
# 初始化因子设置
factor_analysis_initialize(context)
# 定义股票池
set_stockpool(context)
# 运行函数(reference_security为运行时间的参考标的;传入的标的只做种类区分,因此传入'000300.XSHG'或'510300.XSHG'是一样的)
run_daily(set_stockpool, time='before_open', reference_security='000300.XSHG')
run_daily(before_market_open, time='before_open', reference_security='000300.XSHG')
#设置策略交易时间间隔
#run_daily(trade, time='14:50', reference_security='000300.XSHG')
run_weekly(trade,1, time='14:50', reference_security='000300.XSHG')
#run_monthly(trade,1, time='14:50', reference_security='000300.XSHG')
run_daily(after_market_close, time='after_close', reference_security='000300.XSHG')
# 定义股票池
def set_stockpool(context):
# 获取股票池
stocks = get_index_stocks(g.benchmark,context.previous_date)
paused_series = get_price(stocks,end_date=context.current_dt,count=1,fields='paused')['paused'].iloc[0]
# g.stock_pool 为因子挖掘的对象股票池,用户不可对此股票池进行二次筛选
g.stock_pool = paused_series[paused_series==False].index.tolist()
# 定义需要用到的全局变量
def factor_analysis_initialize(context):
# g.weight_method 为加权方式, "avg"按平均加权
g.weight_method = "avg"
weight_method_model = {"avg": "平均加权"}
# 持仓股票数量
g.buy_num = 20
# g.sell为卖出股票权重列表
g.sell = pd.Series(dtype=float)
# g.buy为买入股票权重列表
g.buy = pd.Series(dtype=float)
#g.ind为行业分类
g.ind = 'jq_l1'
# g.d 为获取昨天的时间点
g.d = context.previous_date
# 对因子进行分析计算出每日买入或卖出的股票
def fac(context):
# 获取因子值
far = calc_factor(context)
# 买入股票池
try:
buy = far.sort_values(ascending=True).index.tolist()[:g.buy_num]
except:
buy = far.order(ascending=True).index.tolist()[:g.buy_num]
# 买卖股票权重
if g.weight_method == "avg":
buy_weight = pd.Series(1. / len(buy), index=buy)
else:
raise ValueError('invalid weight_method %s', weight_method)
return buy_weight
#股票交易
def trade(context):
# 计算买入卖出的股票和权重
try:
factor_analysis_initialize(context)
g.buy = fac(context)
except ValueError:
if "Bin edges must be unique" in str(e):
log.error("计算因子值过程出错!")
else:
raise
for s in context.portfolio.positions.keys():
if s not in g.buy.index:
order_target_value(s, 0)
long_cash = context.portfolio.total_value
for s in g.buy.index:
order_target_value(s, g.buy.loc[s] * 0.98 * long_cash)
# 买入股票
def buy(context):
# 计算买入卖出的股票和权重
try:
factor_analysis_initialize(context)
g.buy = fac(context)
except ValueError:
if "Bin edges must be unique" in str(e):
log.error("计算因子值过程出错!")
else:
raise
long_cash = context.portfolio.total_value
for s in g.buy.index:
order_target_value(s, g.buy.loc[s] * 0.98 * long_cash)
# 卖出股票
def sell(context):
for s in context.portfolio.positions.keys():
order_target_value(s, 0)
请先从下面内容开始¶
因子分析基础模板¶
#导入需要的数据库
from jqfactor import *
from jqdata import *
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
#获取日期列表
def get_tradeday_list(start,end,frequency=None,count=None):
if count != None:
df = get_price('000001.XSHG',end_date=end,count=count)
else:
df = get_price('000001.XSHG',start_date=start,end_date=end)
if frequency == None or frequency =='day':
return df.index
else:
df['year-month'] = [str(i)[0:7] for i in df.index]
if frequency == 'month':
return df.drop_duplicates('year-month').index
elif frequency == 'quarter':
df['month'] = [str(i)[5:7] for i in df.index]
df = df[(df['month']=='01') | (df['month']=='04') | (df['month']=='07') | (df['month']=='10') ]
return df.drop_duplicates('year-month').index
elif frequency =='halfyear':
df['month'] = [str(i)[5:7] for i in df.index]
df = df[(df['month']=='01') | (df['month']=='06')]
return df.drop_duplicates('year-month').index
===初始化====¶
# 设置起止时间
start='2016-07-01'
end='2019-07-01'
# 设置调仓周期
periods=(5,10,20)
# 设置分层数量
quantiles=5
#获取日期列表
date_list = get_tradeday_list(start=start,end=end,count=None)#获取回测日期间的所有交易日
date_list
===原始计算因子数据===¶
- 进行因子值函数定义
- 循环日期获取因子值
股票价格的动量(Momentum) , 顾名思义代表的是股价在一定时间内延续前期走势的现象。 不过与海外长期的研究和经验相悖的是, 在 A 股市场, 我们发现股价的反转(Reverse) 效应要远强于动量效应, 且短期反转因子的历史收益非常出色。
但常用动量因子也存在单调性不佳, 多头收益不稳定的问题, 因此参考研报我们尝试从不同角度出发对动量因子进行改造, 寻找提升常用动量因子选股 效果和稳定性的方法。
在该多因子系列报告中, 曾给出过动量类因子的因子测试结论, 报告中测试的几个常用动量因子,也是我们经常接触到的基础动量因子,明细如下
下面我们将以统计周期为21天的动量因子为例进行探索演示
#定义要计算的动量因子
def factor_cal(pool,date):
df = get_price(pool,end_date=date,count=21,fields=['close'])['close']
far = df.iloc[-1,:]/df.iloc[0,:] - 1
return far
factor_cal(['000001.XSHE','600000.XSHG'],'2019-07-12')
#定义一个空的dataframe记录因子值
factor_df = pd.DataFrame()
#循环计算给定日期范围的因子值
mark = 1
for d in date_list:
pool = get_index_stocks('000905.XSHG',date=d)
far = factor_cal(pool,d)
if mark == 1:
factor_df = far
mark = 0
else:
factor_df = pd.concat([far,factor_df],axis=1)
#将columns更改为可以日期标签
factor_df.columns = date_list
factor_df.head(3)
===进行因子数据优化===¶
- 标准化
- 风格中性化
根据研报的观点,动量(反转) 效应的成因是投资者认识出现偏差,对信息的解读不够及时充分。 也可以认为动量(反转) 效应是来自于投资者认知偏差或者说噪音交易行为。
由此可以推测, 散户集中程度或者说流动性因素是影响动量因子效果的一个重要变量。 因此, 我们会自然的联想到采用截面中性化的方法, 将衡量散户集中程度的流动性因素从原始动量因子中剥离。
因此,我们接下来对原始因子数据进行特定风格中性化处理
#数据清洗、包括去极值、标准化、中性化等,并加入y值
for date in date_list:
#对数据进行处理、标准化、去极值、中性化
#factor_df = winsorize_med(factor_df, scale=3, inclusive=True, inf2nan=True, axis=0) #中位数去极值处理
se = standardlize(factor_df[date], inf2nan=True) #对每列做标准化处理
se = neutralize(se, how=['liquidity'], date=date)#剔除原始因子值与流动性相关的部分
factor_df[date] = se
#进行转置,调整为分析可用的格式
factor_df = factor_df.T
factor_df.head()
注意!!!¶
需要将处理好的factor_df格式设置为:
- factor_df是dataframe格式
- index 为日期
- columns 是股票名称
将满足如上格式内容的df传入下面的效果模板即可
因子效果检查¶
在调整了因子数据格式之后,接下来的部分,我们将利用聚宽的因子分析模板,对计算好的因子进行收益分析。
我们将通过如下三个方面进行因子效果检查
1.IC信息比率
2.分组收益
3.换手率
在收益分析中, 分位数的平均收益, 第一分位数的因子值最小, 第五分位数的因子值最大。
分位数收益: 表示持仓5、10、20天后,各分位数可以获得的平均收益。
#使用获取的因子值进行单因子分析
far = analyze_factor(factor=factor_df, start_date=date_list[0], end_date=date_list[-1], weight_method='avg', industry='jq_l1', quantiles=quantiles, periods=periods,max_loss=0.3)
IC分析
# 打印信息比率(IC)相关表
far.plot_information_table(group_adjust=False, method='rank')
IC分析:
以上展示了该因子的IC分析数据,通常来讲,我们主要观察的两个指标是IC值与IR值。
从分析结果我们看到,在持仓5天、10天、20天的分组中,持仓20天收益IC均值最大,IR信息比率最高,但是IC值不到0.03,说明该因子效果也有待提升。
分组收益
# 画各分位数平均收益图
far.plot_quantile_returns_bar(by_group=False, demeaned=0, group_adjust=False)
分组收益分析:
从五分组平均收益来看,基本上是有较为明显的分组效果,随着因子值1-5组从小到大的过程,分组跌幅也从大到小的变化。
从不同持仓周期看,持仓20天(绿色柱子)收益递增关系看起来更稳定一些
换手率分析
# 打印换手率表
far.plot_turnover_table()
换手率分析:
因子的换手率是在不同的时间周期下, 观察因子个分位中个股的进出情况。 计算方法举例: 某因子第一分位持有的股票数量为30支, 5天后有一只发生变动, 换手率为:
1/30 *100% = 3.33%
对于10日、20日的换手率,在每日都会对比当日1、5分位数的成分股与10日、20日前该分位数的成分股的变化进行计算。
从该分析结果我们看到,5、10、20不同持仓周期,5日换手率最低,20日换手率最高,1-5分组无明显区别
因子效果检查综述¶
结论:
通过以上IC分析、分组收益检查、换手率分析,我们初步对该示例因子有了一定了解
该因子有一定的收益预测能力,但是不够显著,最好的IC均值为0.027,不到0.03,存在分组效果,换手率在与因子统计周期一致是换手率最高,因子效果有待进一步优化提升
因子分析信息全览¶
具体说明可参考:
https://www.joinquant.com/help/api/help?name=factor#%E5%9B%A0%E5%AD%90%E5%88%86%E6%9E%90%E7%BB%93%E6%9E%9C
在收益分析中, 分位数的平均收益, 各分位数的累积收益, 以及分位数的多空组合收益三方面观察因子的表现。 第一分位数的因子值最小, 第五分位数的因子值最大。
分位数收益: 表示持仓5、10、20天后,各分位数可以获得的平均收益。
分位数的累积收益: 表示各分位数持仓收益的累计值。
多空组合收益: 做多五分位(因子值最大), 做空一分位(因子值最小)的投资组合的收益。
#调用因子分析方法,进行因子信息全览
far.create_full_tear_sheet(demeaned=False, group_adjust=False, by_group=False, turnover_periods=None, avgretplot=(5, 15), std_bar=False)
**分析数据的获取函数¶
这部分内容不做要求,可以根据因子分析功能 官网介绍,获取因子分析模块的原始数据进行更多的操作
# 计算指定调仓周期的各分位数每日累积收益
df = far.calc_cumulative_return_by_quantile(period=1)
#进行数据展示
df.plot(figsize=(15,6))