6. 协整检验实战:Engle-Granger两步法、Johansen检验的Python实现

协整检验,说白了就是帮我们判断两个或多个价格序列之间,是否存在长期稳定的线性关系。在基差交易里,这就是核心中的核心——没有协整关系,统计套利就是空中楼阁。

我个人习惯把协整检验分成两类来理解:一类是检验两个品种之间的协整关系,用Engle-Granger两步法;另一类是检验多个品种之间的协整关系,用Johansen检验。今天我们就来手撕这两种方法的Python实现。

6.1 为什么需要协整检验?

你想想看,我们做基差交易,本质上是赌价差会回归均值。但如果两个品种的价格走势根本就是各走各的,那价差就没有均值可回归了。协整检验就是帮我们确认:这两个品种是不是真的「绑在一起」的。

我在项目中遇到过这样的情况:螺纹钢和热卷看起来走势很像,但一跑协整检验,发现根本不协整。如果当时直接上套利策略,后果可想而知。

核心要点:协整 ≠ 相关。两个高度相关的序列,可能完全不协整。比如两个随机游走序列,相关系数可能很高,但它们的价差会发散,无法回归。

6.2 Engle-Granger两步法

这个方法名字听着唬人,其实逻辑很直白。就两步:

  1. 第一步:用OLS回归估计协整系数
  2. 第二步:对残差进行单位根检验(ADF检验)

如果残差是平稳的,那这两个序列就协整。就这么简单。

6.2.1 Python实现

import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller

def engle_granger_test(y1, y2):
    """
    Engle-Granger两步法协整检验
    y1, y2: 两个价格序列
    """
    # 第一步:OLS回归
    X = sm.add_constant(y2)
    model = sm.OLS(y1, X).fit()
    residuals = model.resid
    
    # 第二步:对残差做ADF检验
    adf_stat, p_value, _, _, critical_values, _ = adfuller(residuals, maxlag=1)
    
    return {
        'cointegrating_vector': [1, -model.params[1]],
        'intercept': model.params[0],
        'adf_statistic': adf_stat,
        'p_value': p_value,
        'critical_values': critical_values,
        'is_cointegrated': p_value < 0.05
    }

# 示例:检验螺纹钢和热卷的协整关系
# 假设我们有价格数据
np.random.seed(42)
n = 500
# 构造协整序列
error = np.random.randn(n)
y2 = np.cumsum(np.random.randn(n))  # 随机游走
y1 = 0.8 * y2 + error  # 协整关系

result = engle_granger_test(y1, y2)
print(f"ADF统计量: {result['adf_statistic']:.4f}")
print(f"p值: {result['p_value']:.4f}")
print(f"是否协整: {result['is_cointegrated']}")

避坑指南:我曾经在实盘数据上吃过亏——ADF检验的滞后阶数选择很关键。默认的AIC准则有时会选太多滞后,导致检验功效下降。我建议至少试3个不同的滞后阶数,看结果是否稳健。

6.3 Johansen检验

当我们要检验三个或更多品种之间的协整关系时,Engle-Granger两步法就不够用了。这时候需要上Johansen检验。

Johansen检验的核心思想是:通过向量误差修正模型(VECM),同时检验多个协整关系。它给出两个统计量:迹统计量和最大特征值统计量。

6.3.1 Python实现

from statsmodels.tsa.vector_ar.vecm import coint_johansen

def johansen_test(data, det_order=1, k_ar_diff=1):
    """
    Johansen协整检验
    data: 多列价格序列,DataFrame格式
    det_order: 确定性趋势项,0-无,1-常数项,2-趋势项
    k_ar_diff: 差分滞后阶数
    """
    result = coint_johansen(data, det_order, k_ar_diff)
    
    # 迹统计量
    trace_stat = result.trace_stat
    trace_crit = result.trace_stat_crit_vals
    
    # 最大特征值统计量
    max_eigen_stat = result.max_eigen_stat
    max_eigen_crit = result.max_eigen_stat_crit_vals
    
    # 协整向量
    coint_vecs = result.evec
    
    return {
        'trace_statistic': trace_stat,
        'trace_critical_values': trace_crit,
        'max_eigen_statistic': max_eigen_stat,
        'max_eigen_critical_values': max_eigen_crit,
        'cointegrating_vectors': coint_vecs,
        'rank': result.r
    }

# 示例:检验三个品种的协整关系
# 构造数据
np.random.seed(42)
n = 500
e1 = np.random.randn(n)
e2 = np.random.randn(n)
e3 = np.random.randn(n)

# 构造两个协整关系
y1 = e1
y2 = 0.5 * y1 + e2
y3 = 0.3 * y1 + 0.2 * y2 + e3

data = pd.DataFrame({
    'y1': np.cumsum(y1),
    'y2': np.cumsum(y2),
    'y3': np.cumsum(y3)
})

result = johansen_test(data)
print("迹统计量:", result['trace_statistic'])
print("迹统计量临界值(95%):", result['trace_critical_values'][:, 1])
print("协整秩:", result['rank'])

6.4 两种方法的对比

对比维度 Engle-Granger两步法 Johansen检验
适用场景 两个品种 多个品种(≥2)
协整关系数量 最多1个 可检测多个
计算复杂度 较高
小样本表现 偏差较大 相对稳健
Python实现 statsmodels + adfuller statsmodels的coint_johansen

重要提醒:Johansen检验对滞后阶数非常敏感。我建议用信息准则(AIC/BIC)选择滞后阶数,然后做敏感性分析。如果不同滞后阶数下结论不一致,那就要小心了——可能数据本身就不适合做协整分析。

6.5 实战中的注意事项

  • 数据频率要一致:别拿日线数据和小时数据混着做协整检验,那会出大问题。
  • 样本量要足够:我个人建议至少250个数据点(约1年交易日),太少了检验功效不够。
  • 结构性断点:如果样本期内发生了政策变化或市场结构变化,协整关系可能已经变了。我习惯用滚动窗口检验,看看协整关系是否稳定。
  • 不要过度拟合:协整检验通过不代表一定能赚钱。我见过太多人拿着历史数据跑出协整关系,实盘一跑就崩——因为样本外协整关系可能已经失效了。

6.6 知识体系图

协整检验知识体系 协整检验 Engle-Granger两步法 OLS回归 残差ADF检验 判断是否协整 Johansen检验 迹统计量 最大特征值 确定协整秩 核心目标:确认价差序列的平稳性 为统计套利提供数学基础

嗯,到这里协整检验的核心内容就讲完了。记住一点:协整检验只是工具,不是目的。我们真正要的是找到稳定可交易的价差关系。下次当你跑完协整检验看到p值小于0.05时,别急着高兴——先问问自己:这个关系在样本外还能不能站得住?