第九章:协整关系建模——多变量协整检验与均值回复策略
各位同学,今天我们来聊聊协整关系建模。说实话,这是整个统计套利体系里最核心的一环。你想想看,我们做期权波动率曲面套利,本质上就是在找那些「走散了还会回来」的价差关系。而协整,就是量化这种关系的数学工具。
9.1 为什么需要多变量协整检验?
单变量协整检验(比如Engle-Granger两步法)有个硬伤——它只能处理两个变量之间的关系。但在波动率曲面套利中,我们经常要同时处理多个期限、多个行权价的波动率序列。比如,同时看1个月、3个月、6个月的平值期权隐含波动率,它们之间可能存在某种长期均衡关系。
我个人习惯用Johansen检验来处理这种情况。为什么?因为它能一次性告诉你:
- 这些序列之间到底存在几个协整关系
- 每个协整关系的具体系数是什么
- 哪个组合的均值回复特性最好
核心要点:Johansen检验基于VAR模型,通过特征根迹检验和最大特征值检验来判断协整秩(即协整关系的个数)。
9.2 Johansen检验的数学原理
说白了,Johansen检验就是在解一个广义特征值问题。假设我们有k个时间序列,构建一个VAR(p)模型:
ΔY_t = ΠY_{t-1} + ΣΓ_iΔY_{t-i} + ε_t
其中Π矩阵的秩r就是协整关系的个数。如果r=0,说明没有协整关系;如果r=k,说明所有序列都是平稳的;如果0<r<k,说明存在r个协整关系。
检验统计量有两种:
| 检验方法 | 统计量 | 原假设 | 备择假设 |
|---|---|---|---|
| 迹检验 | λ_trace = -TΣln(1-λ_i) | r ≤ r0 | r > r0 |
| 最大特征值检验 | λ_max = -Tln(1-λ_{r0+1}) | r = r0 | r = r0+1 |
嗯,这里要注意:迹检验的检验功效通常比最大特征值检验高一些,但最大特征值检验在识别具体协整向量时更清晰。我一般两个都看,如果结果不一致,优先相信迹检验的结果。
9.3 实战:用Python做Johansen检验
我在项目中遇到过最坑的一件事:用日频数据做Johansen检验,结果发现协整关系时有时无。后来才发现,是滞后阶数选错了。这里给大家一个经验法则:
- 日频数据:滞后阶数选5-10
- 小时频数据:滞后阶数选12-24
- 分钟频数据:滞后阶数选50-100
来看代码实现:
import numpy as np
import pandas as pd
from statsmodels.tsa.vector_ar.vecm import coint_johansen
def johansen_test(data, det_order=0, k_ar_diff=5):
"""
Johansen协整检验
data: DataFrame,每列一个时间序列
det_order: 确定性趋势类型
k_ar_diff: 差分后的滞后阶数
"""
result = coint_johansen(data, det_order, k_ar_diff)
# 输出迹检验结果
print("=== 迹检验 ===")
for i in range(len(result.lr1)):
print(f"r<={i}: 统计量={result.lr1[i]:.4f}, 临界值(95%)={result.cvt[i, 1]:.4f}")
# 输出最大特征值检验结果
print("\n=== 最大特征值检验 ===")
for i in range(len(result.lr2)):
print(f"r={i}: 统计量={result.lr2[i]:.4f}, 临界值(95%)={result.cvm[i, 1]:.4f}")
# 输出协整向量
print("\n=== 协整向量(标准化后) ===")
print(result.evec[:, :result.r])
return result
# 示例:用三个波动率序列做检验
# 假设我们有1M、3M、6M的平值期权隐含波动率
data = pd.DataFrame({
'IV_1M': iv_1m_series,
'IV_3M': iv_3m_series,
'IV_6M': iv_6m_series
})
result = johansen_test(data, det_order=0, k_ar_diff=5)
个人经验:Johansen检验对滞后阶数很敏感。我建议先用AIC或BIC准则选一个最优滞后阶数,然后在这个阶数附近±2做敏感性测试。如果结果稳定,那就可以放心用了。
9.4 构建协整组合
找到协整关系后,下一步就是构建具体的交易组合。协整向量给出了每个资产的权重,比如:
# 假设协整向量为 [1, -0.8, -0.2]
# 意味着:IV_1M - 0.8*IV_3M - 0.2*IV_6M 是平稳的
weights = result.evec[:, 0] # 取第一个协整向量
spread = data.dot(weights) # 计算价差序列
这里有个细节:协整向量不是唯一的。你可以对向量做任意缩放,价差序列的平稳性不变。但为了交易方便,我通常会把第一个资产的系数标准化为1。
9.5 计算半衰期——均值回复速度
半衰期(Half-life)是衡量均值回复速度的关键指标。它告诉我们:价差偏离均衡后,平均需要多长时间才能回复一半。
计算方法很简单:对价差序列做一阶自回归:
Δspread_t = α + β * spread_{t-1} + ε_t
半衰期 = ln(2) / |β|
来看代码:
def calculate_half_life(spread):
"""
计算价差序列的半衰期
"""
spread_lag = spread.shift(1).dropna()
spread_diff = spread.diff().dropna()
# 对齐数据
y = spread_diff.values
x = spread_lag.values
# OLS回归
x = np.column_stack([np.ones(len(x)), x])
beta = np.linalg.lstsq(x, y, rcond=None)[0]
# 半衰期
half_life = -np.log(2) / beta[1]
return half_life
half_life = calculate_half_life(spread)
print(f"半衰期: {half_life:.2f} 个时间单位")
避坑指南:我曾经遇到过半衰期算出来是负数的情况。这通常意味着价差序列不是均值回复的,而是发散的。这时候千万别硬做套利,否则会亏得很惨。另外,半衰期太长(比如超过100个时间单位)的价差也不适合做统计套利,因为资金占用成本太高。
9.6 知识体系总览
下面这张图总结了协整关系建模的完整流程:
9.7 几个实用技巧
- 数据频率要匹配:做协整检验时,所有序列的数据频率必须一致。我见过有人把日频和小时频混在一起用,结果检验结果完全不可信。
- 小心结构性突变:如果样本期内发生了重大事件(比如金融危机、政策变化),协整关系可能会断裂。建议做滚动窗口检验,看看协整关系是否稳定。
- 半衰期的实际意义:半衰期在5-20个时间单位之间的价差最适合做统计套利。太短了交易成本太高,太长了资金占用成本太高。
我的习惯:每次构建完协整组合后,我都会做一遍回测,看看价差在历史上的表现。如果回测中价差经常突破3倍标准差还不回归,那这个组合可能有问题,需要重新审视协整关系是否真的存在。
好了,协整关系建模的核心内容就这些。记住,统计套利不是印钞机,它需要你不断监控、调整。但掌握了协整这个工具,你至少有了一个科学的框架来捕捉市场中的定价偏差。