说明
上一篇提到,一般有两种方式构造大小盘轮动策略:
一种是从技术面出发,基于量价指标建立模型,典型的比如上篇复制的基于相对强弱指标的大小盘轮动策略。
另一种是从基本面出发,考虑影响股票回报的各项因素,以此为基础构建模型。
本文属于第二种,文章主要参考报告包括:
《国泰君安-风格投资IV:A股大小盘风格轮动研究-11112》、《20161202-申万宏源-申万宏源兼谈FOF构建中的市场风格识别:解密大小盘风格轮动之logistic模型》、《安信证券-四维视角下的大小盘风格轮动-因子法》。
报告、数据和代码后台回复“代码”获取。
02
股价影响因素
[1]中将影响股票回报率的因素分为估值因素、动量因素、货币因素、经济增长因素四大类,我们结合三篇报告,最终使用如下各项因子进行建模:
共计13项指标,数据来自WIND,提取数据代码略。各项因子的分析不再说明,参见报告。
由于数据披露的滞后性,为了防止出现未来信息,回测时对各项因子进行滞后处理。比如滞后两期的意思是,当期为10月时,使用8月份的数据。
各因子相关性如图。
03
基于Logistic回归模型的大小盘轮动
基本面数据与量价数据的最大不同在于,量价因子的及时性高但持续性弱,基本面数据披露有一定滞后性,响应没有量价因子那么迅速强烈,但持续时间较长。因此回测时采用月频调仓。
Logistic回归模型的原理不具体说明,python中可以直接通过函数sklearn.LogisticRegression完成。
总体思路为,通过logistic模型预测未来市场是大盘走强还是小盘走强,因变量Y用小盘指数收益率和大盘指数收益率的差值定义,Y = 1 若差值大于0,表明小盘走强;Y = 1 若差值小于0,表明大盘走强。
每一期末,利用历史数据预测下一期Y为0还是1,为1则下一期配置小盘指数,为0则下一期配置大盘指数。
采用这种策略的收益上下界可以预期,分别对应模型预测准确率100%,0%的情况,分别对应图中蓝色、橙色曲线。
04
轮动策略1
回测区间:2005年5月-2018年7月
大盘指数:HS300(000300.SH)
小盘指数:ZZ500(000905.SH)
使用所有13个因子作为自变量,建立Logistic模型,代码如下
代码语言:javascript复制def backtest(j,datause):
datause['predict'] = np.nan
for i in range(5,datause.shape[0]):
X = datause.iloc[max(0,i - j):i,1:14].values
Y = datause.iloc[max(0,i - j):i,14].values
X_pred = datause.iloc[i,1:14].values.reshape(1, -1)
clf = LogisticRegression(random_state=0, solver='lbfgs').fit(X, Y)
datause.loc[i,'predict'] = clf.predict(X_pred)[0]
flags = datause[['ym','predict','next_Y']].dropna()
flags['next_ym'] = flags.ym.apply(lambda x: x 1 if x0 < 12 else (x//100 1)*100 1)
result = pd.merge(Indexprice,flags,left_on = 'ym',right_on = 'next_ym')
result['net_zz'] = result['ZZ500']/result['ZZ500'][0]
result['net_hs'] = result['hs300']/result['hs300'][0]
result['ret_zz'] = result['ZZ500'].pct_change(1).fillna(0)
result['ret_hs'] = result['hs300'].pct_change(1).fillna(0)
result['strategy_ret'] = result.ret_zz*(result.predict ==1) result.ret_hs*(result.predict == 0)
result['net_strategy'] = (result.strategy_ret 1).cumprod()
return result
其中,j为用来训练模型的数据期数。如果每次使用过去所有数据训练模型,结果如下
明显优于大盘指数,但不如小盘指数。
考虑滚动的方式,每次只使用过去j期的数据,我们对j从10-100进行循环计算每个参数下的策略净值和预测准确率,结果如下
j = 20时,预测准确率62.19%,策略净值2.28。
代码语言:javascript复制result20 = backtest(20,datause)
X = np.arange(result20.shape[0])
xticklabel = result20.stockdate
xticks = np.arange(0,result20.shape[0],np.int((result10.shape[0] 1)/7))
plt.figure(figsize = [20,5])
SP = plt.axes()
SP.plot(X,result20['net_strategy'],label = 'net_strategy',color = 'deepskyblue',linewidth = 3)
SP.plot(X,result20['net_zz'],label = 'net_zz',color = 'cornflowerblue',linewidth = 2)
SP.plot(X,result20['net_hs'],label = 'net_hs',color = 'darkred',linewidth = 2)
SP.set_xticks(xticks)
SP.set_xticklabels(xticklabel[xticks],size = 20)
plt.legend()
plt.show()
05
轮动策略2
策略2出发点为,不同因素在不同时刻对于股价的影响不尽相同,因此建立Logistic模型时,考虑只使用与当期所用数据中因变量相关性最高(相关系数绝对值最大)的5个因子,其余同策略1。
代码语言:javascript复制# j 训练模型使用的数据长度
def backtest2(j,datause):
datause['predict'] = np.nan
for i in range(5,datause.shape[0]):
col = pd.DataFrame(datause[max(0,i - j):i - 1].corr()['Y'][1:14].abs()).sort_values('Y',ascending = False)[:5].index.tolist()
X = datause.loc[max(0,i - j):i - 1,col].values
Y = datause.loc[max(0,i - j):i - 1,'Y'].values
X_pred = datause.loc[i,col].values.reshape(1, -1)
clf = LogisticRegression(random_state=0, solver='lbfgs').fit(X, Y)
datause.loc[i ,'predict'] = clf.predict(X_pred)[0]
flags = datause[['ym','predict','next_Y']].dropna()
flags['next_ym'] = flags.ym.apply(lambda x: x 1 if x0 < 12 else (x//100 1)*100 1)
result = pd.merge(Indexprice,flags,left_on = 'ym',right_on = 'next_ym')
result['net_zz'] = result['ZZ500']/result['ZZ500'][0]
result['net_hs'] = result['hs300']/result['hs300'][0]
result['ret_zz'] = result['ZZ500'].pct_change(1).fillna(0)
result['ret_hs'] = result['hs300'].pct_change(1).fillna(0)
result['strategy_ret'] = result.ret_zz*(result.predict ==1) result.ret_hs*(result.predict == 0)
result['net_strategy'] = (result.strategy_ret 1).cumprod()
return result
不同j净值及准确率如下
j = 10时,预测准确率61.4%,策略净值2.38。
整体来看,策略效果并不好,练手, 看看就好,欢迎指正!!!