本人量化入门小白一枚,拜读了大神发的 【量化课堂】基于协整的搬砖策略 这一帖子,发现协整是一个可以作为入门的策略,于是查阅了若干英文文献,基于前辈的想法,重新做了一遍,做了一点点修改(主要是对于协整检验的完善),策略还很不完善,欢迎各位大神批评指正。
基于协整关系的配对交易策略
配对交易是统计套利的一种典型手段,通过选择一对具有相似的价格走势的股票,并通过对两只股票的多空设置,实现期望的profit。传统的配对交易是基于两只股票间的相关性以及其他的非参数决策规则。相关性主要用于描述短期的关系,在描述长期性的关系时有所欠缺。因此这里我们参考诸多关于配对交易的文献,使用协整来描述两只股票之间长期的关系。
在说到协整的概念之前,先要说一下时间序列的稳定性,对于一个时间序列{Xt},满足以下条件,就可以称之为弱平稳,记为I(0): 1、E(Xt)=μ,与时间不相关 2、V(Xt)=S**2,与时间不相关 3、协方差只与时间间隔p相关
一个时间序列{Yt}在称为稳定序列之前,要经过d次差分,则称{Yt}为d阶单整,记为I(d)。 假如两个非稳定时间序列{Xt}{Yt}都为1阶单整,且存在一个b,Yt=bXt+C C为常数, 使得{Yt-bXt}为I(0),则称{Xt}{Yt}协整。
本次研究中稳定性检验使用ADF方法,协整检验使用EG两步法。投资组合设置为市场中性,研究思路如下: 1、首先使用Python Statsmodels扩展包中的内置函数coint()来初步选择两只可能存在协整关系的股票,选择的股票区间采用 【量化课堂】基于协整的搬砖策略 这一研究中的12支股票。筛选的标准为根据返回的p值( MacKinnon's approximate p-value based on MacKinnon)。p值越小,代表协整关系越强。 2、画出两只股票的log price 走势图,直观的感受一下相关性。 3、对两只股票的log price组成的时间序列做ADF检验(平稳性检验),若不能拒绝原假设,则继续差分,再次进行ADF检验。 4、一阶差分之后发现可以拒绝原假设,则两个时间序列是一阶单整,可以进行协整检验。 5、采用OLS方法确定协整系数,并对拟合的残差进行单整检验(ADF方法) 6、基于回归系数构建平稳序列,Zt=Yt-b*Xt 7、定义zscore=(Zt-Zt.mean())/(Zt.std),zscore代表偏离平均值几个sigma。 8、依据zscore构建交易信号。
1、首先使用Python Statsmodels扩展包中的内置函数coint()来初步选择两只可能存在协整关系的股票,选择的股票区间采用
社区里面现有的帖子 : 【量化课堂】基于协整的搬砖策略
这一研究中的12支股票。筛选的标准为根据返回的p值( MacKinnon's approximate p-value based on MacKinnon)。p值越小,代表协整关系越强。
import numpy as np
import pandas as pd
import statsmodels.api as sm
import seaborn as sns
def find_cointergrated_pairs(dataframe):
n=dataframe.shape[1]
pvalue_matrix=np.ones((n,n))
keys=dataframe.keys()
pairs=[]
for i in range(n):
for j in range(i+1,n):
stk1=dataframe[keys[i]]
stk2=dataframe[keys[j]]
result=sm.tsa.stattools.coint(stk1,stk2)
pvalue=result[1]
pvalue_matrix[i, j] = pvalue
if pvalue<0.05:
pairs.append((keys[i],keys[j],pvalue))
return pvalue_matrix,pairs
stock_list = ["002142.XSHE", "600000.XSHG", "600015.XSHG", "600016.XSHG", "600036.XSHG", "601009.XSHG",
"601166.XSHG", "601169.XSHG", "601328.XSHG", "601398.XSHG", "601988.XSHG", "601998.XSHG"]
prices_df = get_price(stock_list, start_date="2014-01-01", end_date="2015-01-01", frequency="daily", fields=["close"])["close"]
log_price_df=np.log(prices_df)
pvalues, pairs = find_cointergrated_pairs(log_price_df)
sns.heatmap(1-pvalues, xticklabels=stock_list, yticklabels=stock_list, cmap='RdYlGn_r',mask = (pvalues == 1))
print pairs
[('600000.XSHG', '600015.XSHG', 0.03983508244814473), ('600000.XSHG', '601988.XSHG', 0.022753486859363257), ('601398.XSHG', '601988.XSHG', 0.0154252055760712)]
2、画出两只股票的log price 走势图,直观的感受一下相关性。
import matplotlib.pyplot as plt
stk1_price=log_price_df['600015.XSHG']
stk2_price=log_price_df['600000.XSHG']
plot(stk1_price)
plot(stk2_price)
plt.xlabel('Time')
plt.ylabel('ClosePrice')
plt.legend(['600015.XSHG','600000.XSHG'],loc='upper center')
<matplotlib.legend.Legend at 0x7ff502c60690>
3、对两只股票的log price组成的时间序列做ADF检验(平稳性检验),若不能拒绝原假设,则继续差分,再次进行ADF检验。
from statsmodels.tsa.stattools import adfuller
def testStationarity(data):
adftest=adfuller(data)
result=pd.Series(adftest[0:4],index=['adf','pvalue','lags used','number of pbservations'])
for key,value in adftest[4].items():
result['Critical value(%s)'%key]=value
return result
x=np.array(stk1_price)
y=np.array(stk2_price)
zz=pd.concat([testStationarity(x),testStationarity(y)],axis=1)
zz.columns=['600015','600000']
zz
600015 | 600000 | |
---|---|---|
adf | 2.263274 | 2.359863 |
pvalue | 0.998931 | 0.998990 |
lags used | 12.000000 | 12.000000 |
number of pbservations | 232.000000 | 232.000000 |
Critical value(5%) | -2.874080 | -2.874080 |
Critical value(1%) | -3.458855 | -3.458855 |
Critical value(10%) | -2.573453 | -2.573453 |
首先对两只股票收盘价组成的时间序列进行单整检验,第一步进行平稳性检验,若不能拒绝原假设,进行一次差分,再进行平稳性检验,看两者是否是同阶单整。ADF检验方法。
进行一次差分,然后再次检验平稳性。
diffx=stk1_price.diff(1)
diffx.dropna(inplace=True)
diffx=np.array(diffx)
diffy=stk2_price.diff(1)
diffy.dropna(inplace=True)
diffy=np.array(diffy)
tz=pd.concat([testStationarity(diffx),testStationarity(diffy)],axis=1)
tz.columns=['600015','600000']
tz
600015 | 600000 | |
---|---|---|
adf | -7.614129e+00 | -3.955579 |
pvalue | 2.214907e-11 | 0.001662 |
lags used | 4.000000e+00 | 11.000000 |
number of pbservations | 2.390000e+02 | 232.000000 |
Critical value(5%) | -2.873710e+00 | -2.874080 |
Critical value(1%) | -3.458011e+00 | -3.458855 |
Critical value(10%) | -2.573256e+00 | -2.573453 |
一阶差分以后,两个序列就已经平稳了,他们的单整阶数都是一,所以是单整同阶的,下面就可以做协整了:这里的原假设是两者不存在协整关系。 协整检验如下。
from statsmodels.tsa.stattools import coint
coint_result=coint(x,y)
pvalue=coint_result[1]
print 'pvalue=',pvalue
pvalue= 0.051264531578
5、采用OLS方法确定协整系数,并对拟合的残差进行单整检验(ADF方法)
pvalue在0.05附近,可以认为协整关系非常强。接下来,我们用这两支股票的价格来进行一次OLS线性回归,以此算出它们是以什么线性组合的系数构成平稳序列的。
x = stk1_price
y = stk2_price
X = sm.add_constant(x)
result = (sm.OLS(y,X)).fit()
print(result.summary())
a=result.resid
residus=[]
for k,v in a.iteritems():
residus.append((v))
b=np.array(residus)
bz=pd.concat([testStationarity(x),testStationarity(y),testStationarity(b)],axis=1)
bz.columns=['600015','600000','residus']
bz
OLS Regression Results ============================================================================== Dep. Variable: 600000.XSHG R-squared: 0.953 Model: OLS Adj. R-squared: 0.953 Method: Least Squares F-statistic: 4954. Date: Fri, 17 Feb 2017 Prob (F-statistic): 1.27e-163 Time: 20:07:31 Log-Likelihood: 514.80 No. Observations: 245 AIC: -1026. Df Residuals: 243 BIC: -1019. Df Model: 1 Covariance Type: nonrobust =============================================================================== coef std err t P>|t| [95.0% Conf. Int.] ------------------------------------------------------------------------------- const 0.0115 0.030 0.390 0.697 -0.047 0.070 600015.XSHG 1.1056 0.016 70.386 0.000 1.075 1.137 ============================================================================== Omnibus: 19.805 Durbin-Watson: 0.183 Prob(Omnibus): 0.000 Jarque-Bera (JB): 28.977 Skew: 0.526 Prob(JB): 5.10e-07 Kurtosis: 4.316 Cond. No. 37.5 ============================================================================== Warnings: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
600015 | 600000 | residus | |
---|---|---|---|
adf | 2.263274 | 2.359863 | -3.423230 |
pvalue | 0.998931 | 0.998990 | 0.010192 |
lags used | 12.000000 | 12.000000 | 0.000000 |
number of pbservations | 232.000000 | 232.000000 | 244.000000 |
Critical value(5%) | -2.874080 | -2.874080 | -2.873459 |
Critical value(1%) | -3.458855 | -3.458855 | -3.457438 |
Critical value(10%) | -2.573453 | -2.573453 | -2.573122 |
系数是1.1056.对残差进行平稳性检验,可以拒绝原假设。接下来画出拟合曲线和数据
fig, ax = plt.subplots(figsize=(12,7))
ax.plot(x, y, 'o', label="data")
ax.plot(x, result.fittedvalues, 'r', label="OLS")
ax.legend(loc='best')
<matplotlib.legend.Legend at 0x7ff50979a8d0>
6、基于回归系数构建平稳序列,Zt=Yt-b*Xt
设浦发银行的股价为Y,华夏银行的股价为X,则回归的拟合结果为:Y=1.1056X+0.0115,也就是说Y-1.1056X是平稳序列。 依照这个比例,我们画出它们价差的平稳序列。可以看出,虽然价差上下波动,但都会回归中间的均值。
plot(1.1056*stk1_price-stk2_price)
plt.axhline((1.1056*stk1_price-stk2_price).mean(), color="red", linestyle="--")
plt.xlabel("Time"); plt.ylabel("Stationary Series")
plt.legend(["Stationary Series", "Mean"])
<matplotlib.legend.Legend at 0x7fbe285b1a90>
7、定义zscore=(Zt-Zt.mean())/(Zt.std),zscore代表偏离平均值几个sigma。 8、依据zscore构建交易信号。
def zscore(series):
return (series - series.mean()) / np.std(series)
plot(zscore(1.1056*stk1_price-stk2_price))
plt.axhline(zscore(1.1056*stk1_price-stk2_price).mean(), color="black")
plt.axhline(1.0, color="red", linestyle="--")
plt.axhline(-1.0, color="green", linestyle="--")
plt.legend(["z-score", "mean", "+1", "-1"])
<matplotlib.legend.Legend at 0x7ff502b63350>
当两这个序列的 z-score 突破 1 或者 −1 时,说明两支股票的价差脱离了统计概念中的合理区间,如果它们的协整关系能够保持,那么它们的价差应该收敛。所以,在发现上述序列突破 1或 −1 时,应该按照比例买多一支股票并做空另外一支,从而赚取之后收敛的差价。
结合上图,当 z-score 突破上方红线时,说明浦发银行的价格相对于华夏银行高估,因此我们买入 1 份华夏银行并卖空 1.3689 份浦发银行(系数根据前面的线性回归得出),并当 z-score 回归于 0 时清仓获利。如果 z-score 突破下方绿线的话,反方向操作。
由于A股市场不可以做空,因此将所有的做空操作改为全部卖出。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程