配对交易原理:假设我们持有一组股票X和Y,它们之间存在某种经济
上的关联。比如,两家生产同样产品的公司或者是两家处于同一条供应链
上的公司,它是基于数学分析的策略的一个很好的范例。现在,让我们来
虚拟一个例子。
In [8]:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import statsmodels
from statsmodels.tsa.stattools import coint
# 设置生成随机数的种子
np.random.seed(107)
import matplotlib.pyplot as plt
概念解释:首先生成两个虚拟的股票
假设股票X的收益率服从正态分布,每天随机抽取一个值作为X的日收益率
。然后加总得到X的每天的价格。
In [9]:
X_returns = np.random.normal(0, 1, 100) # 生成日收益率
# 把他们加总,然后把价格提高到一个合理的范围
X = pd.Series(np.cumsum(X_returns), name='X') + 50
X.plot()
Out[9]:
现在我们来生成Y。记住Y与X有很强的经济关联,因此Y的价格与X的
价格变化路径与X的变化路径应该非常相似。因此我们可以用这样的方式
来生成Y:把X的价格提高一定值,然后再随机加上一个干扰,并且这个干
扰服从正态分布。
In [3]:
some_noise = np.random.normal(0, 1, 100)
Y = X + 5 + some_noise
Y.name = 'Y'
pd.concat([X, Y], axis=1).plot()
Out[3]:
定义:协整
现在我们已经生成了两个协整序列。粗略得来讲,协整是相关的一种
不同的形式。两个协整序列间的差在均值上下来回波动。对于成功的配对
交易,还要求这个差值在时间向后推延的时候收敛到均值。换一种方式来
想,就是说协整序列不一定会收敛到同样的值,但是它们都会收敛到某个
特定的值。
下面我们把他们之间的差画出来来看看它具体是什么形状。
In [4]:
(Y-X).plot() # 画出差值
plt.axhline((Y-X).mean(), color='red', linestyle='--') # 加上均
值
Out[4]:
检验协整关系
上述定义比较直观,我们怎样从统计的角度去检验两组序列是否协整
呢?在'statsmodels.tsa.stattools'中有一个非常方便的检定方法。我
们将会得到一个非常小的p-value,因为上述的X,Y序列已经最大限度的满
足协整关系了。
In [16]:
#计算这个协整检验的p-value,可以由此判断这两个时间序列在均值附近
是否稳定
score, pvalue, _ = coint(X,Y)
print pvalue
2.75767345363e-16
相关 vs. 协整
相关和协整,虽然在理论上是相似,但也不尽相同。为了说明这一点
,我们将给出相关但不协整的时间序列以及协整但不相关的序列。现在,
让我们先来检验一下刚才生成的序列的相关性。
In [6]:
X.corr(Y)
Out[6]:
0.94970906463859306
跟预见的一样,X,Y的相关性非常强。那么相关但不协整的序列是什
么样的呢?
相关但不协整
一个简单的例子就是两个逐渐发散的序列。
In [8]:
X_returns = np.random.normal(1, 1, 100)
Y_returns = np.random.normal(2, 1, 100)
X_diverging = pd.Series(np.cumsum(X_returns), name='X')
Y_diverging = pd.Series(np.cumsum(Y_returns), name='Y')
pd.concat([X_diverging, Y_diverging], axis=1).plot()
Out[8]:
In [10]:
print '相关系数 ' + str(X_diverging.corr(Y_diverging))
score, pvalue, _ = coint(X_diverging,Y_diverging)
print '协整检定的p-value:' + str(pvalue)
相关系数 0.991426733118
协整检定的p-value:0.52802926858
协整但不相关
一个简单的例子就是一个服从正态分布的序列和一个方波
In [11]:
Y2 = pd.Series(np.random.normal(0, 1, 1000), name='Y2') + 20
Y3 = Y2.copy()
In [12]:
# Y2 = Y2 + 10
Y3[0:100] = 30
Y3[100:200] = 10
Y3[200:300] = 30
Y3[300:400] = 10
Y3[400:500] = 30
Y3[500:600] = 10
Y3[600:700] = 30
Y3[700:800] = 10
Y3[800:900] = 30
Y3[900:1000] = 10
In [13]:
Y2.plot()
Y3.plot()
plt.ylim([0, 40])
Out[13]:(0, 40)
In [17]:
# 相关系数接近于0
print '相关系数:'+ str(Y2.corr(Y3))
score, pvalue, _ = coint(Y2,Y3)
print '协整检定的p-value: ' + str(pvalue)
相关系数:-0.0296754336659
协整检定的p-value: 0.0
可以确定,两个序列的相关系数非常的低,但是P-value显示它们满足很
强的协整关系。
定义: 对冲头寸
由于人们希望对冲市场行情不好的风险,因此通常在持有某种资产的
长头寸的同时还会持有另一些资产的短头寸。在股票价格上涨的时候,长
头寸部分盈利;股票价格下跌的时候短头寸部分盈利。因此,人们可以持
有部分股票的长头寸,部分股票的短头寸。这样的话,如果整个市场的行
情都不好,持有的资产中仍然有一部分是盈利的,甚至有可能盈利的部分
会超过亏损的部分。在只有两种股票的情形下,如果持有其中一个的长头
寸和另一个的短头寸,我们把这个叫对冲头寸。
一个小技巧:在股票价格趋近时实现
由于两只股票的价格可能相互接近也可能相互远离,因此两只股票的
价格差有大有小。配对交易的技巧就是保持在X,Y上保持一个对冲头寸。
当两只股票的价格都下跌时,我们既不盈利也不亏损,都上涨时也一样。
我们从两只股票价格回复到均值时的价格差中来盈利。为了实现这一点,
当X,Y的价格相差很大时,卖空Y,买入X。类似的,当价格相差很小时,
买入Y,卖空X。
寻找现实中存在协整关系的X,Y
最好的方法是先选一些可能存在协整关系的股票找起,然后进行统计检定
。如果直接对所有股票进行统计检定,可能会存在很大偏差。
下面提供了一种从一系列股票中寻找可能存在协整关系的股票组合的函数
。这个函数会返回协整关系检定结果矩阵,一个p-value矩阵,并且每对
组合的p-value都小于0.05。
下面提供了一种从一系列股票中寻找可能存在协整关系的股票组合的
函数。这个函数会返回协整关系检定结果矩阵,一个p-value矩阵,并且
每对组合的p-value都小于0.05。
In [11]:
def find_cointegrated_pairs(securities_panel):
n = len(securities_panel.minor_axis)
score_matrix = np.zeros((n, n))
pvalue_matrix = np.ones((n, n))
keys = securities_panel.keys
pairs = []
for i in range(n):
for j in range(i+1, n):
S1 = securities_panel.minor_xs
(securities_panel.minor_axis[i])
S2 = securities_panel.minor_xs
(securities_panel.minor_axis[j])
result = coint(S1, S2)
score = result[0]
pvalue = result[1]
score_matrix[i, j] = score
pvalue_matrix[i, j] = pvalue
if pvalue <>
pairs.append((securities_panel.minor_axis[i],
securities_panel.minor_axis[j]))
return score_matrix, pvalue_matrix, pairs
在汽车制造业中寻找存在协整关系的股票
下面我们来看几家制造汽车的公司,看他们的股票是否有满足协整关
系的。首先,确定一个我们要检验的股票列表。然后取出它们2014年的价
格数据。
以下是如何用get_price来得到将所有股票价格用一个pandas
DataFrame显示出来的例子。
In [3]:
securities_panel.loc['price'].head(5)
Out[3]:
用get_price得到特定股票价格的示例
In [5]:
securities_dataframe=securities_panel['price']
securities_dataframe['000338.XSHE'].head(5)
Out[5]:
2014-01-02 9.10
2014-01-03 8.94
2014-01-06 8.67
2014-01-07 8.60
2014-01-08 8.62
Name: 000338.XSHE, dtype: float64
现在我们来找哪些股票组合存在协整关系
In [13]:
# 用一个热能图来显示股票组合的协整检定的p-value。为避免重复,只
画出了对角线上方的热能图。对角线下方的都用1来代替
scores, pvalues, pairs = find_cointegrated_pairs
(securities_panel)
import seaborn
seaborn.heatmap(pvalues, xticklabels=symbol_list,
yticklabels=symbol_list, cmap='RdYlGn_r'
, mask = (pvalues >= 0.95)
)
print pairs
[(u'000581.XSHE', u'000625.XSHE'), (u'000581.XSHE',
u'000710.XSHE')]
看起来是('000581.XSHE', '000625.XSHE')和('000581.XSHE',
'000710.XSHE')这两对组合存在协整关系。现在我们以('000581.XSHE',
'000625.XSHE')为例进一步研究它们的价格关系以确保检定结果没有错误
。
In [14]:
S1 = securities_panel.loc['price']['000581.XSHE']
S2 = securities_panel.loc['price']['000625.XSHE']
In [16]:
score, pvalue, _ = coint(S1, S2)
pvalue
Out[16]:
0.028668177055257312
把两只股票的差价画出来
In [18]:
diff_series = S1 - S2
diff_series.plot()
plt.axhline(diff_series.mean(), color='black')
Out[18]:
差价本身在并没有很重要的统计意义。用z-score来将它正态化会更
能说明问题。
In [20]:
def zscore(series):
return (series - series.mean()) / np.std(series)
In [21]:
zscore(diff_series).plot()
plt.axhline(zscore(diff_series).mean(), color='black')
plt.axhline(1.0, color='red', linestyle='--')
plt.axhline(-1.0, color='green', linestyle='--')
Out[21]:
简易的策略:
在z-score低于-1.0的时候买入差价(即买入价格高的,卖出价格低
的)
在z-score低于高于1.0的时候卖空差价(即卖出价格高的,买入价格
低的)
在z-score接近0的时候退出所有头寸
由于我们一开始已经定义了差价为S1-S2,买入差价的含义就是买入
一份S1,卖空一份S2,卖出差价就是买入一份S2,卖空一份S1。
以上的策略仅仅只是为了更好地阐释配对交易的概念。在实际操作中
,我们还需要计算S1,S2分别应该持有多少才能最大化盈利。
根据持续更新的统计数据来交易
定义:移动平均
移动平均是指过去的n对数据点在给定时间内的平均值。因此,在序
列中,对于最前面的n个数据点无法定义移动平均。
In [22]:
# 获取2只股票的价格差
difference = S1 - S2
difference.name = 'diff'
# 获取价格差10天的移动平均值
diff_mavg10 = pd.rolling_mean(difference, window=10)
diff_mavg10.name = 'diff 10d mavg'
# 获取60天的移动平均值
diff_mavg60 = pd.rolling_mean(difference, window=60)
diff_mavg60.name = 'diff 60d mavg'
pd.concat([diff_mavg60, diff_mavg10], axis=1).plot()
# pd.concat([diff_mavg60, diff_mavg10, difference],
axis=1).plot()
Out[22]:
我们可以用移动平均来计算给定时间内股票差价的z-score。这可以
告诉我们差价相对有多大,帮助我们判断是否应该进入头寸。现在,让沃
恩来看看用移动平均计算的z-score。
In [23]:
# 计算每60天的标准差
std_60 = pd.rolling_std(difference, window=60)
std_60.name = 'std 60d'
# 计算每一天的z-score
zscore_60_10 = (diff_mavg10 - diff_mavg60)/std_60
zscore_60_10.name = 'z-score'
zscore_60_10.plot()
plt.axhline(0, color='black')
plt.axhline(1.0, color='red', linestyle='--')
plt.axhline(-1.0, color='green', linestyle='--')
Out[23]:
单看z-socre并不能说明什么问题,我们把z-score和股票价格一起画
出来看。
In [25]:
two_stocks = securities_panel.loc['price'][['000581.XSHE',
'000625.XSHE']]
# 把股票价格都除以10,以方便观察
pd.concat([two_stocks/10, zscore_60_10], axis=1).plot()
Out[25]:
联系客服