【续】复现网红阻力支撑指标RSRS,手把手教你构建大盘择时策略

前文再续,书接上回,之前讲过光大金工的研报《基于阻力支撑相对强度的市场择时》,并对其中的阻力支撑相对强度RSRS指标/策略进行了复现,最终结果跟研报当中的一致,庆幸木有翻车。

图片

但那篇研报的核心分为3个部分,首先是RSRS概念的提出以及斜率策略和标准分策略的构建,接下来就是对标准分策略进行优化,提出了优化标准分策略和右偏标准分策略,最后就是将前面筛选出来的最优策略,再结合量价数据进行优化。

上一篇文章《复现网红阻力支撑指标RSRS,手把手教你构建大盘择时策略》主要复现的就是这篇研报当中的第一部分,我向来是既挖坑又填坑的新时代三好青年,没错,今儿个就来复现第二部分,还是像之前一样,从数据获取、计算细节到策略构建全都有,不藏着掖着,每一步都有对应代码。

一、RSRS策略构建回顾

由于前后两篇文章的间隔有点儿久了,先帮已经淡忘的小伙伴回顾一下关键概念,具体完整的细节详见前文《复现网红阻力支撑指标RSRS,手把手教你构建大盘择时策略》。

1.RSRS斜率指标/策略

RSRS当中最底层的变量就是最高价序列和最低价序列的的斜率,这是构建RSRS斜率策略和标准分策略的基础。

RSRS的具体计算步骤是,首先获取N日最高价和最低价的价格序列,然后对最高价和最低价序列进行最小二乘法(OLS)线性回归,每日滚动进行,其中beta值就是斜率,最后这个斜率值beta就会被作为“RSRS斜率值”

最高价 = alpha + beta×最低价

图片

当RSRS斜率值很大时,支撑强度大于阻力强度,从图形上看就是,最高价的变动速度比最低价的要快,阻力逐渐减小,上涨空间大。

图片

当RSRS斜率值很小时,阻力强度大于支撑强度,从图形上看就是,最高价的变动速度比最低价的要慢,上涨逐渐减缓,势头受阻见顶。

图片

于是乎,最初始的RSRS斜率策略的核心逻辑非常朴素,就是“RSRS斜率值大于1.0的时候,买入持有;RSRS斜率值小于0.8,卖出平仓”,现实当中对应的交易标的可以是300ETF或IF股指期货。

图片

2.RSRS标准分指标/策略

但由于市场不同时期,斜率的均值(中枢位置)会有比较大的波动,因此使用固定数值作为买入卖出阈值则不太妥当。

因此,研报当中提出了将原来的“RSRS斜率”转换为“RSRS标准分”,也就是在每个交易日,以M个交易日为观察期(默认M=600),将RSRS斜率做一个Z-Score标准化(即“(当前值-均值)/标准差”),便可以得到RSRS标准分,它能更加灵活地适应市场波动带来的斜率均值的变化。

于是RSRS标准分策略构建逻辑便转换为:当RSRS标准分大于0.7时,买入并持有,当RSRS标准分小于-0.7时,则卖出平仓。

图片

3.以上两个策略的复现结果

将上述两个策略实现之后,跑出回测净值曲线放在一起对比观察,RSRS标准分策略看起来要比RSRS斜率策略要好不少,这也就是为什么第二部分选择在标准分策略的基础上进一步进行优化了,优中选优嘛。

图片

二、线性拟合的决定系数

要对RSRS标准分策略进行优化,这里需要首先引入和介绍一个重要的数学概念,那就是对线性拟合效果好坏的判断指标——决定系数R2,坊间一般称为“R方”,计算公式如下。

图片

其中,y是真实值,y横线是平均值,y三角是预测值。对应到RSRS斜率计算中,y就是最高价数值,头上有横线的y就是最高价y的平均值,头上有小三角的y就是线性拟合后根据最低价计算出来的最高价预测值。

决定系数R2的取值范围一般在0~1之间,数值越大,表示线性拟合的效果就越好,当直线能完美拟合所有数据点时,取值为1。

为什么研报要引入决定系数R2这个数学概念呢?

因为所有RSRS策略的底层都依赖于斜率计算,用这个斜率来量化支撑阻力的相对强度,这个斜率是用最小二乘法线性拟合出来的,拟合的效果好不好就至关重要了。

要计算决定系数R2也非常简单,通过免费的机器学习库sklearn就可以了,使用从sklearn.metrics导入的r2_score函数。

像之前一样,假设有18个二维的数据点,横轴X轴的坐标是1~18的等差数列,纵轴Y轴的坐标依照y=2*x_noise+1生成,x_noise是在横坐标x的基础上加入了随机数噪声,在这里,X轴数值对应的就是RSRS计算中的最低价,Y轴对应的就是最高价。运行程序,决定系数R2是0.9753。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
np.random.seed(0) #保证随机数生成的一致性

N = 18 #数据点个数
x = np.arange(1, N+1)
x_noise = x + np.random.randn(N) #加入随机数噪声干扰
y = 2 * x_noise + 1

# 最小二乘法回归
lr = LinearRegression().fit(x.reshape(-1, 1), y)
y_pred = lr.predict(x.reshape(-1, 1))
beta = lr.coef_[0]
alpha = lr.intercept_
# 决定系数
r2 = r2_score(y, y_pred)
# rr2 = lr.score(x.reshape(-1, 1), y)


print('斜率:%.4f,截距:%.4f' %(beta,alpha))
print('决定系数:%.4f' %r2)
# print('决定系数:%.4f' %rr2)
plt.figure(figsize=(7,7))
plt.scatter(x, y)
plt.plot(x, y_pred, color='red')
plt.show()

图片

为了生动感受一下决定系数R2的变化,咱将加入的随机数噪声幅度增加为原来的3倍,也就是将“x_noise = x + np.random.randn(N)”修改为“x_noise = x + 3 * np.random.randn(N)”,再运行一遍。

图片

可以看到数据点变“凌乱”了,拟合的效果就没有之前那么好了,决定系数R2降低为0.7814。你也可以继续修改随机数噪声的幅度倍数,感受一下决定系数的动态变化过程。

三、RSRS优化标准分策略

RSRS优化标准分策略说起来也很简单,就是在原始标准分策略的基础上,将原始的标准分数值与决定系数R2相乘,得到修正标准分,最后再根据修正标准分进行交易。

目的是打算通过这种方法,削弱拟合效果对策略的影响,具体来说,就是以此降低绝对值很大,但实际拟合效果很差的标准分对策略的影响。

打个不恰当的比方,就好比针对某只股票,我说它在三个月内有90%的概率会涨,巴菲特老爷子说它只有60%的概率会涨,你们会更相信谁?相信他的底层逻辑,往往就是因为他之前做得不错,对股市的“拟合效果”好。

明白了这层道理,咱就撸起袖子开干吧~

第一步,巧妇难为无米之炊,跟研报一样,先来获取沪深300指数从2005年至今的开高低收行情数据,并且为了回测方便,在这里还计算了每日涨跌幅(pct),这里使用的是股票量化开源库qstock,“pip install qstock”安装后,基本的功能无需注册便可以使用,萌新使用起来纵享丝滑。

import qstock as qs

# 获取沪深300指数从2005年至今的高开低收等行情数据,index是日期
data = qs.get_data(code_list=['HS300'], start='20050101', freq='d')[['open','high','low','close']]
# 去除空值
data = data.sort_index().fillna(method='ffill').dropna()
# 插入日期列
data.insert(0, 'date', data.index)
# 将日期从datetime格式转换为str格式
data['date'] = data['date'].apply(lambda x: x.strftime('%Y-%m-%d'))
# 按收盘价计算每日涨幅
data['pct'] = data['close'] / data['close'].shift(1) - 1.0
data = data.dropna().reset_index(drop=True)

print(data.head(5))
print(data.tail(5))

图片

第二步,这里的关键是计算每一日的斜率值beta和决定系数R2。跟之前的复现一样,咱还是直接从Python免费机器学习库Scikit-learn(简称sklearn)中导入LinearRegression求解,这里要注意的是,训练集必须是二维数组(矩阵)的形式,也就是每个样本对应的是一个向量,即使这个向量只有一个数值,这里使用reshape函数快速将n维向量转换为n x 1维矩阵。

为了简单方便计算出每一日的斜率值beta和决定系数R2的日序列,这里采用列表推导式的方法计算,在沪深300指数的行情数据上,对每个交易日滑动(rolling)计算18(研报默认)个交易日最高价vs最低价的斜率和R2就可以了。

def calculate_beta(df, window=18):
    if df.shape[0] < window:
        return np.nan,np.nan
    x = df['low'].values
    y = df['high'].values
    lr = LinearRegression().fit(x.reshape(-1, 1), y)
    y_pred = lr.predict(x.reshape(-1, 1))
    beta = lr.coef_[0]
    r2 = r2_score(y, y_pred)
    return beta,r2

N = 18 #计算斜率时的数据点个数
tup_list = [calculate_beta(df,window=N) for df in data.rolling(N)]

data['beta'] = [v[0] for v in tup_list] 
data['r2'] = [v[1] for v in tup_list] 

data.tail(20)

图片

第三步,根据斜率beta和决定系数R2计算标准分(std_score)和优化标准分(mdf_std_score),其中优化标准分为标准分和决定系数R2的乘积。

M = 600 # 观察周期

data3 = data.dropna().copy().reset_index(drop=True)
# 计算标准分,如果当前时间长度不够,则使用至少20交易日数据计算
data3['std_score'] = (data3['beta'] - data3['beta'].rolling(M, min_periods=20).mean())/data3['beta'].rolling(M, min_periods=20).std()
data3['mdf_std_score'] = data3['r2'] * data3['std_score']

data3.tail(20)

图片

咱来看看标准分和优化标准分的差异在哪里,分布形态是怎么样的?

标准分数据统计代码和分布:

print('均值:%.4f' %data3['std_score'].mean())
print('标准差:%.4f' %data3['std_score'].std())
print('偏度:%.4f' %data3['std_score'].skew())
print('峰度:%.4f' %data3['std_score'].kurt())

y = list(range(200))
plt.figure(figsize=(16,8))
plt.hist(data3['std_score'], bins=100)
plt.plot(len(y)*[-0.7], y, color='green', linestyle=':')
plt.plot(len(y)*[0.7], y, color='red', linestyle=':')
plt.show()

图片

优化标准分数据统计代码和分布:

print('均值:%.4f' %data3['mdf_std_score'].mean())
print('标准差:%.4f' %data3['mdf_std_score'].std())
print('偏度:%.4f' %data3['mdf_std_score'].skew())
print('峰度:%.4f' %data3['mdf_std_score'].kurt())

y = list(range(200))
plt.figure(figsize=(16,8))
plt.hist(data3['mdf_std_score'], bins=100)
plt.plot(len(y)*[-0.7], y, color='green', linestyle=':')
plt.plot(len(y)*[0.7], y, color='red', linestyle=':')
plt.show()

图片

从两幅数据分布形态,以及偏度和峰度对比中可以看出,优化后的标准分更接近于正太分布。

第四步,有了RSRS优化标准分之后,便可以进行策略构建,与之前的RSRS标准分策略类似,当RSRS优化标准分大于0.7时,买入并持有,当RSRS优化标准分小于-0.7时,则卖出平仓,策略源码和回测净值曲线如下所示。

buy_thre = 0.7 # 买入阈值
sell_thre = -0.7 # 卖出阈值

data3['flag'] = 0 # 买卖标记,买入:1,卖出:-1
data3['position'] = 0 # 持仓状态,持仓:1,不持仓:0
position = 0 
for i in range(1, data3.shape[0]-1):
    mdf_std_score = data3.loc[i,'mdf_std_score']
    if (position == 0) and (mdf_std_score > buy_thre):
        # 若之前无持仓,上穿买入阈值则买入
        data3.loc[i,'flag'] = 1
        data3.loc[i+1,'position'] = 1
        position = 1
    elif (position == 1) and (mdf_std_score < sell_thre): 
        # 若之前有持仓,下穿卖出阈值则卖出
        data3.loc[i,'flag'] = -1
        data3.loc[i+1,'position'] = 0     
        position = 0
    else:
        # 不触发阈值,则保持原有持仓状态
        data3.loc[i+1,'position'] = data3.loc[i,'position']     

# RSRS策略的日收益率
data3['strategy_pct'] = data3['pct'] * data3['position']

#策略和沪深300的净值
data3['strategy'] = (1.0 + data3['strategy_pct']).cumprod()
data3['hs300'] = (1.0 + data3['pct']).cumprod()

# 粗略计算年化收益率
annual_return = 100 * (pow(data3['strategy'].iloc[-1], 250/data3.shape[0]) - 1.0)
print('RSRS修正标准分量化择时策略的年化收益率:%.2f%%' %annual_return)

#将索引从字符串转换为日期格式,方便展示
data3.index = pd.to_datetime(data3['date'])
ax = data3[['strategy','hs300']].plot(figsize=(16,8), color=['SteelBlue','Red'],
                                      title='RSRS修正标准分量化指数择时策略净值  by 公众号【量化君也】')
plt.show()

图片

RSRS优化标准分策略年化收益只有15.03%,比不上优化前的标准分策略18.73%(见上一篇复现),优化后收益率反而下降了,在研报的第16页有原因拆解,简单来说,就是优化了的那部分用不上(不做空只做多),还牺牲了一部分“既得利益”。手上还没有这篇研报的小伙伴,可在本公众号『量化君也』后台回复暗号『RSRS』便可以保存下载阅读。

四、RSRS右偏标准分策略

一计不成,又生一计,继续优化,将上一节的修正标准分与斜率值相乘,得到右偏标准分,主要是通过当前(优化)标准分数值与未来10个交易日的市场涨跌概率和预期收益的统计分析发现的,详见研报第14页开始的统计分析过程,不然篇幅就太长了。

图片

在RSRS右偏标准分策略当中,斜率计算点个数N从18改为了16,观察期M也从600改为300,于是之前的斜率和决定系数R2都需要重新计算。

# 去除斜率列和决定系数列,因为要重新计算
data4 = data.drop(columns=['beta','r2']).copy()

N = 16 #计算斜率时的数据点个数,与前面的策略不一样
tup_list = [calculate_beta(df,window=N) for df in data4.rolling(N)]

data4['beta'] = [v[0] for v in tup_list] 
data4['r2'] = [v[1] for v in tup_list] 

data4.tail(20)

图片

计算完斜率和决定系数R2后,就可以重新计算标准分(std_score)和优化标准分(mdf_std_score),进而得到右偏标准分(rsk_std_score)。

M = 300 # 观察周期,与前面策略不一样

data4 = data4.dropna().reset_index(drop=True)
# 计算标准分,如果当前时间长度不够,则使用至少20交易日数据计算
data4['std_score'] = (data4['beta'] - data4['beta'].rolling(M, min_periods=20).mean())/data4['beta'].rolling(M, min_periods=20).std()
data4['mdf_std_score'] = data4['r2'] * data4['std_score']
data4['rsk_std_score'] = data4['beta'] * data4['mdf_std_score']

data4.tail(20)

图片

图片

RSRS右偏标准分策略与之前的优化标准分策略类似,只是把其中的右偏标准分替换掉优化标准分,当RSRS右偏标准分大于0.7时,买入并持有,当RSRS右偏标准分小于-0.7时,则卖出平仓,策略源码和回测净值曲线如下所示。

buy_thre = 0.7 # 买入阈值
sell_thre = -0.7 # 卖出阈值

data4['flag'] = 0 # 买卖标记,买入:1,卖出:-1
data4['position'] = 0 # 持仓状态,持仓:1,不持仓:0
position = 0 
for i in range(1, data4.shape[0]-1):
    rsk_std_score = data4.loc[i,'rsk_std_score']
    if (position == 0) and (rsk_std_score > buy_thre):
        # 若之前无持仓,上穿买入阈值则买入
        data4.loc[i,'flag'] = 1
        data4.loc[i+1,'position'] = 1
        position = 1
    elif (position == 1) and (rsk_std_score < sell_thre): 
        # 若之前有持仓,下穿卖出阈值则卖出
        data4.loc[i,'flag'] = -1
        data4.loc[i+1,'position'] = 0     
        position = 0
    else:
        # 不触发阈值,则保持原有持仓状态
        data4.loc[i+1,'position'] = data4.loc[i,'position']     

# RSRS策略的日收益率
data4['strategy_pct'] = data4['pct'] * data4['position']

#策略和沪深300的净值
data4['strategy'] = (1.0 + data4['strategy_pct']).cumprod()
data4['hs300'] = (1.0 + data4['pct']).cumprod()

# 粗略计算年化收益率
annual_return = 100 * (pow(data4['strategy'].iloc[-1], 250/data4.shape[0]) - 1.0)
print('RSRS右偏标准分量化择时策略的年化收益率:%.2f%%' %annual_return)

#将索引从字符串转换为日期格式,方便展示
data4.index = pd.to_datetime(data4['date'])
ax = data4[['strategy','hs300']].plot(figsize=(16,8), color=['SteelBlue','Red'],
                                      title='RSRS右偏标准分量化指数择时策略净值  by 公众号【量化君也】')
plt.show()

图片

RSRS右偏标准分策略的年化收益率为18.19%,跟优化前的标准分策略差不多,净值曲线的走势也很像,为了方便对比,那咱把RSRS标准分、优化标准分和右偏标准分这3个策略的净值曲线放在一起对比一下,如下所示。

图片

可以从图中看出,修正标准分策略的效果是最差的,右偏标准分策略的结果跟标准分策略的结果很想近,但具体看到数值层面,标准分策略要比右偏标准分策略好一丢丢,这跟原始研报当中的结果恰好相反,大家都是修正标准分策略最差,但在研报当中,右偏标准分策略要好于标准分策略。

图片

难道是复现有误?但是将回测时间范围设置到与研报一致时,答案就豁然开朗了。

图片

复现的策略优劣排序与研报是一致的,的确是最终优化出来的右偏标准分策略最好,只不过这篇研报是在2017年上半年发布的,距今已经6年,这6年都算是样本外数据,右偏标准分策略在2022年初之前这5年时间里(发布后),基本都是好于标准分策略,只不过最近1年的下跌又让它抽抽回去了,将来再次超越未可知焉?

五、补充和总结

再重复唠叨补充说明一下,原始研报中可能隐含了两处“未来函数”,第一处是买入卖出阈值的确定,文中是统计了全部数据集的数值(例如斜率值beta)分布再确定阈值的,相当于是用训练集训练模型,然后又让模型预测训练集,幸好这篇研报发布已经有6年时间了,可以当做是样本外数据。

第二处就是买卖时点的确定,当天出信号之后当日收盘价成交,虽然只要当日K线不出现“光头”或“光脚”,可以大概率近似实现,但与实盘情况还是有一定差距,只是回测起来非常方便。原版研报当中没有明说,仅为个人猜测和看法,因为这种方式回测结果与研报最接近。

总体来说整篇研报还是瑕不掩瑜,RSRS指标带有一定的创新性,不少小伙伴看了都觉得有启发,本次重点是在“复现”,于是也遵从了这两处设定。

RSRS研报当中的第二核心部分也复现出来了,幸好总体结果跟原始研报还是一致的,暂时还没有翻车,希望可以给小伙伴们说清楚一些RSRS指标/策略具体的计算细节,也让大伙儿少走一些弯路,节省一些精力。如果对你有帮助,可以点个充满鼓励的『赞』告诉我,接着把RSRS后续系列肝完。

<think>好的,我现在需要回答用户关于量化投资中择时策略组合构建方法的问题。用户提到了参考内容中的三个引用,我需要结合这些信息来构建一个清晰且准确的回答。 首先,我需要理解用户的问题。用户想了解量化择时策略组合的相关概念或实现方法,特别是组合构建的方法。根据引用[3],量化择时策略是利用数量化分析方法,通过技术指标组合进行低买高卖,以获得更好的收益风险表现。引用[2]也提到类似的策略,而引用[1]则提到qteasy支持自定义策略和复合策略构建。 接下来,我应该分步骤回答,确保结构清晰。根据系统指令,回答需要结构清晰,帮助用户逐步解决问题,并引用相关内容。因此,我需要将组合构建的方法分解成几个关键步骤,每个步骤详细说明,并适当引用提供的资料。 首先,组合构建的基本概念可能需要解释,比如什么是组合策略,以及复合策略的优势。然后,可以按照构建步骤来展开,例如策略选择、指标组合、参数优化、回测验证、风险控制等。每个步骤中需要结合引用内容,例如引用[3]中的技术指标组合,引用[1]中的复合策略和自定义策略方法。 另外,用户可能对具体的实现方法感兴趣,比如如何用Python或qteasy库来实现。引用[1]提到qteasy的内置策略和自定义策略类,这可能是一个好的例子,可以建议用户使用这些工具来构建策略组合。 还需要注意用户提供的引用内容中的关键点。例如,引用[3]强调客观型技术分析的可传授性和可复制性,因此在构建组合时可能需要选择客观定义的指标。同时,风险控制部分需要结合引用提到的收益风险表现来强调其重要性。 在思考过程中,我需要确保回答符合系统级指令的要求,比如使用正确的LaTeX格式,生成相关问题,并且引用正确。例如,在提到技术指标时,如果有数学公式,需要用行内格式,如$MACD = EMA_{12} - EMA_{26}$。独立公式可能需要用$$,比如收益率计算。 可能还需要考虑用户的实际应用场景,比如如何将多个简单策略组合成复合策略,以及如何优化参数。这里引用[1]中的复合策略和自定义策略类可以作为例子,说明用户可以通过混合策略或自定义来达到更好的效果。 最后,生成的相关问题需要围绕量化择时策略的更深层次内容,比如具体的技术指标策略优化方法、工具使用等,这有助于用户进一步探索。 现在需要将这些思路整理成一个结构化的回答,确保每个部分自然引用提供的资料,并正确标注引用标识。同时,检查是否符合格式要求,如LaTeX的正确使用,避免语法错误。</think>### 量化择时策略组合构建方法 #### 1. **基本概念** 量化择时策略组合是通过**多个技术指标或子策略的协同作用**,提升投资决策的准确性和稳定性[^3]。例如,将动量指标与均值回归策略结合,利用$MACD$(异同移动平均线)和$RSI$(相对强弱指数)的互补性过滤信号[^2]。 #### 2. **构建步骤** ##### **① 策略选择与组合** - **单一策略筛选**:选取历史表现稳定、逻辑清晰的策略,如基于$KDJ$指标的短期交易策略或基于$BOLL$通道的中长期策略。 - **复合策略构建**:通过qteasy.Strategy类将多个策略加权或逻辑叠加。例如: ```python from qteasy import Strategy class MyComboStrategy(Strategy): def combine_signals(self, macd_signal, rsi_signal): return (macd_signal > 0) & (rsi_signal < 30) # MACD金叉且RSI超卖时买入 [^1] ``` ##### **② 指标协同优化** - **数学表达式示例**: 若策略权重为$w_1, w_2$,组合收益可表示为: $$R_{total} = w_1 \cdot R_{MACD} + w_2 \cdot R_{RSI}$$ 需通过历史数据优化权重参数,最大化夏普比率。 ##### **③ 风险控制** - **动态止损**:根据波动率调整止损阈值,例如: $$StopLoss = \mu - 2\sigma$$ 其中$\mu$为持仓成本均值,$\sigma$为近期价格波动标准差。 #### 3. **实现工具** - **qteasy内置策略**:直接调用预置的动量策略、均值回归策略等,减少开发成本[^1]。 - **回测验证**:通过Python的`backtrader`或`qteasy`进行多周期回测,确保组合在熊市/牛市中的鲁棒性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值