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由随时间变化的参数生成。
# 该函数是产生服从正态分布的随机变量
def generate_datapoint(params):
mu = params[0]
sigma = params[1]
return np.random.normal(mu, sigma)
序列A
# 设置参数和数据量的个数
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
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']);
许多统计实验深深地依赖于他们的假设,他们要求被测数据是平稳的。 另外,如果您在非平稳数据集上使用某些统计信息,您将获得没有意义的结果。 我们来看一个例子:
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单位根检验函数)
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
check_for_stationarity(A);
check_for_stationarity(B);
果然,序列A稳定,序列B不稳定。 我们接着尝试一个更微妙的例子。
# 设置数据量
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的平稳性,不同的实验得到的检验结果也不一样,有时候平稳有时候非平稳。
check_for_stationarity(C);
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]
check_for_stationarity(prices);
检验得出万科A的股价数据并不符合平稳性特征,我们看下走势图
plt.plot(prices.index, prices.values)
plt.ylabel('Price')
plt.legend([prices.name]);
现在我们来看看这个序列的一阶差分,然后检查其平稳性。
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]);
通过价格这个序列,我们来检查每日收益率这个序列的平稳性。
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]);
获取中信银行和交通银行的股票数据
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]]
绘制两只股票股价实际走势图
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]);
通过线性回归挖掘两只股票股价之间的关系
X1 = sm.add_constant(X1)
results = sm.OLS(X2, X1).fit()
# Get rid of the constant column
X1 = X1[symbol_list[0]]
results.params
可以发现两股票之间价格关系为: $601328.SHA = 0.94 * 601998.SHA + 1.22$
通过回归系数挖掘其长期稳定的关系
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);
可以看到,在我们看过的时间框架内,结果Z应该是平稳的。 这使得我们接受两个资产在同一时间框架内协整的假设。
此外,幸运地是对两个序列协整性的检验已经有现成的函数,通过stattools包的coint函数可直接检验协整性。
from statsmodels.tsa.stattools import coint
coint(X1, X2)