第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 本章知识体系
下面这张图总结了协整检验的核心逻辑:
这张图把整个流程串起来了。从价格序列输入开始,选择合适的方法,经过核心步骤,最终得到协整关系判定,然后才能进入策略构建环节。
嗯,协整检验这部分就讲到这里。记住一句话:检验通过只是开始,真正的考验在实盘。下一章我们会聊聊如何构建交易信号,到时候见。