7个月前我发布了【均值回归】基于zscore的均值回归策略(胜率100%)一文,
我将zscore思想用于单支股票,思想是股价和其均线值的差价在一定范围内波动,并计算其zscore值,获取买卖信号。
在本策略中,我将zscore思想还本复原,用于配对交易,利用两支相关性强的股票价格差价在一定范围内波动的思想,进行交易。具体做法如下:
1、计算沪深300指数成分股近10年股价相关系数,根据相关系数进行配对股票选择
2、设定zscore上下限(即买卖信号点),zscore窗口大小
3、在每个交易日,计算zscore值
4、若触发上下限,则卖出超涨股,买入超跌股
经过对比调试,本策略最终选择了海康威视和格力电器两支股票进行配对交易,回测结果如下,从12年初到19年3月,大概7年多的时间,年化45.38%,夏普1.169,胜率88.2%(一共交易17次),最大回撤53.75%
本策略思想很简单,编写得也较为粗糙,仓位一直保持满仓状态,无止损,导致15年股灾期间出现较大回撤。
另外,在选股方面,相比于之前发布的单股票策略选股的主观性,本策略利用相关系数进行选股,相对更客观,但对于指数的选择(此处选择沪深300指数)可自行调整。
共勉
1、相关性分析¶
# 获取指数成分股历史股价index = '000300.XSHG'start = '2009-1-1'end = '2019-1-1'code_list = get_index_stocks(index)index_df = get_price(code_list, start, end, fields='close').closeindex_df.head()
/opt/conda/lib/python3.6/site-packages/jqresearch/api.py:86: FutureWarning: Panel is deprecated and will be removed in a future version. The recommended way to represent these types of 3-dimensional data are with a MultiIndex on a DataFrame, via the Panel.to_frame() method Alternatively, you can use the xarray package http://xarray.pydata.org/en/stable/. Pandas provides a `.to_xarray()` method to help automate this conversion. pre_factor_ref_date=_get_today())
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
000001.XSHE | 000002.XSHE | 000063.XSHE | 000069.XSHE | 000100.XSHE | 000157.XSHE | 000166.XSHE | 000333.XSHE | 000338.XSHE | 000402.XSHE | 000408.XSHE | 000413.XSHE | 000415.XSHE | 000423.XSHE | 000425.XSHE | 000503.XSHE | 000538.XSHE | 000553.XSHE | 000568.XSHE | 000625.XSHE | 000627.XSHE | 000630.XSHE | 000651.XSHE | 000661.XSHE | 000671.XSHE | 000703.XSHE | 000709.XSHE | 000725.XSHE | 000728.XSHE | 000768.XSHE | 000776.XSHE | 000783.XSHE | 000786.XSHE | 000792.XSHE | 000826.XSHE | 000839.XSHE | 000858.XSHE | 000876.XSHE | 000895.XSHE | 000898.XSHE | ... | 601633.XSHG | 601668.XSHG | 601669.XSHG | 601688.XSHG | 601727.XSHG | 601766.XSHG | 601788.XSHG | 601800.XSHG | 601808.XSHG | 601818.XSHG | 601828.XSHG | 601838.XSHG | 601857.XSHG | 601877.XSHG | 601878.XSHG | 601881.XSHG | 601888.XSHG | 601898.XSHG | 601899.XSHG | 601901.XSHG | 601919.XSHG | 601933.XSHG | 601939.XSHG | 601985.XSHG | 601988.XSHG | 601989.XSHG | 601991.XSHG | 601992.XSHG | 601997.XSHG | 601998.XSHG | 603156.XSHG | 603160.XSHG | 603259.XSHG | 603260.XSHG | 603288.XSHG | 603799.XSHG | 603833.XSHG | 603858.XSHG | 603986.XSHG | 603993.XSHG | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2009-01-05 | 3.23 | 5.18 | 9.22 | 3.13 | 1.19 | 2.60 | NaN | NaN | 1.64 | 4.80 | 4.02 | 1.39 | 1.61 | 12.13 | 2.47 | 3.67 | 16.36 | 5.39 | 13.16 | 1.67 | 1.67 | 1.91 | 2.92 | 7.98 | 0.37 | 0.93 | 3.37 | 2.27 | 6.44 | 5.76 | 4.23 | 3.10 | 2.03 | 31.91 | 3.68 | 2.30 | 11.36 | 2.52 | 7.68 | 6.71 | ... | NaN | NaN | NaN | NaN | 5.38 | 3.80 | NaN | NaN | 11.31 | NaN | NaN | NaN | 8.11 | NaN | NaN | NaN | NaN | 5.96 | 2.69 | NaN | 7.60 | NaN | 2.36 | NaN | 1.79 | NaN | 5.63 | NaN | NaN | 2.82 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2009-01-06 | 3.43 | 5.34 | 9.59 | 3.23 | 1.21 | 2.86 | NaN | NaN | 1.76 | 4.94 | 4.16 | 1.42 | 1.71 | 12.40 | 2.72 | 3.82 | 16.01 | 5.56 | 13.40 | 1.67 | 1.75 | 1.99 | 2.84 | 8.05 | 0.38 | 0.98 | 3.70 | 2.34 | 6.60 | 6.14 | 4.23 | 3.18 | 2.09 | 28.73 | 4.02 | 2.38 | 11.82 | 2.61 | 7.45 | 7.05 | ... | NaN | NaN | NaN | NaN | 5.92 | 3.82 | NaN | NaN | 11.82 | NaN | NaN | NaN | 8.28 | NaN | NaN | NaN | NaN | 6.33 | 2.79 | NaN | 8.01 | NaN | 2.41 | NaN | 1.82 | NaN | 5.70 | NaN | NaN | 2.88 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2009-01-07 | 3.32 | 5.31 | 9.20 | 3.32 | 1.20 | 2.83 | NaN | NaN | 1.75 | 4.88 | 4.22 | 1.45 | 1.74 | 13.00 | 2.78 | 3.82 | 16.12 | 5.53 | 13.25 | 1.67 | 1.87 | 2.05 | 2.81 | 8.29 | 0.38 | 0.98 | 3.63 | 2.33 | 6.52 | 6.25 | 4.23 | 3.14 | 2.09 | 25.85 | 3.94 | 2.36 | 11.68 | 2.68 | 7.34 | 6.95 | ... | NaN | NaN | NaN | NaN | 5.86 | 3.70 | NaN | NaN | 11.79 | NaN | NaN | NaN | 8.20 | NaN | NaN | NaN | NaN | 6.29 | 2.72 | NaN | 8.13 | NaN | 2.35 | NaN | 1.78 | NaN | 5.77 | NaN | NaN | 2.83 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2009-01-08 | 3.19 | 5.34 | 8.88 | 3.36 | 1.15 | 2.74 | NaN | NaN | 1.71 | 4.86 | 4.19 | 1.47 | 1.76 | 13.18 | 2.74 | 3.67 | 15.82 | 5.32 | 13.09 | 1.67 | 1.79 | 1.95 | 2.79 | 8.46 | 0.39 | 0.96 | 3.47 | 2.23 | 6.36 | 6.42 | 4.23 | 3.09 | 1.98 | 23.62 | 3.96 | 2.25 | 11.53 | 2.59 | 7.33 | 6.67 | ... | NaN | NaN | NaN | NaN | 5.54 | 3.58 | NaN | NaN | 11.54 | NaN | NaN | NaN | 7.95 | NaN | NaN | NaN | NaN | 6.15 | 2.61 | NaN | 7.74 | NaN | 2.26 | NaN | 1.75 | NaN | 5.50 | NaN | NaN | 2.74 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2009-01-09 | 3.28 | 5.33 | 8.96 | 3.29 | 1.18 | 2.85 | NaN | NaN | 1.76 | 4.90 | 4.21 | 1.47 | 1.76 | 13.19 | 2.79 | 3.76 | 15.61 | 5.43 | 13.28 | 1.67 | 1.83 | 2.03 | 2.85 | 9.26 | 0.39 | 1.01 | 3.54 | 2.27 | 6.52 | 6.45 | 4.23 | 3.15 | 2.05 | 24.16 | 4.13 | 2.48 | 11.79 | 2.67 | 7.55 | 6.77 | ... | NaN | NaN | NaN | NaN | 5.69 | 3.64 | NaN | NaN | 12.04 | NaN | NaN | NaN | 7.98 | NaN | NaN | NaN | NaN | 6.35 | 2.65 | NaN | 7.91 | NaN | 2.28 | NaN | 1.77 | NaN | 5.94 | NaN | NaN | 2.77 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
import pandas as pd# 计算每支股票与其余股票的相关系数,降序排列# 相关系数相等的两支股票即为配对股票corr_df = index_df.corr()corr_df[corr_df==1] = nancorr_df = pd.DataFrame(corr_df.max().sort_values(ascending=False).head(20), columns=['corr'])corr_df['name'] = [get_security_info(code).display_name for code in corr_df.index]corr_df
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
corr | name | |
---|---|---|
601398.XSHG | 0.992742 | 工商银行 |
601939.XSHG | 0.992742 | 建设银行 |
000858.XSHE | 0.988095 | 五粮液 |
000333.XSHE | 0.988095 | 美的集团 |
002415.XSHE | 0.986354 | 海康威视 |
000651.XSHE | 0.986354 | 格力电器 |
002508.XSHE | 0.984371 | 老板电器 |
002572.XSHE | 0.984371 | 索菲亚 |
601288.XSHG | 0.984116 | 农业银行 |
600036.XSHG | 0.982082 | 招商银行 |
601318.XSHG | 0.982082 | 中国平安 |
600893.XSHG | 0.981993 | 航发动力 |
600118.XSHG | 0.981993 | 中国卫星 |
600104.XSHG | 0.981922 | 上汽集团 |
600660.XSHG | 0.981922 | 福耀玻璃 |
601390.XSHG | 0.981689 | 中国中铁 |
601186.XSHG | 0.981689 | 中国铁建 |
000568.XSHE | 0.981355 | 泸州老窖 |
002142.XSHE | 0.981053 | 宁波银行 |
600741.XSHG | 0.977252 | 华域汽车 |
2、计算zscore¶
window = 60 # zscore窗口code1 = '002415.XSHE'code2 = '000651.XSHE'name1 = corr_df.loc[code1, 'name']name2 = corr_df.loc[code2, 'name']price_df = get_price([code1, code2], end_date=end, count=len(index_df)+window-1, fields='close').closeprice_df.columns = [name1, name2]price_df.dropna(inplace=True)sub = price_df[name1] - price_df[name2]ma = sub.rolling(window).mean()std = sub.rolling(window).std()price_df['zscore'] = round((sub - ma) / std, 4)price_df.dropna(inplace=True)price_df.head()
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
海康威视 | 格力电器 | zscore | |
---|---|---|---|
2010-08-24 | 3.85 | 5.66 | -1.2659 |
2010-08-25 | 3.81 | 5.52 | -0.9804 |
2010-08-26 | 3.90 | 5.54 | -0.7757 |
2010-08-27 | 3.91 | 5.54 | -0.7262 |
2010-08-30 | 4.03 | 5.65 | -0.6745 |
sample = price_df.head(800)# 正常显示负号plt.rcParams['axes.unicode_minus'] = Falseplt.figure(figsize=(20, 10))plt.plot(sample[name1], label=name1)plt.plot(sample[name2], label=name2)plt.legend(loc=1)plt.twinx()plt.plot(sample['zscore'], label='zscore', c='g')plt.axhline(2, c='r'); plt.axhline(-2, c='r')plt.legend(loc=2)plt.show()