RSRS模型配合简单多因子策略(1)

时间:2024-04-05 13:24:33

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个——价值投资。

RSRS模型配合简单多因子策略(1)

 2018年跑出来还算差强人意,不过大多数时间都是空仓,交易也是那么几次。

这个策略我将来还会优化,这是一个比较好的策略,鲁棒性很强。

补充:

1.算排序总得分的时候,因为输入的数据是dataframe类型,所以加总的时候,用df.rank().sum(axis=1)这个地方是一个技巧——横向加总得分

2.总得分有了之后,用df.sort()排序切片就行了