Johansen 协整检验:用迹统计量与最大特征值锁定多变量长期关系
Engle-Granger 两步法只能处理两条序列、只能找一个协整关系,选谁当因变量还会改变结果。Johansen 检验把问题升级成特征值分解:对 VECM 的 Π 矩阵做约化秩回归,一次性回答『k 条序列里藏着几个协整关系』。纯 numpy 实现完整流程:三元系统(2 条共同随机趋势)迹统计量 176.1 >> 29.7 拒绝 r=0,r≤1 时 4.5 < 15.4 停下——精确识别协整秩 r=1;估出的协整向量 [1, −1.874, 1.247] vs 真值 [1, −1.875, 1.250],误差不到 0.3%。对照组:独立随机游走迹统计量 22.6 徘徊在临界值边缘,秩=2 系统两级统计量全部爆表。样本外 600 天三腿价差交易:z>2 开仓、|z|<0.5 平仓,净费后 Sharpe 3.15、最大回撤 6.6。诚实说明:合成数据协整永不破裂;迹检验对滞后阶数敏感(观测噪声的 MA 结构会让 nlag 选小时假拒绝);三腿组合的执行成本是两腿的 1.5 倍。拆穿『迹检验显著=能赚钱』『特征值大=价差好交易』等误区(中高阶)。
先说结论:Engle-Granger 两步法有三个天生残疾:只能处理两条序列的场景才自然、一次只能找出一个协整关系、选谁当回归因变量会改变检验结果。Johansen 检验把这三个问题一次解决——它对 VECM 里的长期关系矩阵 Π 做约化秩回归(特征值分解),迹统计量和最大特征值统计量从 r=0 开始逐级检验,第一个不能拒绝的地方就是协整秩。 三元合成系统(2 条共同随机趋势、真实协整秩 r=1)实测:迹统计量 176.1 远超 29.7 的 5% 临界值拒绝 r=0,检验 r≤1 时 4.5 < 15.4 停下,精确识别 r=1;第一特征向量给出的协整系数 [1, −1.874, 1.247],与真值 [1, −1.875, 1.250] 的误差不到 0.3%。用它构造的三腿价差样本外 600 天:净费后 Sharpe 3.15,40 次动作,最大回撤 6.6 个价差单位。但先泼冷水:迹检验对滞后阶数的选择比教科书说的敏感得多——我们的实验里观测噪声带 AR 结构时,nlag 选小一档,r≤1 的检验就会在临界值边缘反复横跳。

一、为什么 Engle-Granger 不够用#
上一篇 VECM 文章里我们用 Engle-Granger(EG)两步法处理了两条序列的协整。但把场景扩展到三条以上,EG 立刻露出三个结构性缺陷:
缺陷 1:协整关系可能不止一个。 k 条 I(1) 序列之间最多可以有 k−1 个线性无关的协整关系。三条国债收益率(2 年、5 年、10 年期)之间可能同时存在两个协整关系(两个利差都平稳)。EG 法每次只能估一个,你甚至不知道自己找到的是哪一个——它给你的是”某个”协整组合,不是”全部”。
缺陷 2:因变量的选择改变结果。 EG 第一步要跑 OLS:y1 对 y2、y3 回归。换成 y2 对 y1、y3 回归,估出的协整向量不同,残差 ADF 检验的结论也可能不同。三条序列有三种回归方向,可能出现”两种方向说协整、一种方向说不协整”的尴尬。
缺陷 3:两步法把第一步的估计误差传给第二步。 第一步 OLS 估出的 β 带误差,第二步对残差做 ADF 检验时这个误差不被考虑,导致检验的实际显著性水平偏离名义水平。
Johansen(1988, 1991)的方法从根上绕开这三个问题:不做任何”谁对谁回归”的选择,直接在 VECM 框架里对整个系统做最大似然估计。
二、Johansen 检验的核心:Π 矩阵的秩#
把 k 维价格向量 写成 VECM:
所有关于协整的信息都压缩在 k×k 矩阵 里:
- rank(Π) = 0:不存在协整,VECM 退化成差分 VAR;
- rank(Π) = k: 本身就是平稳的,根本不需要协整框架;
- 0 < rank(Π) = r < k:存在 r 个协整关系,,其中 β 的 r 列是协整向量(价差配方),α 是误差修正速度(每条序列被拉回均衡的力度)。
所以”检验协整”变成了”检验矩阵的秩”。Johansen 的做法是约化秩回归(reduced rank regression):
第一步:偏回归清场。 把 和 分别对短期动态项( 和常数)回归,取残差 和 。这一步把短期噪声清出去,只留下”今天的变化”和”昨天的水平”之间的净相关。
第二步:解广义特征值问题。 计算矩阵 的特征值 。每个特征值衡量一个方向上”水平项对差分项的解释力”——本质是 和 之间第 i 个典型相关系数的平方。协整方向上这个相关强(偏离均衡预示未来变化),非协整方向上趋近于零。
def johansen(Y, nlag=2):
"""Johansen 迹/最大特征值检验,纯 numpy"""
dY = np.diff(Y, axis=0)
Z0 = dY[nlag:] # ΔY_t
Z1 = Y[nlag:-1] # Y_{t-1}
parts = [dY[nlag-i-1:len(dY)-i-1] for i in range(nlag)]
n = Z0.shape[0]
ZK = np.column_stack(parts + [np.ones(n)]) # 短期动态 + 常数
# 偏回归取残差
R0 = Z0 - ZK @ np.linalg.lstsq(ZK, Z0, rcond=None)[0]
R1 = Z1 - ZK @ np.linalg.lstsq(ZK, Z1, rcond=None)[0]
S00 = R0.T @ R0 / n; S01 = R0.T @ R1 / n; S11 = R1.T @ R1 / n
M = np.linalg.solve(S11, S01.T) @ np.linalg.solve(S00, S01)
eigvals, eigvecs = np.linalg.eig(M)
order = np.argsort(eigvals.real)[::-1]
lam, V = eigvals.real[order], eigvecs.real[:, order]
k = Y.shape[1]
trace = [-n * np.sum(np.log(1 - lam[i:])) for i in range(k)]
maxeig = [-n * np.log(1 - lam[i]) for i in range(k)]
return lam, np.array(trace), np.array(maxeig), Vpython第三步:两套统计量逐级检验。
- 迹统计量 ,检验 "" 对 "";
- 最大特征值统计量 ,检验 "" 对 ""。
流程是顺序的:先检验 r=0,拒绝了再检验 r≤1,一直到第一个不能拒绝的假设——那就是协整秩的估计。临界值不是标准卡方分布,要查 Osterwald-Lenum 表(带常数、k−r 个公共趋势对应的分位数)。
三、合成实验:能不能精确抓出 r=1#
构造一个知道标准答案的三元系统。两条独立随机游走 作为共同随机趋势:
偏离项 是 AR(0.85) 平稳过程(不是白噪声——真实价差有持续性,半衰期约 4 天)。三条序列、两条公共趋势,由 Granger 表示定理,协整秩 r = 3 − 2 = 1:只有一个线性组合能同时消掉 和 。解 (A 是载荷矩阵)得真实协整向量 [1, −1.875, 1.250]。
T=2000 天,nlag=2,结果:
| 原假设 | 迹统计量 | 5% 临界值 | 最大特征值 | 5% 临界值 | 结论 |
|---|---|---|---|---|---|
| r = 0 | 176.1 | 29.68 | 171.6 | 20.97 | 拒绝 |
| r ≤ 1 | 4.49 | 15.41 | 4.41 | 14.07 | 不能拒绝 → r = 1 |
| r ≤ 2 | 0.07 | 3.76 | 0.07 | 3.76 | — |
两套统计量一致:第一级检验爆表(176 vs 29.7),第二级立刻掉到临界值的三分之一以下。特征值序列 [0.0823, 0.0022, 0.00004] 也直观印证——只有一个方向有实质性的均值回复信息。

协整向量的恢复精度:第一特征向量归一化后 [1, −1.874, 1.247],对比真值 [1, −1.875, 1.250],三个系数误差全部小于 0.3%。这比 EG 法的典型表现好一个量级——Johansen 的最大似然估计是超一致的(super-consistent),且不受”谁当因变量”影响。
两个对照组(防止检验器自己是坏的):
- 三条独立随机游走:迹统计量 [22.6, 12.3, 2.9],r=0 的检验 22.6 < 29.68,正确地不拒绝——没有协整就是没有;
- 秩=2 系统(三条序列共享一条趋势):迹统计量 [1135.0, 556.2, 0.04],前两级全部爆表、第三级归零,正确识别 r=2。
检验器在”无协整、一个协整、两个协整”三种世界里都给出正确答案,才能信它。
四、从检验到交易:三腿价差的样本外表现#
协整向量就是价差配方:。前 1400 天估计 β 和价差的均值方差,后 600 天完全冻结参数做交易:
z = (spread - mu_train) / sd_train # 只用训练段统计量
# z > 2 做空价差,z < -2 做多价差
# |z| < 0.5 平仓,|z| > 4 止损python样本外 600 天结果:净费后 Sharpe 3.15,40 次动作,最大回撤 6.6 个价差单位,累计 84.8 个单位。价差半衰期 3.8 天,意味着典型持仓一到两周。

样本外重估的协整向量 [1, −1.920, 1.258] 与训练段 [1, −1.874, 1.247] 基本一致——合成数据里协整关系稳定,参数漂移很小。这正是合成数据最不真实的地方,下面细说。
五、已知偏差与诚实说明#
偏差 1:合成协整永不破裂。 我们的 DGP 里 永远是 AR(0.85),均衡关系写死在数据生成过程里。真实市场的协整来自套利力量、产业链关系或政策锚定,这些力量会消失:汇改会杀死汇率锚、行业格局变化会杀死配对关系。Sharpe 3.15 是”协整假设永远成立”世界里的上限表现,不是实盘预期。
偏差 2:滞后阶数选择影响检验结论。 实验初期我们把偏离项设成白噪声、nlag 设成 1,结果 r≤1 的检验在 10 个随机种子里有 5 个假拒绝(迹统计量 15.4~16.4 vs 临界值 15.41,反复横跳)。原因:水平序列加观测噪声后,差分序列带 MA(1) 结构,短滞后的 VECM 吸收不掉,残余自相关污染特征值。实务建议:用信息准则(AIC/BIC)选 VAR 滞后阶再减 1 得到 VECM 滞后,且对 nlag±1 做敏感性检查——结论翻转就不要信。
偏差 3:三腿组合的交易成本高于两腿。 每次开平仓要动三条腿,成本大约是配对交易的 1.5 倍;且 β 系数非整数(−1.874、1.247),实盘要做手数取整,小资金下取整误差会显著扭曲价差敞口。回测里我们只按换手扣了固定成本,没建模取整误差。
偏差 4:临界值是渐近的。 Osterwald-Lenum 表假设大样本。T<200 时迹检验明显过度拒绝(假发现协整),有限样本修正(如 Cheung-Lai 调整)在小样本里是必需的,本文 T=2000 可以忽略。
六、结果解读与实战边界#
迹检验显著 ≠ 能赚钱。 迹统计量 176 只说明”存在统计上平稳的线性组合”,它不告诉你:价差的方差是否大到覆盖成本(半衰期 3.8 天、日波动足够,本例可以;若半衰期 60 天,同样的检验结论下根本没法交易);也不告诉你协整关系的经济来源是否可持续。检验回答”有没有”,交易还需要回答”值不值”和”稳不稳”。
特征值大小 ≠ 价差质量。 看起来很小,但它衡量的是”日频上偏离对次日变化的解释力”,不是 R²意义上的拟合优度。半衰期 4 天的价差对应的 λ 本来就在这个量级。用 λ 排序选价差可以,跨系统比较 λ 绝对值没有意义(它随样本量和滞后设定变化)。
收益归因:样本外 84.8 个单位的收益来自约 20 轮完整的开平仓循环,单轮平均贡献 4 个单位、接近 2 倍开仓阈值与平仓阈值之差(2σ−0.5σ=1.5σ,σ≈2.8)——这正是均值回归策略该有的样子:没有单笔暴利,靠重复收割偏离。若你的协整策略收益集中在一两笔,大概率不是均值回归在赚钱,是碰上了趋势。
什么时候用 Johansen 而不是 EG:三条以上序列(收益率曲线、跨市场同一资产、产业链上下游组合)必须用 Johansen;两条序列且你对”谁是锚”有先验时,EG 更简单直接。什么时候两个都别用:序列不是 I(1)(先做单位根检验)、样本跨越明显的结构断点(先分段)、或者你找协整的方式是在几百个组合里海选(多重检验会让 5% 显著性变成笑话——300 个组合里按定义就该有 15 个假阳性)。
下一篇预告:非线性 Granger 因果——当 x 通过 x² 驱动 y 时,线性 F 检验为什么会完全漏检,以及怎么用神经网络加置换检验把这条暗通道抓出来。