第1章:协整检验实战——从理论到代码

1.1 为什么协整检验这么重要?

做统计套利,说白了就是找一对「会分手但总会和好」的资产。我刚开始做量化那会儿,看到两个品种走势长得像就往上冲,结果亏得挺惨。后来才明白——相关性高不代表能套利,协整才是关键。

相关性告诉你两条线走得近,协整告诉你它们之间的价差会回归。你想想看,如果两个价格序列的线性组合是平稳的,那这个组合的价差就会围绕均值波动。这就是我们套利的理论基础。

核心概念:协整关系意味着两个或多个非平稳时间序列的线性组合是平稳的。简单说,就是它们之间存在长期均衡关系。

1.2 Engle-Granger两步法

这个方法很直观,我当年学的时候一下就记住了。分两步走:

第一步:做回归

假设我们要检验A和B是否协整。先跑一个OLS回归:

import statsmodels.api as sm
import numpy as np

# 假设price_a和price_b是价格序列
X = sm.add_constant(price_b)
model = sm.OLS(price_a, X).fit()
residuals = model.resid  # 这就是价差序列

第二步:检验残差是否平稳

用ADF检验看看残差有没有单位根:

from statsmodels.tsa.stattools import adfuller

adf_result = adfuller(residuals, maxlag=1)
print(f'ADF统计量: {adf_result[0]:.4f}')
print(f'p值: {adf_result[1]:.4f}')

if adf_result[1] < 0.05:
    print('残差平稳,存在协整关系')
else:
    print('残差不平稳,没有协整关系')

我的经验:ADF检验的滞后阶数别乱选。我习惯用AIC或BIC自动选,或者设个maxlag=5。选太多会降低检验功效,选太少又可能漏掉自相关。

1.3 Johansen检验——多品种协整的利器

Engle-Granger只能处理两个品种,而且不能告诉你到底有几对协整关系。Johansen检验就强大多了,它能同时检验多个品种,还能给出协整关系的个数。

我举个例子,假设我们有3只股票:

from statsmodels.tsa.vector_ar.vecm import coint_johansen

# data是3列价格数据
johansen_result = coint_johansen(data, det_order=0, k_ar_diff=1)

# 迹统计量检验
trace_stats = johansen_result.lr1
trace_crit = johansen_result.cvt

# 最大特征值检验
max_eigen_stats = johansen_result.lr2
max_eigen_crit = johansen_result.cvm

print('协整秩检验结果:')
for i in range(len(trace_stats)):
    print(f'秩r<={i}: 统计量={trace_stats[i]:.2f}, 临界值={trace_crit[i, 1]:.2f}')

输出结果怎么看?很简单:

  • 从r=0开始,如果统计量大于临界值,拒绝原假设
  • 继续检验r<=1,直到不能拒绝为止
  • 最后一个不能拒绝的r值,就是协整关系的个数

注意:Johansen检验对滞后阶数很敏感。我踩过坑——有一次选了滞后2阶结果说没有协整,改成滞后1阶就显著了。建议多试几个滞后阶数,看看结果是否稳健。

1.4 检验结果解读——别被p值骗了

很多人看到p值小于0.05就兴奋,觉得找到金矿了。其实没那么简单。

检验结果 可能原因 我的建议
p值显著 真的存在协整 检查经济逻辑是否合理
p值显著 伪回归(数据量太少) 至少用200个以上数据点
p值不显著 确实没有协整 换个时间窗口试试
p值不显著 结构突变(政策变化等) 分段检验,或者用结构突变检验

我曾经遇到过一个案例:两只银行股,2018年之前协整关系很稳定,2019年突然失效了。后来一查,原来是其中一家被收购了。你看,基本面变了,统计关系自然就断了。

1.5 常见陷阱——我踩过的坑

陷阱一:忽略数据频率

日线数据和分钟线数据的协整性质可能完全不同。我建议先用日线做初步筛选,再用分钟线做精细验证。

陷阱二:过度拟合

有些人为了找到协整对,试了几百个品种组合。你想想看,在5%的显著性水平下,每20次检验就有1次是假的。试100次,平均能找出5个「伪协整」对。

避坑指南:我一般会做样本外检验。把数据分成两段,前70%做检验,后30%做验证。如果样本外也显著,那才敢用。

陷阱三:忽略交易成本

协整检验通过只是第一步。别忘了算上手续费、滑点、冲击成本。有些价差看起来能套利,扣掉成本就亏了。

1.6 实战代码——完整的协整检验流程

下面是我常用的一个函数,把Engle-Granger和Johansen都封装好了:

def cointegration_test(price_df, method='eg'):
    """
    协整检验函数
    method: 'eg' 或 'johansen'
    """
    if method == 'eg':
        # Engle-Granger
        from statsmodels.tsa.stattools import coint
        score, pvalue, _ = coint(price_df.iloc[:, 0], 
                                 price_df.iloc[:, 1])
        return {'statistic': score, 'pvalue': pvalue}
    
    elif method == 'johansen':
        # Johansen
        result = coint_johansen(price_df.values, 0, 1)
        return {
            'trace_stat': result.lr1,
            'trace_crit': result.cvt,
            'max_eigen_stat': result.lr2,
            'max_eigen_crit': result.cvm
        }

# 使用示例
# result = cointegration_test(price_data, method='eg')
# if result['pvalue'] < 0.05:
#     print('找到协整对!')

小技巧:我习惯把检验结果可视化一下。画个价差图,标上均值线和±2倍标准差线。一眼就能看出价差是否真的会回归。

1.7 本章知识体系

下面这张图总结了协整检验的核心逻辑:

协整检验知识体系 价格序列输入 Engle-Granger两步法 Johansen检验 OLS回归 ADF检验残差 迹统计量 最大特征值 协整关系判定 统计套利策略构建

这张图把整个流程串起来了。从价格序列输入开始,选择合适的方法,经过核心步骤,最终得到协整关系判定,然后才能进入策略构建环节。

嗯,协整检验这部分就讲到这里。记住一句话:检验通过只是开始,真正的考验在实盘。下一章我们会聊聊如何构建交易信号,到时候见。

专注资料整理