本文是对报告《国信证券-单向波动率差值择时》部分内容的复现,个人理解,不保证正确性,欢迎指正!
单向波动差
根据报告的观点,波动率用来反映市场波动幅度大小。由于A股做空机制的欠缺,波动率分布不对称,因此可以把波动率区分为上行波动和下行波动。根据上行波动和下行波动的不同分布来预测市场走势。
波动率以开盘价为计准,开盘价以上的波动定义为上行波动,开盘价以下的定义为下行波动。市场处于上升阶段时,上行波动要大于下行波动,处于下跌阶段时下行波动大于上行波动。同时报告定义二者之差为剪刀差。
具体定义为:
上行波动 = (HIGH - OPEN)OPEN
下行波动 = (OPEN - LOW)/OPEN
剪刀差 = 上行波动 - 下行波动
根据报告的测试,剪刀差的预测效果要比直接使用波动率的效果好很多,因此在一个不对称的市场中把波动率分为上行波动和下行波动非常有必要。
择时策略1
基于以上的逻辑,可以根据剪刀差构造择时策略:
剪刀差N日移动平均为正时,做多;
剪刀差N日移动平均为负时,做空/平仓;
这里移动平均是为了增加策略稳定性,回测时不考虑做空,只考虑做多和平仓。
回测区间:20060406-20190531
标的:沪深300指数
语言:python
为了方便与报告结果相对比,回测区间起点与报告相同,终点设置为上月末。需要数据和代码后台回复“单向波动差”。
首先给出报告的回测结果,这里移动平均窗宽为60日
这里的净值比 = 策略净值/沪深300归一化到20060406
我回测的60日结果如下
10日与60日对比如下
基本上是一样的,说明应该没有做错。代码如下
代码语言:javascript复制"""
策略1
单向波动差移动平均为正:买入
单向波动差移动平均为负:卖出
"""
def strategy1(datas,win = 60):
datas['flag'] = 0
datas['position'] = 0
datas['vol'] = (datas.HIGH - datas.OPEN)/datas.OPEN - (datas.OPEN - datas.LOW)/datas.OPEN
datas['vol_roll'] = datas.vol.rolling(window = win,min_periods = 0).mean()
for i in range(1,datas.shape[0]):
if datas.vol_roll[i-1] > 0 :
datas.loc[i,'flag'] = 1
datas.loc[i,'position'] = 1
else:
datas.loc[i,'flag'] = 0
datas.loc[i,'position'] = 0
datas = datas.loc[datas.tradedate>=datetime.date(2006,4,6)].reset_index(drop = True)
datas['nav'] = (datas.CLOSE.pct_change(1).fillna(0)*datas.position 1).cumprod()
return datas
result1 = strategy1(price)
result2 = strategy1(price,win = 10)
相对强弱RPS
报告认为,市场处于不同状态下时,两个不同的移动平均值下策略效果是相反的,因此需要对移动平均窗口进行动态调整,以适应市场状态。市场强势时对波动率不敏感,市场调整、震荡时对波动率敏感。
报告采用RPS衡量市场的相对强弱,RPS定义如下
RPS_1 = (当前收盘价 - min(过去250日收盘价))/(max(过去250日收盘价)-min(过去250日收盘价))
RPS = RPS_1的10日移动平均值
代码语言:javascript复制def getRPS(datas,win = 60):
datas['vol'] = (datas.HIGH - datas.OPEN)/datas.OPEN - (datas.OPEN - datas.LOW)/datas.OPEN
datas['vol_roll'] = datas.vol.rolling(window = win,min_periods = 0).mean()
datas['min_250'] = datas.CLOSE.rolling(250).min()
datas['max_250'] = datas.CLOSE.rolling(250).max()
datas['RPS_1'] = (datas.CLOSE - datas.min_250)/(datas.max_250 - datas.min_250)
datas['RPS'] = datas.RPS_1.rolling(10).mean()
datas = datas.loc[datas.tradedate>=datetime.date(2006,2,6)].reset_index(drop = True)
return datas
最终算出来RPS与指数走势如下
跟报告对比确保没有算错
从图也能看出,RPS能很好的刻画出市场的状态,市场强势时,RPS高位,市场震荡时,RPS低位。因此可以用RPS来指导单向波动差的窗宽,RPS高位时,窗宽较大,RPS低位时,窗宽较小。
择时策略2
在使用RPS指导单向波动差的窗宽设定之前,报告用RPS做了一个简单回测确保这个逻辑时合理的,如图黄色部分。
报告结果如上所示,个人回测结果如下(注意这里回测起点变成了20060125)
结果基本一致,并且从结果可以看出,结果与预期一致,因此可以用RPS指导单向波动差的窗宽设置。本部分代码如下
代码语言:javascript复制"""
RPS上穿80%,买入
RPS下穿80%,卖出
"""
def strategy2(datas):
datas['flag'] = 0
datas['position'] = 0
datas['vol'] = (datas.HIGH - datas.OPEN)/datas.OPEN - (datas.OPEN - datas.LOW)/datas.OPEN
datas['pct'] = datas.CLOSE.pct_change(1).fillna(0)
datas['min_250'] = datas.CLOSE.rolling(250).min()
datas['max_250'] = datas.CLOSE.rolling(250).max()
datas['RPS_1'] = (datas.CLOSE - datas.min_250)/(datas.max_250 - datas.min_250)
datas['RPS'] = datas.RPS_1.rolling(window = 10,min_periods = 0).mean()
datas = datas.dropna().reset_index(drop = True)
for i in range(2,datas.shape[0]):
if (datas.RPS[i-2] < 0.8) & (datas.RPS[i-1] >0.8) :
datas.loc[i,'flag'] = 1
datas.loc[i,'position'] = 1
elif (datas.RPS[i-2] > 0.8) & (datas.RPS[i-1] <0.8) :
datas.loc[i,'flag'] = 0
datas.loc[i,'position'] = 0
else:
datas.loc[i,'position'] = datas.loc[i-1,'position']
datas = datas.loc[datas.tradedate>=datetime.date(2006,1,25)].reset_index(drop = True)
datas['nav'] = (datas.CLOSE.pct_change(1).fillna(0)*datas.position 1).cumprod()
return datas
result2 = strategy2(price)
X = np.arange(result2.shape[0])
xticklabel = result2.tradedate
xticks = np.arange(0,len(xticklabel),240)
plt.figure(figsize = [9,4])
SP = plt.axes()
SP.plot(X,result2['nav'],label = 'strategy',color = 'deepskyblue',linewidth = 1)
SP.plot(X,result2['CLOSE']/result1.CLOSE[0],label = '000300',color = 'darkred',linewidth = 1)
SP.set_xticks(xticks)
SP.set_xticklabels(xticklabel[xticks],rotation = 45)
plt.legend()
plt.show()
相对强弱RPS下单向波动差择时
接下来考虑用RPS指导单向波动差的窗宽设定,报告中策略步骤如下
报告最终回测的效果跟之前比非常逆天,净值曲线回撤很小,很平稳。
但是报告中并没有给出具体的RPS值跟移动平均窗宽之间的关系,所以就只能自己尝试了,这里我尝试了一个很简单的逻辑:RPS的值都在0-1之间,设定最大窗宽为60,最小为1,根据RPS对窗宽做一个线性的插值,再取整。最终回测结果如下
结果相比于之前回撤降低了很多,但远远达不到报告中的逆天效果,而且我这里还是没有考虑交易成本的,代码调整比较简单,略过。
RPS分级靠档
报告后续测试指出,前文给出的通过RPS确定窗宽的方法太灵敏,RPS的微小变动会引起窗宽的迅速调整,因此可以对RPS的值进行分级靠档,以降低交易频率,具体来说
报告回测结果如下
当然由于上一个策略没有办法完美复现,这一个就更不可能了,尝试了一下,没有什么效果,这里如果有大神复制出来了,可以交流一下。
参考文献
国信证券-基于相对强弱下单向波动差值应用-151022
国信证券-单向波动率差值择时之二:rps分级靠档减少交易频率-160321