1. 概述参考方正金工最近的研究报告『凤鸣朝阳:股价日内模式中蕴藏的选股因子』作者:魏建榕,在本文做了一些实现:
2. 因子计算市场交易其实是在交易者基于已知信息的基础上达成的,而晚上闭市之后产生的各种信息,可能在第二天开盘时达到集中释放。如何去通过市场交易特征去掌握这种信息的集中释放,一篇文章Exploring Market State and Stock Interactions on the Minute Timescale给了我们很大的启发。 作者在该文章中得到了如图的结果 中蓝线是个股之间相关系数的平均值,不难发现,下午个股之间的相关性要显著地高于上午。也就是说,上午会集中释放前夜的信息,上午是知情交易者最活跃的时候。怎么样利用这一特征呢,方正的研报中给出了一种方法凤鸣朝阳 本文中就利用这种方法构造选股因子。 xxxxxxxxxx 13 1 from datetime import datetime, timedelta 2 import matplotlib.pyplot as plt 3 from matplotlib import rc 4 rc('mathtext', default='regular') 5 import seaborn as sns 6 sns.set_style('white') 7 from matplotlib import dates 8 import numpy as np 9 import pandas as pd 10 import statsmodels.api as sm 11 import time 12 import scipy.stats as st 13 from CAL.PyCAL import * # CAL.PyCAL中包含font xxxxxxxxxx 9 1 # 已经将上下午的涨跌幅进行读取保存为文件,方便直接读取 2 3 apmret = pd.read_csv('APM_Ret.csv') 4 5 apmret = apmret.set_index(['tradeDate', 'secID']).stack().unstack(level=1) 6 apm = pd.DataFrame(index=apmret.index.levels[0], columns=apmret.columns) 7 apmret = apmret.apply(lambda x: [ np.NaN if (xx == np.inf) else xx for xx in x]) 8 9 apmret['mktRet'] = apmret.mean(axis=1) 按照研报中的方法计算因子 xxxxxxxxxx 34 1 window = 20 2 3 count = 0 4 for dt in apm.index: 5 6 count += 1 7 if count % 100 == 0: 8 print dt 9 10 # 拿取20天的上下午涨跌幅度数据 11 dt_data = apmret[apmret.index.get_level_values('tradeDate')<=dt].tail(window*2) 12 if len(dt_data) < window*2: 13 continue 14 15 # 股票涨跌幅和市场涨跌幅 16 # 市场涨跌幅为所有股票涨跌幅的简单平均 17 stk_ret = dt_data.drop('mktRet', axis=1).values 18 mkt_ret = dt_data['mktRet'].values 19 20 # 上午、下午涨跌幅和市场数据进行回归 21 aa = np.matrix([np.ones_like(range(window*2)), mkt_ret]).T 22 bb = stk_ret 23 xx = np.linalg.lstsq(aa, bb)[0] 24 residuals = bb - aa.dot(xx) 25 26 # 上午和下午的回归残差相减 27 delta = residuals[0::2,:] - residuals[1::2,:] 28 29 # stat衡量上下午残差的差异程度 30 stat = np.nanmean(delta, axis=0) * np.sqrt(window) / np.nanstd(delta, axis=0) 31 32 apm.ix[dt] = stat 33 34 apm.to_csv('APM_FullA.csv') 3. 因子截面特征3.1 加载数据文件xxxxxxxxxx 18 1 # 提取数据 2 factor_data = pd.read_csv('APM_FullA.csv') # 选股因子 3 factor_data['tradeDate'] = map(Date.toDateTime, map(DateTime.parseISO, factor_data['tradeDate'])) 4 factor_data = factor_data[factor_data.columns[:]].set_index('tradeDate') 5 6 mkt_value_data = pd.read_csv('MarketValues_FullA.csv') # 市值数据 7 mkt_value_data['tradeDate'] = map(Date.toDateTime, map(DateTime.parseISO, mkt_value_data['tradeDate'])) 8 mkt_value_data = mkt_value_data[mkt_value_data.columns[1:]].set_index('tradeDate') 9 10 forward_20d_return_data = pd.read_csv('ForwardReturns_W20_FullA.csv') # 未来20天收益率 11 forward_20d_return_data['tradeDate'] = map(Date.toDateTime, map(DateTime.parseISO, forward_20d_return_data['tradeDate'])) 12 forward_20d_return_data = forward_20d_return_data[forward_20d_return_data.columns[1:]].set_index('tradeDate') 13 14 backward_20d_return_data = pd.read_csv('BackwardReturns_W20_FullA.csv') # 过去20天收益率 15 backward_20d_return_data['tradeDate'] = map(Date.toDateTime, map(DateTime.parseISO, backward_20d_return_data['tradeDate'])) 16 backward_20d_return_data = backward_20d_return_data[backward_20d_return_data.columns[1:]].set_index('tradeDate') 17 18 factor_data[factor_data.columns[0:5]].tail()
3.2 因子截面特征xxxxxxxxxx 40 1 # 因子历史表现 2 3 n_quantile = 10 4 # 统计十分位数 5 cols_mean = ['meanQ'+str(i+1) for i in range(n_quantile)] 6 cols = cols_mean 7 corr_means = pd.DataFrame(index=factor_data.index, columns=cols) 8 9 # 计算相关系数分组平均值 10 for dt in corr_means.index: 11 qt_mean_results = [] 12 13 # 相关系数去掉nan 14 tmp_factor = factor_data.ix[dt].dropna() 15 16 pct_quantiles = 1.0/n_quantile 17 for i in range(n_quantile): 18 down = tmp_factor.quantile(pct_quantiles*i) 19 up = tmp_factor.quantile(pct_quantiles*(i+1)) 20 mean_tmp = tmp_factor[(tmp_factor<=up) & (tmp_factor>=down)].mean() 21 qt_mean_results.append(mean_tmp) 22 corr_means.ix[dt] = qt_mean_results 23 24 25 # ------------- 因子历史表现作图 ------------------------ 26 27 fig = plt.figure(figsize=(12, 6)) 28 ax1 = fig.add_subplot(111) 29 30 lns1 = ax1.plot(corr_means.index, corr_means.meanQ1, label='Q1') 31 lns2 = ax1.plot(corr_means.index, corr_means.meanQ5, label='Q5') 32 lns3 = ax1.plot(corr_means.index, corr_means.meanQ10, label='Q10') 33 34 lns = lns1+lns2+lns3 35 labs = [l.get_label() for l in lns] 36 ax1.legend(lns, labs, bbox_to_anchor=[0.5, 0.1], loc='', ncol=3, mode="", borderaxespad=0., fontsize=12) 37 ax1.set_ylabel(u'因子', fontproperties=font, fontsize=16) 38 ax1.set_xlabel(u'日期', fontproperties=font, fontsize=16) 39 ax1.set_title(u"因子历史表现", fontproperties=font, fontsize=16) 40 ax1.grid() 可以发现,其实股灾时候这个因子的值也出现一些异常,毕竟是千古跌停奇观 3.3 因子预测能力初探接下来,我们计算了每一天的因子和之后20日收益的秩相关系数 xxxxxxxxxx 46 1 # 计算了每一天的**因子**和**之后20日收益**的秩相关系数 2 3 ic_data = pd.DataFrame(index=factor_data.index, columns=['IC','pValue']) 4 5 # 计算相关系数 6 for dt in ic_data.index: 7 if dt not in forward_20d_return_data.index: 8 continue 9 10 tmp_factor = factor_data.ix[dt] 11 tmp_ret = forward_20d_return_data.ix[dt] 12 fct = pd.DataFrame(tmp_factor) 13 ret = pd.DataFrame(tmp_ret) 14 fct.columns = ['fct'] 15 ret.columns = ['ret'] 16 fct['ret'] = ret['ret'] 17 fct = fct[~np.isnan(fct['fct'])][~np.isnan(fct['ret'])] 18 if len(fct) < 5: 19 continue 20 21 ic, p_value = st.spearmanr(fct['fct'],fct['ret']) # 计算秩相关系数 RankIC 22 ic_data['IC'][dt] = ic 23 ic_data['pValue'][dt] = p_value 24 25 ic_data.dropna(inplace=True) 26 27 print 'mean of IC: ', ic_data['IC'].mean(), ';', 28 print 'median of IC: ', ic_data['IC'].median() 29 print 'the number of IC(all, plus, minus): ', (len(ic_data), len(ic_data[ic_data.IC>0]), len(ic_data[ic_data.IC<0])) 30 31 32 # 每一天的**因子**和**之后20日收益**的秩相关系数作图 33 34 fig = plt.figure(figsize=(16, 6)) 35 ax1 = fig.add_subplot(111) 36 37 lns1 = ax1.plot(ic_data[ic_data>0].index, ic_data[ic_data>0].IC, '.r', label='IC(plus)') 38 lns2 = ax1.plot(ic_data[ic_data<0].index, ic_data[ic_data<0].IC, '.b', label='IC(minus)') 39 40 lns = lns1+lns2 41 labs = [l.get_label() for l in lns] 42 ax1.legend(lns, labs, bbox_to_anchor=[0.6, 0.1], loc='', ncol=2, mode="", borderaxespad=0., fontsize=12) 43 ax1.set_ylabel(u'相关系数', fontproperties=font, fontsize=16) 44 ax1.set_xlabel(u'日期', fontproperties=font, fontsize=16) 45 ax1.set_title(u"因子和之后20日收益的秩相关系数", fontproperties=font, fontsize=16) 46 ax1.grid() mean of IC: 0.0216027879304 ; median of IC: 0.0239615147292
the number of IC(all, plus, minus): (1804, 1121, 683)
从上面计算结果可知,该因子和之后20日收益的秩相关系数在大部分时间为正,因子对之后20日收益有预测性 4. 历史回测概述本节使用2009年以来的数据对于该选股因子进行回测,进一步简单涉及几个风险因子暴露情况 4.1 该因子选股的分组超额收益(月度)xxxxxxxxxx 49 1 n_quantile = 10 2 # 统计十分位数 3 cols_mean = [i+1 for i in range(n_quantile)] 4 cols = cols_mean 5 6 excess_returns_means = pd.DataFrame(index=factor_data.index, columns=cols) 7 8 # 计算因子分组的超额收益平均值 9 for dt in excess_returns_means.index: 10 if dt not in forward_20d_return_data.index: 11 continue 12 qt_mean_results = [] 13 14 tmp_factor = factor_data.ix[dt].dropna() 15 tmp_return = forward_20d_return_data.ix[dt].dropna() 16 tmp_return = tmp_return[tmp_return<4.0] 17 tmp_return_mean = tmp_return.mean() 18 19 pct_quantiles = 1.0/n_quantile 20 for i in range(n_quantile): 21 down = tmp_factor.quantile(pct_quantiles*i) 22 up = tmp_factor.quantile(pct_quantiles*(i+1)) 23 i_quantile_index = tmp_factor[(tmp_factor<=up) & (tmp_factor>=down)].index 24 mean_tmp = tmp_return[i_quantile_index].mean() - tmp_return_mean 25 qt_mean_results.append(mean_tmp) 26 27 excess_returns_means.ix[dt] = qt_mean_results 28 29 excess_returns_means.dropna(inplace=True) 30 31 # 因子分组的超额收益作图 32 fig = plt.figure(figsize=(12, 6)) 33 ax1 = fig.add_subplot(111) 34 35 excess_returns_means_dist = excess_returns_means.mean() 36 excess_dist_plus = excess_returns_means_dist[excess_returns_means_dist>0] 37 excess_dist_minus = excess_returns_means_dist[excess_returns_means_dist<0] 38 lns2 = ax1.bar(excess_dist_plus.index, excess_dist_plus.values, align='center', color='r', width=0.35) 39 lns3 = ax1.bar(excess_dist_minus.index, excess_dist_minus.values, align='center', color='g', width=0.35) 40 41 ax1.set_xlim(left=0.5, right=len(excess_returns_means_dist)+0.5) 42 # ax1.set_ylim(-0.008, 0.008) 43 ax1.set_ylabel(u'超额收益', fontproperties=font, fontsize=16) 44 ax1.set_xlabel(u'十分位分组', fontproperties=font, fontsize=16) 45 ax1.set_xticks(excess_returns_means_dist.index) 46 ax1.set_xticklabels([int(x) for x in ax1.get_xticks()], fontproperties=font, fontsize=14) 47 ax1.set_yticklabels([str(x*100)+'0%' for x in ax1.get_yticks()], fontproperties=font, fontsize=14) 48 ax1.set_title(u"因子选股分组超额收益", fontproperties=font, fontsize=16) 49 ax1.grid() 可以看到,该因子选股不同分位数组合的超额收益呈很好的单调性;因子空头收益更显著 4.2 因子选股的市值分布特征检查因子的小市值暴露情况。因为很多策略因为小市值暴露在A股市场表现优异。 xxxxxxxxxx 50 1 # 计算因子分组的市值分位数平均值 2 def quantile_mkt_values(signal_df, mkt_df): 3 n_quantile = 10 4 # 统计十分位数 5 cols_mean = [i+1 for i in range(n_quantile)] 6 cols = cols_mean 7 8 mkt_value_means = pd.DataFrame(index=signal_df.index, columns=cols) 9 10 # 计算相关系数分组的市值分位数平均值 11 for dt in mkt_value_means.index: 12 if dt not in mkt_df.index: 13 continue 14 qt_mean_results = [] 15 16 # 相关系数去掉nan和绝对值大于0.97的 17 tmp_factor = signal_df.ix[dt].dropna() 18 tmp_mkt_value = mkt_df.ix[dt].dropna() 19 tmp_mkt_value = tmp_mkt_value.rank()/len(tmp_mkt_value) 20 21 pct_quantiles = 1.0/n_quantile 22 for i in range(n_quantile): 23 down = tmp_factor.quantile(pct_quantiles*i) 24 up = tmp_factor.quantile(pct_quantiles*(i+1)) 25 i_quantile_index = tmp_factor[(tmp_factor<=up) & (tmp_factor>=down)].index 26 mean_tmp = tmp_mkt_value[i_quantile_index].mean() 27 qt_mean_results.append(mean_tmp) 28 mkt_value_means.ix[dt] = qt_mean_results 29 mkt_value_means.dropna(inplace=True) 30 return mkt_value_means.mean() 31 32 # 计算因子分组的市值分位数平均值 33 origin_mkt_means = quantile_mkt_values(factor_data, mkt_value_data) 34 35 # 因子分组的市值分位数平均值作图 36 fig = plt.figure(figsize=(12, 6)) 37 ax1 = fig.add_subplot(111) 38 39 width = 0.3 40 lns1 = ax1.bar(origin_mkt_means.index, origin_mkt_means.values, align='center', width=width) 41 42 ax1.set_ylim(0.3,0.6) 43 ax1.set_xlim(left=0.5, right=len(origin_mkt_means)+0.5) 44 ax1.set_ylabel(u'市值百分位数', fontproperties=font, fontsize=16) 45 ax1.set_xlabel(u'十分位分组', fontproperties=font, fontsize=16) 46 ax1.set_xticks(origin_mkt_means.index) 47 ax1.set_xticklabels([int(x) for x in ax1.get_xticks()], fontproperties=font, fontsize=14) 48 ax1.set_yticklabels([str(x*100)+'0%' for x in ax1.get_yticks()], fontproperties=font, fontsize=14) 49 ax1.set_title(u"因子分组市值分布特征", fontproperties=font, fontsize=16) 50 ax1.grid() 上图展示,该选股因子并没有明显的小市值暴露;倒是多头组合(第十分位组合)市值略大 4.3 因子分组选股的一个月反转分布特征xxxxxxxxxx 50 1 n_quantile = 10 2 # 统计十分位数 3 cols_mean = [i+1 for i in range(n_quantile)] 4 cols = cols_mean 5 hist_returns_means = pd.DataFrame(index=factor_data.index, columns=cols) 6 7 # 因子分组的一个月反转分布特征 8 for dt in hist_returns_means.index: 9 if dt not in backward_20d_return_data.index: 10 continue 11 qt_mean_results = [] 12 13 # 去掉nan 14 tmp_factor = factor_data.ix[dt].dropna() 15 tmp_return = backward_20d_return_data.ix[dt].dropna() 16 tmp_return_mean = tmp_return.mean() 17 18 pct_quantiles = 1.0/n_quantile 19 for i in range(n_quantile): 20 down = tmp_factor.quantile(pct_quantiles*i) 21 up = tmp_factor.quantile(pct_quantiles*(i+1)) 22 i_quantile_index = tmp_factor[(tmp_factor<=up) & (tmp_factor>=down)].index 23 mean_tmp = tmp_return[i_quantile_index].mean() - tmp_return_mean 24 qt_mean_results.append(mean_tmp) 25 26 hist_returns_means.ix[dt] = qt_mean_results 27 28 hist_returns_means.dropna(inplace=True) 29 30 # 一个月反转分布特征作图 31 fig = plt.figure(figsize=(12, 6)) 32 ax1 = fig.add_subplot(111) 33 ax2 = ax1.twinx() 34 35 hist_returns_means_dist = hist_returns_means.mean() 36 lns1 = ax1.bar(hist_returns_means_dist.index, hist_returns_means_dist.values, align='center', width=0.35) 37 lns2 = ax2.plot(excess_returns_means_dist.index, excess_returns_means_dist.values, 'o-r') 38 39 ax1.legend(lns1, ['20 day return(left axis)'], loc=2, fontsize=12) 40 ax2.legend(lns2, ['excess return(right axis)'], fontsize=12) 41 ax1.set_xlim(left=0.5, right=len(hist_returns_means_dist)+0.5) 42 ax1.set_ylabel(u'历史一个月收益率', fontproperties=font, fontsize=16) 43 ax2.set_ylabel(u'超额收益', fontproperties=font, fontsize=16) 44 ax1.set_xlabel(u'十分位分组', fontproperties=font, fontsize=16) 45 ax1.set_xticks(hist_returns_means_dist.index) 46 ax1.set_xticklabels([int(x) for x in ax1.get_xticks()], fontproperties=font, fontsize=14) 47 ax1.set_yticklabels([str(x*100)+'%' for x in ax1.get_yticks()], fontproperties=font, fontsize=14) 48 ax2.set_yticklabels([str(x*100)+'0%' for x in ax2.get_yticks()], fontproperties=font, fontsize=14) 49 ax1.set_title(u"因子选股一个月历史收益率(一个月反转因子)分布特征", fontproperties=font, fontsize=16) 50 ax1.grid() 可以看出,因子和反转因子的相关性较强 5. 因子历史回测净值表现5.1 简单做多策略接下来,考察因子的选股能力的回测效果。历史回测的基本设置如下:
xxxxxxxxxx 40 1 start = '2009-03-01' # 回测起始时间 2 end = '2016-10-12' # 回测结束时间 3 4 benchmark = 'ZZ500' # 策略参考标准 5 universe = set_universe('A') # 证券池,支持股票和基金 6 capital_base = 10000000 # 起始资金 7 freq = 'd' # 策略类型,'d'表示日间策略使用日线回测 8 refresh_rate = 10 # 调仓频率,表示执行handle_data的时间间隔 9 10 factor_data = pd.read_csv('APM_FullA.csv') # 读取因子数据 11 factor_data = factor_data[factor_data.columns[:]].set_index('tradeDate') 12 factor_dates = factor_data.index.values 13 14 quantile_ten = 10 # 选取股票的因子十分位数,1表示选取股票池中因子最小的10%的股票 15 commission = Commission(0.0002,0.0002) # 交易费率设为双边万分之二 16 17 def initialize(account): # 初始化虚拟账户状态 18 pass 19 20 def handle_data(account): # 每个交易日的买入卖出指令 21 pre_date = account.previous_date.strftime("%Y-%m-%d") 22 if pre_date not in factor_dates: # 因子只在每个月底计算,所以调仓也在每月最后一个交易日进行 23 return 24 25 # 拿取调仓日前一个交易日的因子,并按照相应十分位选择股票 26 q = factor_data.ix[pre_date].dropna() 27 q_min = q.quantile((quantile_ten-1)*0.1) 28 q_max = q.quantile(quantile_ten*0.1) 29 my_univ = q[q>=q_min][q<q_max].index.values 30 31 # 调仓逻辑 32 univ = [x for x in my_univ if x in account.universe] 33 34 # 不在股票池中的,清仓 35 for stk in account.valid_secpos: 36 if stk not in univ: 37 order_to(stk, 0) 38 # 在目标股票池中的,等权买入 39 for stk in univ: 40 order_pct_to(stk, 1.1/len(univ))
WARNING: account.valid_secpos is depreciated, please use account.security_position instead. xxxxxxxxxx 29 1 fig = plt.figure(figsize=(12,5)) 2 fig.set_tight_layout(True) 3 ax1 = fig.add_subplot(111) 4 ax2 = ax1.twinx() 5 ax1.grid() 6 7 bt_quantile_ten = bt 8 data = bt_quantile_ten[[u'tradeDate',u'portfolio_value',u'benchmark_return']] 9 data['portfolio_return'] = data.portfolio_value/data.portfolio_value.shift(1) - 1.0 10 data['portfolio_return'].ix[0] = data['portfolio_value'].ix[0]/ 10000000.0 - 1.0 11 data['excess_return'] = data.portfolio_return - data.benchmark_return 12 data['excess'] = data.excess_return + 1.0 13 data['excess'] = data.excess.cumprod() 14 data['portfolio'] = data.portfolio_return + 1.0 15 data['portfolio'] = data.portfolio.cumprod() 16 data['benchmark'] = data.benchmark_return + 1.0 17 data['benchmark'] = data.benchmark.cumprod() 18 # ax.plot(data[['portfolio','benchmark','excess']], label=str(qt)) 19 ax1.plot(data['tradeDate'], data[['portfolio']], label='portfolio(left)') 20 ax1.plot(data['tradeDate'], data[['benchmark']], label='benchmark(left)') 21 ax2.plot(data['tradeDate'], data[['excess']], label='hedged(right)', color='r') 22 23 ax1.legend(loc=2) 24 ax2.legend(loc=0) 25 ax2.set_ylim(bottom=1.0, top=5) 26 ax1.set_ylabel(u"净值", fontproperties=font, fontsize=16) 27 ax2.set_ylabel(u"对冲指数净值", fontproperties=font, fontsize=16) 28 ax2.set_ylabel(u"对冲指数净值", fontproperties=font, fontsize=16) 29 ax1.set_title(u"因子最小的10%股票月度调仓走势", fontproperties=font, fontsize=16) <matplotlib.text.Text at 0x1c447210> 上图显示了简单做多因子最大的10%的股票之后的对冲净值走势,需要注意这里对冲基准为中证500指数 5.2 因子选股 —— 不同五分位数组合回测走势比较xxxxxxxxxx 82 1 # 可编辑部分与 strategy 模式一样,其余部分按本例代码编写即可 2 3 # -----------回测参数部分开始,可编辑------------ 4 start = '2009-03-01' # 回测起始时间 5 end = '2016-10-12' # 回测结束时间 6 benchmark = 'ZZ500' # 策略参考标准 7 universe = set_universe('A') # 证券池,支持股票和基金 8 capital_base = 10000000 # 起始资金 9 freq = 'd' # 策略类型,'d'表示日间策略使用日线回测 10 refresh_rate = 10 # 调仓频率,表示执行handle_data的时间间隔 11 12 factor_data = pd.read_csv('APM_FullA.csv') # 读取因子数据 13 factor_data = factor_data[factor_data.columns[:]].set_index('tradeDate') 14 q_dates = factor_data.index.values 15 16 quantile_five = 1 # 选取股票的因子十分位数,1表示选取股票池中因子最小的10%的股票 17 commission = Commission(0.0002,0.0002) # 交易费率设为双边万分之二 18 # ---------------回测参数部分结束---------------- 19 20 21 # 把回测参数封装到 SimulationParameters 中,供 quick_backtest 使用 22 sim_params = quartz.SimulationParameters(start, end, benchmark, universe, capital_base) 23 # 获取回测行情数据 24 idxmap, data = quartz.get_daily_data(sim_params) 25 # 运行结果 26 results = {} 27 28 # 调整参数(选取股票的Q因子五分位数),进行快速回测 29 for quantile_five in range(1, 6): 30 31 # ---------------策略逻辑部分---------------- 32 refresh_rate = 1 33 commission = Commission(0.0002, 0.0002) 34 35 def initialize(account): # 初始化虚拟账户状态 36 pass 37 38 def handle_data(account): # 每个交易日的买入卖出指令 39 pre_date = account.previous_date.strftime("%Y-%m-%d") 40 if pre_date not in q_dates: # 因子只在每个月底计算,所以调仓也在每月最后一个交易日进行 41 return 42 43 # 拿取调仓日前一个交易日的因子,并按照相应十分位选择股票 44 q = factor_data.ix[pre_date].dropna() 45 # q = q[q>0] 46 q_min = q.quantile((quantile_five-1)*0.2) 47 q_max = q.quantile(quantile_five*0.2) 48 my_univ = q[q>=q_min][q<q_max].index.values 49 50 # 调仓逻辑 51 univ = [x for x in my_univ if x in account.universe] 52 # 不在股票池中的,清仓 53 for stk in account.valid_secpos: 54 if stk not in univ: 55 order_to(stk, 0) 56 # 在目标股票池中的,等权买入 57 for stk in univ: 58 order_pct_to(stk, 1.01/len(univ)) 59 # ---------------策略逻辑部分结束---------------- 60 61 # 把回测逻辑封装到 TradingStrategy 中,供 quick_backtest 使用 62 strategy = quartz.TradingStrategy(initialize, handle_data) 63 # 回测部分 64 bt, acct = quartz.quick_backtest(sim_params, strategy, idxmap, data, refresh_rate=refresh_rate, commission=commission) 65 66 # 对于回测的结果,可以通过 perf_parse 函数计算风险指标 67 perf = quartz.perf_parse(bt, acct) 68 69 # 保存运行结果 70 tmp = {} 71 tmp['bt'] = bt 72 tmp['annualized_return'] = perf['annualized_return'] 73 tmp['volatility'] = perf['volatility'] 74 tmp['max_drawdown'] = perf['max_drawdown'] 75 tmp['alpha'] = perf['alpha'] 76 tmp['beta'] = perf['beta'] 77 tmp['sharpe'] = perf['sharpe'] 78 tmp['information_ratio'] = perf['information_ratio'] 79 80 results[quantile_five] = tmp 81 print str(quantile_five), 82 print 'done' warning: get_daily_data is depreciated, please use get_backtest_data instead
WARNING: account.valid_secpos is depreciated, please use account.security_position instead.
1 2 3 4 5 done
xxxxxxxxxx 54 1 fig = plt.figure(figsize=(10,8)) 2 fig.set_tight_layout(True) 3 ax1 = fig.add_subplot(211) 4 ax2 = fig.add_subplot(212) 5 ax1.grid() 6 ax2.grid() 7 8 for qt in results: 9 bt = results[qt]['bt'] 10 11 data = bt[[u'tradeDate',u'portfolio_value',u'benchmark_return']] 12 data['portfolio_return'] = data.portfolio_value/data.portfolio_value.shift(1) - 1.0 # 总头寸每日回报率 13 data['portfolio_return'].ix[0] = data['portfolio_value'].ix[0]/ 10000000.0 - 1.0 14 data['excess_return'] = data.portfolio_return - data.benchmark_return # 总头寸每日超额回报率 15 data['excess'] = data.excess_return + 1.0 16 data['excess'] = data.excess.cumprod() # 总头寸对冲指数后的净值序列 17 data['portfolio'] = data.portfolio_return + 1.0 18 data['portfolio'] = data.portfolio.cumprod() # 总头寸不对冲时的净值序列 19 data['benchmark'] = data.benchmark_return + 1.0 20 data['benchmark'] = data.benchmark.cumprod() # benchmark的净值序列 21 results[qt]['hedged_max_drawdown'] = max([1 - v/max(1, max(data['excess'][:i+1])) for i,v in enumerate(data['excess'])]) # 对冲后净值最大回撤 22 results[qt]['hedged_volatility'] = np.std(data['excess_return'])*np.sqrt(252) 23 results[qt]['hedged_annualized_return'] = (data['excess'].values[-1])**(252.0/len(data['excess'])) - 1.0 24 # data[['portfolio','benchmark','excess']].plot(figsize=(12,8)) 25 # ax.plot(data[['portfolio','benchmark','excess']], label=str(qt)) 26 ax1.plot(data['tradeDate'], data[['portfolio']], label=str(qt)) 27 ax2.plot(data['tradeDate'], data[['excess']], label=str(qt)) 28 29 30 ax1.legend(loc=0) 31 ax2.legend(loc=0) 32 ax1.set_ylabel(u"净值", fontproperties=font, fontsize=16) 33 ax2.set_ylabel(u"对冲净值", fontproperties=font, fontsize=16) 34 ax1.set_title(u"因子不同五分位数分组选股净值走势", fontproperties=font, fontsize=16) 35 ax2.set_title(u"因子不同五分位数分组选股对冲中证500指数后净值走势", fontproperties=font, fontsize=16) 36 37 # results 转换为 DataFrame 38 import pandas 39 results_pd = pandas.DataFrame(results).T.sort_index() 40 41 results_pd = results_pd[[u'alpha', u'beta', u'information_ratio', u'sharpe', 42 u'annualized_return', u'max_drawdown', u'volatility', 43 u'hedged_annualized_return', u'hedged_max_drawdown', u'hedged_volatility']] 44 45 for col in results_pd.columns: 46 results_pd[col] = [np.round(x, 3) for x in results_pd[col]] 47 48 cols = [(u'风险指标', u'Alpha'), (u'风险指标', u'Beta'), (u'风险指标', u'信息比率'), (u'风险指标', u'夏普比率'), 49 (u'纯股票多头时', u'年化收益'), (u'纯股票多头时', u'最大回撤'), (u'纯股票多头时', u'收益波动率'), 50 (u'对冲后', u'年化收益'), (u'对冲后', u'最大回撤'), 51 (u'对冲后', u'收益波动率')] 52 results_pd.columns = pd.MultiIndex.from_tuples(cols) 53 results_pd.index.name = u'五分位组别' 54 results_pd
上图显示出,因子选股不同五分位构建等权组合,在uqer进行真实回测的净值曲线;显示出因子很强的选股能力,不同五分位组合净值曲线随时间推移逐渐散开。 参考资料:方正金工专题报告《凤鸣朝阳:股价日内模式中蕴藏的选股因子》,作者:魏建榕 |
|