克隆策略
In [69]:
import numpy as np
import pandas as pd
import statsmodels
import statsmodels.api as sm
from statsmodels.tsa.stattools import coint, adfuller
import matplotlib.pyplot as plt

平稳性数据

时间序列分析中通常假设是数据具有平稳性。 当数据生成过程的参数不随时间变化时,数据是平稳的。 例如,我们考虑两个序列(Series)A和B,序列A是从不变参数的过程生成的,系列B由随时间变化的参数生成。

In [70]:
# 该函数是产生服从正态分布的随机变量
def generate_datapoint(params):
    mu = params[0]
    sigma = params[1]
    return np.random.normal(mu, sigma)

序列A

In [71]:
# 设置参数和数据量的个数
params = (0, 1)
T = 100

A = pd.Series(index=range(T))
A.name = 'A'

for t in range(T):
    A[t] = generate_datapoint(params)
    
plt.plot(A)
plt.xlabel('Time')
plt.ylabel('Value')
plt.legend(['Series A']);

序列B

In [72]:
T = 100

B = pd.Series(index=range(T))
B.name = 'B'

for t in range(T):
    # 参数随时间变化
    # 特别地,正态分布的均值随时间变化
    params = (t * 0.1, 1)
    B[t] = generate_datapoint(params)

plt.plot(B)
plt.xlabel('Time')
plt.ylabel('Value')
plt.legend(['Series B']);

非平稳数据的危险性

许多统计实验深深地依赖于他们的假设,他们要求被测数据是平稳的。 另外,如果您在非平稳数据集上使用某些统计信息,您将获得没有意义的结果。 我们来看一个例子:

In [73]:
m = np.mean(B)

plt.plot(B)
plt.hlines(m, 0, len(B), linestyles='dashed', colors='r')
plt.xlabel('Time')
plt.ylabel('Value')
plt.legend(['Series B', 'Mean']);

计算的平均值将显示所有数据点的平均值,但对于未来状态的任何预测都不会有用。 与任何特定时间相比,平均值是无意义的,因为它是不同时期的不同状态的集合。 这只是一个简单而清楚的例子,说明为什么非平稳性实际上可能会出现更多的微妙问题。

平稳性检验

我们使用统计方法检验数据平稳性(借助statsmodels中的adfuller单位根检验函数)

In [74]:
def check_for_stationarity(X, cutoff=0.01):
    # 原假设H0:单位根存在(非平稳)
    # 我们通过重要的p值,以确认自己这个序列是平稳的
    pvalue = adfuller(X)[1]
    if pvalue < cutoff:
        print('p-value = ' + str(pvalue) + ' The series ' + X.name +' is likely stationary.')
        return True
    else:
        print('p-value = ' + str(pvalue) + ' The series ' + X.name +' is likely non-stationary.')
        return False
In [75]:
check_for_stationarity(A);
check_for_stationarity(B);
p-value = 4.49381475504e-20 The series A is likely stationary.
p-value = 0.927553344971 The series B is likely non-stationary.

果然,序列A稳定,序列B不稳定。 我们接着尝试一个更微妙的例子。

In [76]:
# 设置数据量
T = 100

C = pd.Series(index=range(T))
C.name = 'C'

for t in range(T):
    # 正态分布的均值也随时间改变
    params = (np.sin(t), 1)
    C[t] = generate_datapoint(params)

plt.plot(C)
plt.xlabel('Time')
plt.ylabel('Value')
plt.legend(['Series C']);

平均值的周期运动将很难分离出随机噪声。 实际上,在噪声数据和样本量有限的情况下,很难确定一个序列是否是平稳的,以及漂移到底是是随机噪声还是趋势。 你可以多次通过下面这个函数来检验序列C的平稳性,不同的实验得到的检验结果也不一样,有时候平稳有时候非平稳。

In [77]:
check_for_stationarity(C);
p-value = 2.47864416964e-07 The series C is likely stationary.

单个序列平稳性检验

In [78]:
symbol_list = ['000002.SZA'] # 以万科A数据举例
prices = D.history_data(symbol_list, fields=['close']
                               , start_date='2014-01-01', end_date='2014-10-01')[['date','close']].set_index('date')
prices = prices['close']
prices.name = symbol_list[0]
In [79]:
check_for_stationarity(prices);
p-value = 0.836173033163 The series 000002.SZA is likely non-stationary.

检验得出万科A的股价数据并不符合平稳性特征,我们看下走势图

In [81]:
plt.plot(prices.index, prices.values)
plt.ylabel('Price')
plt.legend([prices.name]);

现在我们来看看这个序列的一阶差分,然后检查其平稳性。

In [82]:
X1 = prices.diff()[1:]
X1.name = prices.name + ' Additive Returns'
check_for_stationarity(X1)
plt.plot(X1.index, X1.values)
plt.ylabel('Additive Returns')
plt.legend([X1.name]);
p-value = 2.83752930846e-25 The series 000002.SZA Additive Returns is likely stationary.

通过价格这个序列,我们来检查每日收益率这个序列的平稳性。

In [83]:
X1 = prices.pct_change()[1:]
X1.name = prices.name + ' Daily Returns'
check_for_stationarity(X1)
plt.plot(X1.index, X1.values,)
plt.ylabel('Daily Returns')
plt.legend([X1.name]);
p-value = 1.05773457588e-25 The series 000002.SZA Daily Returns is likely stationary.

两只股票价格协整性检验

获取中信银行和交通银行的股票数据

In [84]:
symbol_list = ['601998.SHA', '601328.SHA']
prices = D.history_data(symbol_list, fields=['close']
                               , start_date='2012-01-01', end_date='2017-01-01')

prices_df=pd.pivot_table(prices, values='close', index=['date'], columns=['instrument'])
 
X1 = prices_df[symbol_list[0]]
X2 = prices_df[symbol_list[1]]

绘制两只股票股价实际走势图

In [85]:
plt.plot(X1.index, X1.values)
plt.plot(X1.index, X2.values)
plt.xlabel('Time')
plt.ylabel('Series Value')
plt.legend([X1.name, X2.name]);

通过线性回归挖掘两只股票股价之间的关系

In [86]:
X1 = sm.add_constant(X1)
results = sm.OLS(X2, X1).fit()

# Get rid of the constant column
X1 = X1[symbol_list[0]]

results.params
Out[86]:
const         1.221847
601998.SHA    0.941158
dtype: float64

可以发现两股票之间价格关系为: $601328.SHA = 0.94 * 601998.SHA + 1.22$

通过回归系数挖掘其长期稳定的关系

In [87]:
b = results.params[symbol_list[0]]
Z = X2 - b * X1
Z.name = 'Z'

plt.plot(Z.index, Z.values)
plt.xlabel('Time')
plt.ylabel('Series Value')
plt.legend([Z.name]);

check_for_stationarity(Z);
p-value = 0.00240769676666 The series Z is likely stationary.

可以看到,在我们看过的时间框架内,结果Z应该是平稳的。 这使得我们接受两个资产在同一时间框架内协整的假设。

此外,幸运地是对两个序列协整性的检验已经有现成的函数,通过stattools包的coint函数可直接检验协整性。

In [88]:
from statsmodels.tsa.stattools import coint
coint(X1, X2)
Out[88]:
(-3.9175752861575646,
 0.0093791668399287216,
 array([-3.90549156, -3.34117182, -3.0479483 ]))