3、协整检验入门:协整的定义、Engle-Granger两步法、Johansen检验简介、Python中的协整检验实现
做统计套利的朋友,一定绕不开协整检验。说白了,协整就是帮我们找到那些「长期走在一起,短期会分开」的资产对。我刚开始做量化那会儿,以为相关性高就能套利,结果吃了不少亏。后来才明白,真正能赚钱的配对,靠的是协整,不是相关。
3.1 协整到底是个啥?
先讲个直觉。你想想看,一个人牵着一条狗散步。人往前走,狗可能跑到左边闻闻,右边嗅嗅,但绳子拽着,它终究会回到主人身边。协整就是这个道理——两个时间序列各自随机游走,但它们之间存在一个稳定的线性组合,这个组合是平稳的。
用数学话说:如果两个序列都是 I(1)(一阶单整),但存在一个系数 β,使得 yt - βxt 是平稳的 I(0),那它们就协整。这个 β 就是协整向量,而 yt - βxt 就是价差序列。
核心要点:协整 ≠ 相关。两个完全不相关的股票,可能协整;两个高度相关的股票,可能不协整。我见过有人把相关系数 0.95 的对子拿去做配对交易,结果价差越走越远,亏得怀疑人生。原因就是它们不协整。
3.2 Engle-Granger两步法
这是最经典的协整检验方法,1987年提出,简单粗暴。我个人习惯先用它快速筛选,毕竟效率高。
第一步:估计协整关系
用 OLS 回归:yt = α + βxt + εt。得到残差序列 εt,这个残差就是我们的「价差」。
第二步:检验残差的平稳性
对残差做 ADF 检验。如果残差是平稳的,说明 yt 和 xt 协整。
注意坑:我曾经用 Engle-Granger 检验 50 个股票对,发现 30 个都「协整」。后来仔细一看,是因为 ADF 检验的临界值没调整。EG 两步法的残差检验,不能用标准的 ADF 临界值,得用 MacKinnon 的专用临界值表。否则会过度拒绝原假设,误判为协整。
3.3 Johansen检验简介
EG 两步法有个硬伤——它只能检验一对一的协整关系。如果你手里有 5 只银行股,想知道它们之间有几个协整关系,EG 就抓瞎了。这时候得用 Johansen 检验。
Johansen 检验基于 VAR 模型,能同时检验多个协整向量。它给出两个统计量:迹统计量和最大特征值统计量。实际项目中,我一般两个都看,以迹统计量为主。
我的经验:做多资产统计套利时,Johansen 检验特别好用。比如我做过一个 4 只能源股的组合,Johansen 检验发现存在 2 个协整关系。这意味着我们可以构建 2 个独立的价差组合,分散风险。但要注意,Johansen 对滞后阶数很敏感,我习惯用 AIC 或 BIC 选滞后阶数。
3.4 Python中的协整检验实现
直接上代码。我常用的库是 statsmodels,它把 EG 和 Johansen 都封装好了。
3.4.1 Engle-Granger 两步法
import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller, coint
# 模拟两个协整序列
np.random.seed(42)
n = 500
x = np.cumsum(np.random.randn(n))
y = x + np.random.randn(n) * 0.5 # 价差是平稳的
# 方法1:直接用 coint 函数(推荐)
score, pvalue, crit_values = coint(y, x)
print(f'EG检验 p-value: {pvalue:.4f}')
if pvalue < 0.05:
print('两个序列协整')
else:
print('不协整')
# 方法2:手动实现两步法(方便看细节)
# 第一步:OLS回归
X = sm.add_constant(x)
model = sm.OLS(y, X).fit()
residuals = model.resid
# 第二步:对残差做ADF检验
adf_result = adfuller(residuals, autolag='AIC')
print(f'残差ADF检验 p-value: {adf_result[1]:.4f}')
注意:coint() 函数内部已经用了 MacKinnon 的临界值,所以 p-value 是准确的。手动做 ADF 检验时,如果直接用 adfuller 的默认临界值,结果会偏乐观。我建议直接用 coint(),省心。
3.4.2 Johansen 检验
from statsmodels.tsa.vector_ar.vecm import coint_johansen
# 准备数据:多列DataFrame
data = pd.DataFrame({
'asset1': y,
'asset2': x,
'asset3': x + np.random.randn(n) * 0.3
})
# Johansen检验
joh_result = coint_johansen(data, det_order=0, k_ar_diff=1)
print('迹统计量:', joh_result.lr1)
print('5%临界值:', joh_result.cvt[:, 1])
print('最大特征值统计量:', joh_result.lr2)
print('5%临界值:', joh_result.cvm[:, 1])
# 判断协整秩
rank = sum(joh_result.lr1 > joh_result.cvt[:, 1])
print(f'协整关系个数: {rank}')
这里 det_order=0 表示没有确定性趋势,k_ar_diff=1 是差分后的滞后阶数。实际使用时,我会先用 select_order() 选最优滞后阶数。
3.5 一张图看懂协整检验流程
下面这张 SVG 图,是我自己梳理的协整检验决策流程。每次做项目前,我都会瞄一眼,防止漏掉步骤。
3.6 实战中的几个坑
做协整检验这几年,我踩过的坑不少。挑几个典型的说说:
- 数据频率问题:日线数据做协整,和小时线数据做协整,结果可能完全不同。我建议先用日线筛选,再用小时线验证。别指望不同频率的结果一致。
- 样本期选择:用 3 年数据检验协整,和用 1 年数据,结论可能相反。我个人习惯用 2 年训练、1 年验证,滚动检验。
- 结构突变:2015 年股灾前后,很多原本协整的对子都崩了。如果检验区间包含明显的市场结构变化,建议分段检验。
- 多重比较问题:你检验 100 个对子,就算全是随机游走,按 5% 显著性水平,也会有 5 个被误判为协整。我一般用 Bonferroni 校正,或者先做行业筛选,减少无效检验。
一个小技巧:检验协整之前,先画个价差图看看。如果价差看起来有明显的趋势或周期性,大概率不协整。眼睛有时候比统计量更灵敏。我经常先目测筛选一遍,再跑检验,能省不少时间。
好了,协整检验的核心内容就这些。记住一句话:协整是统计套利的基石,但别迷信 p-value。多看图,多思考背后的经济逻辑,比单纯跑代码重要得多。
公众号:蓝海资料掘金营,微信deep3321