4. 协整模型构建:Engle-Granger两步法、Johansen检验、误差修正模型
协整,说白了就是两个或多个不平稳的时间序列,它们的线性组合却变得平稳了。这在套利里意味着什么?意味着价差会围绕一个均值来回摆动——高了就跌回去,低了就涨回来。这就是我们做配对交易的数学基础。
我个人习惯把协整比作「拴在一根绳上的蚂蚱」。两个资产各自乱跑,但彼此之间总有个约束。一旦跑远了,绳子就会把它们拉回来。嗯,这个「拉回来」的力度,就是我们套利的利润来源。
4.1 为什么需要协整?
先问个问题:两个股票的价格都在随机游走,你能直接拿它们的价差做交易吗?
答案是不能。因为两个独立的随机游走,价差也是随机游走。你看到的「均值回归」很可能是幻觉。我在项目中遇到过这种情况——回测时曲线漂亮得不行,实盘一跑就崩了。后来才发现,那只是两个随机序列偶然的「假回归」。
协整检验就是帮我们识别:这个价差是真的会回归,还是只是随机游走的假象。
4.2 Engle-Granger两步法
这个方法很直观,我最早做套利时用的就是它。说白了就两步:
- 第一步:做回归——把两个价格序列跑一个OLS回归,得到协整系数
- 第二步:检验残差——对回归的残差做单位根检验(ADF检验),看它是否平稳
如果残差是平稳的,就说明两个序列存在协整关系。
核心逻辑:协整关系 = 长期均衡关系。回归得到的系数就是「对冲比率」,残差就是「价差」。
代码实现其实很简单:
import statsmodels.api as sm
import numpy as np
# 假设 y 和 x 是两个价格序列
def engle_granger(y, x):
# 第一步:OLS回归
x_with_const = sm.add_constant(x)
model = sm.OLS(y, x_with_const).fit()
hedge_ratio = model.params[1] # 协整系数
spread = model.resid # 价差
# 第二步:对残差做ADF检验
adf_result = sm.tsa.stattools.adfuller(spread, maxlag=1)
p_value = adf_result[1]
return hedge_ratio, spread, p_value
# 使用示例
# hedge_ratio, spread, p_value = engle_granger(price_a, price_b)
# if p_value < 0.05:
# print("存在协整关系,可以做配对交易")
避坑指南:我曾经犯过一个错误——直接用价格做回归,没考虑滞后阶数。结果ADF检验的p值总是偏小,导致很多假阳性。后来我习惯在ADF检验里加上合适的滞后阶数(比如用AIC准则选择),效果就好多了。
不过Engle-Granger有个明显的缺点:它只能处理两个变量之间的协整关系。而且第一步回归时,选择哪个变量作为因变量会影响结果。你想想看,如果我把y和x互换,得到的协整系数可能不一样。这就有点尴尬了。
4.3 Johansen检验
Johansen检验解决了上面那个问题。它可以同时检验多个变量之间的协整关系,而且不依赖变量的顺序。
我个人觉得Johansen检验更像一个「系统级」的方法。它不先做回归,而是直接看整个系统的特征根。如果特征根显著不为零,就说明存在协整关系。
它的核心思想是这样的:
- 把多个价格序列放在一起,构成一个向量自回归模型(VAR)
- 然后看这个VAR模型的系数矩阵的秩
- 秩为r,就说明存在r个独立的协整关系
代码实现:
from statsmodels.tsa.vector_ar.vecm import coint_johansen
def johansen_test(data, det_order=0, k_ar_diff=1):
"""
data: 多列价格序列,每列一个资产
det_order: 确定性趋势的阶数(0=无趋势,1=有常数项)
k_ar_diff: 差分后的滞后阶数
"""
result = coint_johansen(data, det_order, k_ar_diff)
# 迹统计量检验
trace_stat = result.lr1
trace_crit = result.cvt[:, 1] # 5%显著性水平
# 最大特征值检验
max_eigen_stat = result.lr2
max_eigen_crit = result.cvm[:, 1]
# 协整向量(即对冲比率)
coint_vectors = result.evec
return trace_stat, trace_crit, max_eigen_stat, max_eigen_crit, coint_vectors
# 使用示例
# data = np.column_stack([price_a, price_b, price_c])
# trace_stat, trace_crit, _, _, vectors = johansen_test(data)
# print(f"迹统计量: {trace_stat}, 临界值: {trace_crit}")
注意:Johansen检验对滞后阶数的选择很敏感。我建议用AIC或BIC准则来选择最优滞后阶数,而不是随便设一个。另外,样本量太小(比如少于100个交易日)时,检验结果不太可靠。
Johansen检验还有一个好处:它直接给出了所有可能的协整向量。比如三个资产之间可能存在两个协整关系,它都能找出来。这在构建多资产套利组合时特别有用。
4.4 误差修正模型(ECM)
好,现在我们已经找到了协整关系。但还有一个问题:当价差偏离均值时,它具体是怎么回归的?回归的速度有多快?
这就是误差修正模型要回答的。ECM把价格的变化分解成两部分:
- 短期波动:由过去的价格变化驱动
- 长期修正:由当前价差偏离均衡的程度驱动
说白了就是:如果价差偏离了均衡,下一期价格就会往均衡方向「拉」一下。这个「拉」的力度,就是误差修正项系数。
数学形式是这样的:
Δy_t = α * (y_{t-1} - β * x_{t-1}) + γ * Δx_t + ε_t
其中:
- Δy_t 是 y 在 t 时刻的变化量
- (y_{t-1} - β * x_{t-1}) 是上一期的价差(误差修正项)
- α 是修正速度(负值表示回归)
- γ 是短期影响系数
代码实现:
import pandas as pd
import statsmodels.api as sm
def estimate_ecm(y, x, hedge_ratio):
"""
y, x: 原始价格序列
hedge_ratio: 协整系数(来自EG或Johansen)
"""
# 计算价差(误差修正项)
spread = y - hedge_ratio * x
# 计算差分
dy = y.diff().dropna()
dx = x.diff().dropna()
spread_lag = spread.shift(1).dropna()
# 对齐数据
data = pd.concat([dy, dx, spread_lag], axis=1).dropna()
data.columns = ['dy', 'dx', 'spread_lag']
# 回归:Δy = α * spread_lag + γ * Δx + ε
X = data[['spread_lag', 'dx']]
X = sm.add_constant(X)
y_var = data['dy']
model = sm.OLS(y_var, X).fit()
alpha = model.params['spread_lag'] # 修正速度
gamma = model.params['dx'] # 短期影响
return alpha, gamma, model
# 使用示例
# alpha, gamma, model = estimate_ecm(price_a, price_b, 1.5)
# print(f"修正速度: {alpha:.4f}") # 应该为负值
关键解读:
- α 为负值且显著 → 存在均值回归,可以做套利
- α 的绝对值越大 → 回归速度越快,套利机会窗口越短
- α 不显著 → 即使协整检验通过了,回归速度也可能很慢,不适合短线套利
我在项目中遇到过这样的情况:协整检验通过了,但ECM的α系数只有-0.01。这意味着价差偏离后,每天只回归1%。如果价差偏离了2个标准差,需要200天才能完全回归——这种套利基本没法做,资金成本都覆盖不了。
4.5 三者的关系
我把这三者的关系总结一下:
| 方法 | 解决的问题 | 适用场景 | 输出 |
|---|---|---|---|
| Engle-Granger | 两个资产是否存在协整 | 简单配对交易 | 协整系数、价差 |
| Johansen | 多个资产是否存在协整 | 多资产套利组合 | 协整向量个数、具体向量 |
| 误差修正模型 | 价差如何回归均衡 | 交易策略参数设定 | 修正速度、短期影响 |
我个人习惯的流程是:先用Johansen检验看整体协整关系,再用EG方法快速验证,最后用ECM确定交易参数。三个方法配合使用,比单独用任何一个都靠谱。
一个小技巧:在做协整检验之前,先确认每个序列都是I(1)的(即一阶差分后平稳)。如果序列是I(2)的,协整检验的结果可能不可靠。我一般先用ADF检验确认每个序列的平稳性,再开始协整分析。
最后说一句:协整模型不是万能的。它假设价差围绕一个固定的均值波动,但现实中这个均值可能会漂移。所以每隔一段时间(比如每3个月)重新估计一次协整关系,是个好习惯。