第九章:协整关系建模——多变量协整检验与均值回复策略

各位同学,今天我们来聊聊协整关系建模。说实话,这是整个统计套利体系里最核心的一环。你想想看,我们做期权波动率曲面套利,本质上就是在找那些「走散了还会回来」的价差关系。而协整,就是量化这种关系的数学工具。

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 知识体系总览

下面这张图总结了协整关系建模的完整流程:

协整关系建模流程 Step 1: 数据准备 多变量波动率序列 Step 2: 平稳性检验 ADF检验 / KPSS检验 Step 3: Johansen检验 确定协整秩r Step 4: 构建协整组合 提取协整向量 → 计算价差序列 Step 5: 计算半衰期 AR(1)回归 → 半衰期 = ln(2)/|β| Step 6: 交易决策 设置开仓阈值 → 执行套利 → 动态调整 注:每一步都需要进行稳健性检验,确保结果可靠

9.7 几个实用技巧

  1. 数据频率要匹配:做协整检验时,所有序列的数据频率必须一致。我见过有人把日频和小时频混在一起用,结果检验结果完全不可信。
  2. 小心结构性突变:如果样本期内发生了重大事件(比如金融危机、政策变化),协整关系可能会断裂。建议做滚动窗口检验,看看协整关系是否稳定。
  3. 半衰期的实际意义:半衰期在5-20个时间单位之间的价差最适合做统计套利。太短了交易成本太高,太长了资金占用成本太高。

我的习惯:每次构建完协整组合后,我都会做一遍回测,看看价差在历史上的表现。如果回测中价差经常突破3倍标准差还不回归,那这个组合可能有问题,需要重新审视协整关系是否真的存在。

好了,协整关系建模的核心内容就这些。记住,统计套利不是印钞机,它需要你不断监控、调整。但掌握了协整这个工具,你至少有了一个科学的框架来捕捉市场中的定价偏差。

公众号:蓝海资料掘金营,微信deep3321