RSRS(下文通用rsrs)这个模型我很早就想写篇文章,这个模型在没有光大证券写的那篇文章之前,我自己就习惯用这个方式去交易股票,当然对于很多不知道这个策略的投资者而言,只要盯盘超过1个月马上就能发现这个小技巧,并不算什么高深的技术手段,但是把这个指标变成策略的话,坑真心是多啊,本篇以填坑为主,下篇文章,我会根据这个策略进行修改,加上我认为更对的一些方法。
rsrs策略的思想很简单——其实交易本身也简单,复杂的是人心,好的策略往往不复杂——按照一定时间(比如18天)对最高价和最低价进行线性回归拟合,得出线性回归的斜率,仅仅用肉眼观察就可以得出明细的结论——斜率越大,趋势越倾向于延续,斜率越小,趋势越倾向于反转。
所以策略就很简单了:
对于股票而言,当斜率大于某个值,做多,小于某个值,空仓;
期货在此基础上,可以加一个小于某个值做空,中间值空仓。
典型的三分类(期货)或者二分类(股票)问题,并不需要机器学习那种高等算法,而且仅仅肉眼就能观察到。
接下来就是代码上的坑。
在用这个策略交易的时候,发现如果仅仅是使用斜率作为判断依据,有时候个别的走势使得斜率发生很大变化,造成交易亏损严重——信噪比低的必然结果——所以在编程的时候,不用斜率,而是将斜率进行标准化变换——去噪。
因为标准化的时候,要用斜率的时间序列去求均值和标准差,所以在编程的时候不能简单的用同期斜率去标准化——因为数据不够,必须加入之前更多的数据才行,所以编程的时候这个地方要注意。
old=get_price('000300.XSHG', start_date='2010-01-01', end_date=context.previous_date, frequency='1d', fields=['high','low'])
#假如我要回测的是2018.1.1的数据,但是实际上,我要之前很久的数据才能拿到斜率的时间序列,去标准化——这里我用的是2010.1.1之后的数据
for i in range(len(old['low']))[g.n:]:
old_low=old['low'][i-g.n+1:i+1].values
oldx=st.add_constant(old_low)
old_high=old['high'][i-g.n+1:i+1].values
oldmodel=st.OLS(old_high,oldx)
g.oldans.append(oldmodel.fit().params[1])
#这个地方很坑,因为range()函数是有头无尾,而且,我要是每隔18天(g.n)就求一次的斜率的时间序列,所以#for循环的时候,一定注意,最后一天要+1才能取到,前18天是没有值的,所以不是从0开始进行for循环,而是##从g.n开始循环,因为开始那天也算,所以也要+1,才能保证是18天——(i+1)-(i-g.n+1)。
1.假如我要回测的是2018.1.1的数据,但是实际上,我要之前很久的数据才能拿到斜率的时间序列去标准化——这里我用的是2010.1.1之后的数据
2.这个地方很坑,因为range()函数是有头无尾,而且,我要是每隔18天(g.n)就求一次的斜率的时间序列,for循环的时候,一定注意,最后一天要+1才能取到。因为前18天是没有值的,所以不是从0开始进行for循环,而是从g.n开始循环,因为开始那天也算,所以也要+1,才能保证是18天——(i+1)-(i-g.n+1)。
最深的坑来了。
随后是计算斜率的均值和标准差,用来降噪,使用的是z-score标准化,这个地方很简单,难点是光大用的不是标准化之后的斜率,而是将标准化的斜率*决定系数*斜率。
标准化的斜率*决定系数好理解,就是用决定系数作为权重,调整了标准化斜率,关键是为什么要再乘以斜率,给出的解释是这么做之后,标准化的斜率分布从正态分布将转为右偏正态分布。使用右偏正态分布的效果更好,从回测的结论来看的确是好了,理论上我实在没搞懂,有正态分布不用非要用偏态分布,实在让人匪夷所思。
最后就是多因子这块没什么坑,代码稍微有点小难,不过理解比较容易,股票池用的是沪深300,用了两个因子,一个PB,一个是ROE的倒数,两个分别单项排名得分,然后加总之后,再排序,取分数最小的前n个——价值投资。
2018年跑出来还算差强人意,不过大多数时间都是空仓,交易也是那么几次。
这个策略我将来还会优化,这是一个比较好的策略,鲁棒性很强。
补充:
1.算排序总得分的时候,因为输入的数据是dataframe类型,所以加总的时候,用df.rank().sum(axis=1)这个地方是一个技巧——横向加总得分
2.总得分有了之后,用df.sort()排序切片就行了