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 两步法,说白了就是:

  1. 第一步:用 OLS 回归估计协整系数
  2. 第二步:对残差做 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 两步法:先回归,再检验残差平稳性
  • 残差分析:均值、标准差、自相关,这些决定了你的策略参数
  • 滚动检验:协整关系会变,别指望一劳永逸

下一章我们会聊到如何基于协整关系构建完整的配对套利策略,包括开仓、平仓、止损的逻辑。到时候我会把今天讲的这些理论全部串起来,做成一个可回测的完整系统。

先消化一下今天的内容,动手跑跑代码。有问题随时交流。