4、协整检验与残差分析:Engle-Granger两步法、Johansen检验、残差序列的平稳性检验、残差自相关处理

协整检验,说白了就是判断两个或多个时间序列之间有没有「长期夫妻相」。我刚开始做统计套利那会儿,总觉得只要两个品种走势看着像,就能配对交易。结果呢?回测漂亮,实盘亏得我怀疑人生。后来才明白——没有协整关系的配对,就像没有感情的婚姻,迟早要崩。

这一节,我们就把协整检验和残差分析彻底讲透。你想想看,如果连两个序列是不是真的「绑在一起」都搞不清楚,后面的交易逻辑全是空中楼阁。

4.1 Engle-Granger两步法:最朴素的协整检验

Engle-Granger两步法,简称EG两步法。我个人习惯叫它「先回归、再检验」。思路非常直接:

  1. 第一步:用OLS回归估计协整关系。比如你拿A股和B股的价格做回归,得到残差序列。
  2. 第二步:对残差序列做单位根检验(ADF检验)。如果残差是平稳的,说明两者协整。

嗯,这里要注意一个坑:EG两步法只能处理单一协整关系。什么意思?就是两个序列之间最多只有一个协整向量。如果你手里有3个以上品种,EG法就不够用了。

核心要点:EG两步法的残差必须用ADF检验,但临界值不能用标准ADF表,得用MacKinnon专门给的协整临界值。我当年就是没注意这个,白白浪费了两周时间。

代码实现其实不复杂。我习惯用statsmodels库,直接调OLS和ADF检验:

import statsmodels.api as sm
import pandas as pd
import numpy as np

# 假设你有两个价格序列
price_a = pd.Series(...)
price_b = pd.Series(...)

# 第一步:OLS回归
X = sm.add_constant(price_b)
model = sm.OLS(price_a, X).fit()
residuals = model.resid

# 第二步:ADF检验残差
from statsmodels.tsa.stattools import adfuller
adf_result = adfuller(residuals, autolag='AIC')
print(f'ADF统计量: {adf_result[0]:.4f}')
print(f'p值: {adf_result[1]:.4f}')

# 注意:这里p值要跟MacKinnon协整临界值对比,不能直接用标准p值

我在项目中遇到过一个问题:如果两个序列的走势高度相关但不同步,EG两步法可能会误判。比如某段时间A股涨得快、B股涨得慢,残差看起来不平稳,但长期看它们确实协整。这时候我建议你多窗口滚动检验,别只看全样本。

4.2 Johansen检验:多品种协整的利器

Johansen检验,说白了就是EG两步法的「升级版」。它能同时检验多个协整关系,而且不需要事先指定哪个是因变量、哪个是自变量。我个人非常喜欢用这个方法,尤其是在做多品种配对时。

Johansen检验的核心思想是:通过VAR模型的特征根来判断协整关系的个数。它给出两个统计量:

  • 迹统计量(Trace Statistic):检验协整秩是否小于等于r
  • 最大特征值统计量(Max Eigenvalue Statistic):检验协整秩是否等于r

实际应用中,我一般两个统计量都看。如果它们结论一致,那就放心了。如果矛盾,我倾向于相信迹统计量——它在小样本下更稳健。

我的经验:Johansen检验对滞后阶数非常敏感。我建议用AIC或BIC准则选择滞后阶数,但别超过(样本量/10)这个上限。曾经有个学员用200个交易日的数据,选了20阶滞后,结果检验结果完全乱套。

代码示例:

from statsmodels.tsa.vector_ar.vecm import coint_johansen

# 假设你有三个品种的价格序列
data = pd.DataFrame({
    'asset1': price1,
    'asset2': price2,
    'asset3': price3
})

# Johansen检验
johansen_result = coint_johansen(data, det_order=0, k_ar_diff=1)
print('迹统计量:', johansen_result.lr1)
print('迹统计量临界值(95%):', johansen_result.cvt)
print('最大特征值统计量:', johansen_result.lr2)
print('最大特征值临界值(95%):', johansen_result.cvm)

# 判断协整秩
rank = sum(johansen_result.lr1 > johansen_result.cvt[:, 1])
print(f'协整秩: {rank}')

你想想看,如果三个品种之间有2个协整关系,那意味着什么?意味着你可以构建2个独立的配对组合,分散风险的同时还能提高资金利用率。这就是Johansen检验的价值所在。

4.3 残差序列的平稳性检验

残差平稳性检验,是协整检验的「最后一关」。说白了,就是确认残差是不是白噪声。如果残差不平稳,那前面的协整关系就是假的。

常用的方法有:

  • ADF检验:最常用,但要注意滞后阶数的选择
  • PP检验:对异方差更稳健,我一般用它做辅助验证
  • KPSS检验:原假设是平稳,跟ADF正好相反。两个结合用更靠谱

避坑指南:我曾经犯过一个低级错误——直接用原始价格序列的ADF临界值来判断残差。结果残差明明平稳,我却说它不平稳。后来才发现,残差的ADF临界值要比标准值更「宽松」一些。记住:用MacKinnon协整临界值!

我个人的习惯是:先用ADF检验,如果p值小于0.05,再用KPSS确认。如果ADF说平稳、KPSS也说平稳,那基本稳了。如果两者矛盾,我会检查一下数据是否有结构性断点。

4.4 残差自相关处理

残差自相关,是统计套利里最让人头疼的问题之一。为什么?因为如果残差有自相关,说明你的模型没有完全捕捉到两个序列的动态关系。说白了,就是模型「漏信息」了。

处理残差自相关,我常用的方法有:

  1. 增加滞后阶数:在EG两步法的第一步回归中,加入滞后项。比如用动态OLS(DOLS)代替普通OLS。
  2. 使用ARMA模型拟合残差:把残差当成一个时间序列,用ARMA模型提取自相关结构。然后交易信号基于ARMA的预测残差来生成。
  3. 误差修正模型(ECM):这是最正统的方法。ECM把短期波动和长期均衡分开建模,残差自相关通常能得到很好的控制。

代码示例——用ARMA处理残差自相关:

from statsmodels.tsa.arima.model import ARIMA

# 假设residuals是EG两步法得到的残差
# 拟合ARMA(1,1)模型
arma_model = ARIMA(residuals, order=(1, 0, 1)).fit()
print(arma_model.summary())

# 获取过滤后的残差(即白噪声部分)
filtered_residuals = arma_model.resid

# 对过滤后的残差做ADF检验
adf_filtered = adfuller(filtered_residuals)
print(f'过滤后残差的ADF p值: {adf_filtered[1]:.4f}')

我的建议:如果你发现残差自相关严重,别急着用复杂模型。先检查一下数据频率是否匹配。比如你用日线数据做回归,但两个品种的微观结构差异导致日内相关性很强,这时候用5分钟数据可能就解决了。我在做股指期货配对时遇到过类似情况,换成15分钟数据后,残差自相关直接消失了。

嗯,最后说一句:残差分析不是一次性工作。我每次回测都会定期检查残差的稳定性。因为市场结构会变,协整关系也会变。你想想看,如果两年前协整的品种,现在不协整了,你还按老策略交易,那不是送钱吗?

协整检验与残差分析 Engle-Granger两步法 OLS回归 → 残差ADF检验 Johansen检验 VAR模型 → 迹统计量/最大特征值 残差平稳性检验 ADF / PP / KPSS检验 残差自相关处理 增加滞后 / ARMA / ECM模型 MacKinnon临界值 滚动窗口检验 滞后阶数选择 AIC/BIC准则 ADF+KPSS组合 结构性断点检查 核心目标:确认长期均衡关系

这张图把整个协整检验与残差分析的流程串起来了。从EG两步法到Johansen检验,再到残差平稳性检验和自相关处理,每一步都有对应的处理技巧。我个人建议你把它打印出来贴在工位上,每次做配对交易前扫一眼,能避免很多低级错误。

最后提醒一句:协整检验不是一劳永逸的。市场在变,品种之间的关系也在变。我每三个月会重新跑一遍协整检验,确保之前的配对关系依然有效。嗯,这就是所谓的「动态协整管理」——说白了,就是别偷懒。