4. 协整检验与参数:ADF检验原理、确定协整系数、滚动窗口协整检验、参数敏感性分析

做价差回归策略,最核心的一步是什么?

我个人觉得,就是协整检验。你想想看,如果两个品种压根儿没有长期稳定的关系,那所谓的回归,说白了就是瞎回归。我见过不少新手,上来就搞回归,结果回测曲线漂亮得不行,一上实盘就崩。为什么?因为协整关系早就变了,他们没发现。

这一章,咱们就把协整检验的底裤扒干净。从原理到代码,从静态到滚动窗口,再到参数敏感性分析,一步到位。

4.1 ADF检验原理:到底在测什么?

ADF检验,全称是Augmented Dickey-Fuller检验。名字挺唬人,其实逻辑很简单——它就是在问一个问题:这个时间序列,是不是随机游走的?

随机游走是什么?就是今天的价格 = 昨天的价格 + 一个随机噪声。这种序列没有均值回归的特性,你没法用它做配对交易。

ADF检验的原假设是:序列存在单位根(也就是非平稳)。备择假设是:序列平稳(没有单位根)。

检验结果会给出一个p值。如果p值小于某个阈值(比如0.05),我们就拒绝原假设,认为序列是平稳的。

关键点:我们做价差回归策略,检验的不是两个品种各自的价格序列,而是它们的价差序列。价差平稳,才能做回归。

嗯,这里要注意一个坑。ADF检验对滞后阶数的选择很敏感。选少了,自相关没消除;选多了,检验功效下降。我一般用AIC准则自动选滞后阶数,省心。

4.2 确定协整系数:怎么算价差?

有了检验方法,下一步就是确定协整系数。说白了,就是找到两个品种之间的线性关系,让价差序列最平稳。

常用的方法有两种:

  1. Engle-Granger两步法:先做OLS回归,得到系数,然后对残差做ADF检验。
  2. Johansen检验:可以同时检验多个品种,给出协整向量。

我个人习惯用Engle-Granger法,简单直接。代码实现也就几行:

import statsmodels.api as sm
import numpy as np

# 假设price1和price2是两个价格序列
X = sm.add_constant(price2)
model = sm.OLS(price1, X).fit()
hedge_ratio = model.params[1]  # 协整系数

# 计算价差
spread = price1 - hedge_ratio * price2

# 对价差做ADF检验
from statsmodels.tsa.stattools import adfuller
adf_result = adfuller(spread, autolag='AIC')
print(f'ADF统计量: {adf_result[0]:.4f}, p值: {adf_result[1]:.4f}')

我曾经遇到过一个案例,OLS回归出来的系数是1.2,但直觉告诉我这个值不太对。后来发现是因为两个品种的价格量级差太多,导致回归系数被扭曲。解决办法很简单——先做标准化,再回归。

小技巧:如果两个品种价格差异很大,建议先做Z-score标准化,再计算协整系数。这样系数更稳定,也更容易解释。

4.3 滚动窗口协整检验:别被静态数据骗了

静态协整检验有个致命问题——它假设关系是永恒不变的。但市场会变,对吧?

我见过最惨的案例:有人用过去3年的数据做协整检验,p值0.001,漂亮得很。结果策略上线后,协整关系突然断裂,价差一去不回头,直接亏了20%。

为什么会这样?因为市场结构变了。可能是政策变了,可能是主力换了,也可能是品种的基本面发生了根本性变化。

解决方案就是滚动窗口协整检验。我们用一个固定长度的窗口,在时间轴上滑动,每个窗口都做一次协整检验。这样就能实时监控协整关系的稳定性。

代码实现也不复杂:

def rolling_cointegration_test(price1, price2, window=252):
    results = []
    for i in range(window, len(price1)):
        p1 = price1[i-window:i]
        p2 = price2[i-window:i]
        
        # 计算协整系数
        X = sm.add_constant(p2)
        model = sm.OLS(p1, X).fit()
        spread = p1 - model.params[1] * p2
        
        # ADF检验
        adf = adfuller(spread, autolag='AIC')
        results.append({
            'date': price1.index[i],
            'hedge_ratio': model.params[1],
            'adf_stat': adf[0],
            'p_value': adf[1]
        })
    return pd.DataFrame(results)

窗口长度怎么选?我一般用252个交易日(一年)。太短了,检验功效不够;太长了,反应太慢。你可以根据自己的交易频率调整。

警告:滚动窗口检验的结果,不要只看p值。要同时关注协整系数的稳定性。如果系数忽大忽小,说明两个品种的关系本身就不稳定,这时候强行做配对交易风险很大。

4.4 参数敏感性分析:你的策略有多脆弱?

参数敏感性分析,说白了就是回答一个问题:如果参数变一点点,策略表现会崩吗?

我见过太多人,回测时参数调得完美无缺,结果实盘一跑就完蛋。为什么?因为参数过拟合了。你想想看,如果策略对参数极其敏感,稍微偏离最优值就亏钱,那这个策略你敢用吗?

敏感性分析通常关注以下几个参数:

参数 典型范围 影响
滚动窗口长度 126 ~ 504 窗口越短,反应越快,但噪声越多
ADF检验p值阈值 0.01 ~ 0.10 阈值越低,开仓越谨慎,但可能错过机会
协整系数更新频率 每日 / 每周 / 每月 更新越频繁,越能捕捉变化,但交易成本越高
开仓阈值(Z-score) 1.5 ~ 3.0 阈值越大,交易次数越少,单笔收益越高

怎么做敏感性分析?很简单:固定其他参数,只改变一个参数,观察策略的夏普比率、最大回撤、胜率等指标的变化。

如果某个参数在合理范围内波动时,策略表现变化不大,说明这个参数是鲁棒的。反之,如果稍微一动就崩,那就要小心了。

我的经验:真正好的策略,参数敏感性应该很低。你随便选一组参数,都能赚钱,那才是好策略。如果只有一组参数能赚钱,那大概率是过拟合。

4.5 本章知识体系

下面这张图,把协整检验与参数优化的核心逻辑串起来了。你可以把它当作一个检查清单,做策略时对照着来:

协整检验与参数优化 · 知识体系 协整检验核心概念 ADF检验原理 单位根检验 确定协整系数 OLS / Johansen 滚动窗口检验 动态监控 参数敏感性分析 鲁棒性测试 ADF检验细节 • 原假设:存在单位根 • p值 < 0.05 拒绝原假设 • 滞后阶数用AIC选择 协整系数计算 • Engle-Granger两步法 • 先回归,再检验残差 • 注意价格量级差异 滚动窗口设置 • 窗口长度:126~504 • 每日滚动检验 • 关注系数稳定性 输出:稳定的价差序列 + 鲁棒的策略参数 用于构建交易信号和仓位管理 核心原则:静态检验是基础,滚动检验是保障,敏感性分析是底线

这张图把整个流程串起来了。从ADF检验原理出发,到确定协整系数,再到滚动窗口动态监控,最后用参数敏感性分析收尾。每一步都有对应的坑和技巧。

我个人觉得,做量化交易最怕的就是「想当然」。你以为协整关系是稳定的,其实它早就变了。你以为参数是最优的,其实只是过拟合。只有把每一步都做扎实了,策略才能经得起市场的考验。

好了,这一章的内容就到这里。记住:协整检验不是一次性工作,而是持续监控的过程。市场在变,你的模型也得跟着变。


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