4、协整理论与检验:平稳性检验(ADF)、协整关系(Engle-Granger)、协整回归、残差分析
做套利策略,最核心的一个问题就是:两个资产到底有没有长期稳定的关系?
你想想看,如果两个价格序列各走各的,那还谈什么配对套利?所以,我们需要一套严谨的方法来验证。这就是协整理论要干的事。
我个人习惯把协整理解成「两个醉汉互相搀扶着走路」。单独看每个人,都走得东倒西歪(非平稳),但他们的相对位置却保持稳定(协整)。嗯,这个比喻很形象。
4.1 平稳性检验(ADF)
在做协整之前,第一步永远是先确认:这两个序列是不是同阶单整的?
说白了,就是看它们各自是不是「非平稳但差分后平稳」。最常用的工具就是 ADF 检验(Augmented Dickey-Fuller Test)。
核心逻辑:
- 原假设 H₀:序列存在单位根(非平稳)
- 备择假设 H₁:序列平稳
- 如果 p-value < 0.05,拒绝原假设,认为序列平稳
我在项目中遇到过不少新手直接拿原始价格做 ADF,发现 p 值很大,就以为不能做套利。其实这是对的——价格序列通常是非平稳的,我们要的是价差的平稳性。
来看代码实现:
import statsmodels.tsa.stattools as ts
# 对价格序列做 ADF 检验
def adf_test(series, name=''):
result = ts.adfuller(series.dropna(), autolag='AIC')
print(f'=== {name} ADF 检验结果 ===')
print(f'ADF Statistic: {result[0]:.6f}')
print(f'p-value: {result[1]:.6f}')
print(f'Critical Values:')
for key, value in result[4].items():
print(f' {key}: {value:.4f}')
if result[1] < 0.05:
print('✅ 拒绝原假设,序列平稳')
else:
print('❌ 无法拒绝原假设,序列非平稳')
return result
这里有个坑,我曾经踩过:ADF 检验对滞后阶数的选择很敏感。默认的 autolag='AIC' 通常够用,但如果你发现结果不稳定,可以手动指定 maxlag。
⚠️ 注意:不要对原始价格做 ADF 检验就期望它平稳。价格序列几乎都是非平稳的。我们要检验的是一阶差分后的序列是否平稳,或者直接检验价差的平稳性。
4.2 协整关系(Engle-Granger 两步法)
好,现在两个序列都是一阶单整的(I(1)),接下来就要看它们之间有没有协整关系。
Engle-Granger 两步法,说白了就是:
- 第一步:用 OLS 回归估计协整系数
- 第二步:对残差做 ADF 检验,看是否平稳
如果残差是平稳的,那就说明两个序列之间存在协整关系。就这么简单。
我个人习惯用 statsmodels 的 OLS 来做回归:
import statsmodels.api as sm
def engle_granger_step1(y, x):
"""第一步:OLS 回归"""
x_with_const = sm.add_constant(x)
model = sm.OLS(y, x_with_const).fit()
hedge_ratio = model.params.iloc[1] # 协整系数(对冲比率)
intercept = model.params.iloc[0] # 截距项
residuals = model.resid # 残差
print(f'协整系数(对冲比率): {hedge_ratio:.6f}')
print(f'截距项: {intercept:.6f}')
return hedge_ratio, intercept, residuals
def engle_granger_step2(residuals):
"""第二步:对残差做 ADF 检验"""
result = ts.adfuller(residuals.dropna(), autolag='AIC')
print(f'残差 ADF p-value: {result[1]:.6f}')
if result[1] < 0.05:
print('✅ 残差平稳,存在协整关系')
else:
print('❌ 残差非平稳,不存在协整关系')
return result
💡 经验之谈:Engle-Granger 两步法有个缺点——它假设协整关系是唯一的。如果存在多个协整向量,建议用 Johansen 检验。不过对于做配对套利,我们通常只关心一对资产,EG 法完全够用。
4.3 协整回归与残差分析
协整回归得到残差后,真正的重头戏才开始。残差分析,说白了就是看这个价差序列的统计特征。
我一般会做以下几件事:
- 残差均值:理论上应该接近 0,但实际中可能有偏移
- 残差标准差:用来设定开仓阈值(通常 ±1.5σ 或 ±2σ)
- 残差自相关:用 Ljung-Box 检验看是否有序列相关性
- 残差分布:看是否接近正态分布(其实套利策略不要求正态,但极端值要注意)
来看完整的残差分析代码:
import numpy as np
from scipy import stats
def residual_analysis(residuals):
"""残差分析"""
mean = np.mean(residuals)
std = np.std(residuals)
skew = stats.skew(residuals)
kurt = stats.kurtosis(residuals)
# Ljung-Box 检验
lb_result = sm.stats.acorr_ljungbox(residuals, lags=[10], return_df=True)
print('=== 残差统计分析 ===')
print(f'均值: {mean:.6f}')
print(f'标准差: {std:.6f}')
print(f'偏度: {skew:.6f}')
print(f'峰度: {kurt:.6f}')
print(f'Ljung-Box p-value: {lb_result["lb_pvalue"].values[0]:.6f}')
# 阈值建议
print(f'\n=== 阈值建议 ===')
print(f'±1.0σ 阈值: {mean:.4f} ± {1.0*std:.4f}')
print(f'±1.5σ 阈值: {mean:.4f} ± {1.5*std:.4f}')
print(f'±2.0σ 阈值: {mean:.4f} ± {2.0*std:.4f}')
return mean, std
关键点:残差分析的结果直接决定了你的套利策略参数。比如,如果残差标准差是 0.5,那么 ±1.5σ 就是 ±0.75。当价差偏离到这个程度时,就可以考虑开仓了。
4.4 实战中的注意事项
嗯,这里我要多说几句。协整检验在实际应用中,有几个容易翻车的地方:
| 常见问题 | 原因 | 解决方法 |
|---|---|---|
| 伪协整 | 数据量太少,或者存在结构性突变 | 至少用 1-2 年日线数据;做滚动协整检验 |
| 协整关系漂移 | 市场环境变化导致关系不稳定 | 定期重新估计协整系数(比如每季度一次) |
| 残差异方差 | 波动率聚集效应 | 考虑用 GARCH 模型建模残差方差 |
| 滞后阶数选择 | ADF 检验对滞后敏感 | 用 AIC/BIC 自动选择,或做敏感性分析 |
我曾经犯过一个错误:用 3 个月的日线数据做协整检验,发现 p 值很小,兴冲冲地开始跑策略。结果实盘一个月,价差直接突破 3σ 还不回归。后来复盘才发现,那 3 个月的数据刚好处于一个特殊的市场阶段,协整关系根本不稳健。
⚠️ 血的教训:协整检验不是做一次就完事的。我建议至少用滚动窗口的方式,每 60 个交易日重新做一次协整检验。如果发现协整关系消失了,立刻停止交易。
4.5 小结
协整理论是配对套利的基石。你想想看,没有这个理论基础,你凭什么相信两个资产的价格会回归?
总结一下今天的核心要点:
- ADF 检验:确认序列是否平稳,或者差分后是否平稳
- Engle-Granger 两步法:先回归,再检验残差平稳性
- 残差分析:均值、标准差、自相关,这些决定了你的策略参数
- 滚动检验:协整关系会变,别指望一劳永逸
下一章我们会聊到如何基于协整关系构建完整的配对套利策略,包括开仓、平仓、止损的逻辑。到时候我会把今天讲的这些理论全部串起来,做成一个可回测的完整系统。
先消化一下今天的内容,动手跑跑代码。有问题随时交流。