3、平稳性检验:ADF检验原理、KPSS检验、实战中如何判断价差是否平稳

做统计套利,最核心的一步是什么?

我个人觉得,就是判断价差是否平稳。

你想想看,如果价差本身就不平稳,那它今天涨到天上去,明天跌到地板上,你凭什么认为它会回归?说白了,没有平稳性,就没有均值回归,也就没有套利机会。

这一节,我们就来彻底搞懂平稳性检验。我会把ADF和KPSS这两个最常用的检验讲透,再结合我踩过的坑,告诉你实战中到底该怎么用。

3.1 什么是平稳性?为什么它这么重要?

平稳性,用大白话说就是:一个时间序列的统计性质不随时间变化。

具体到价差上,我们希望它满足三个条件:

  • 均值恒定:价差围绕一个固定值上下波动,不会越飘越远
  • 方差恒定:波动的幅度大致稳定,不会突然放大或缩小
  • 协方差只与时间间隔有关:过去和未来的关系只取决于相隔多久,不取决于具体在哪个时间点

我在项目中遇到过这样的情况:两个品种看起来走势很相似,价差图也挺漂亮,但一检验发现是非平稳的。结果呢?实盘跑了两个月,价差一路向北,亏得我头皮发麻。嗯,从那以后,我再也不敢跳过平稳性检验了。

核心结论:只有平稳的价差序列,才能用统计套利模型。非平稳的价差,你做的不是套利,是趋势交易。

3.2 ADF检验:最常用的单位根检验

ADF检验,全称是Augmented Dickey-Fuller检验。名字挺长,但原理其实不复杂。

3.2.1 原理一句话说清楚

ADF检验的核心,就是检验序列是否存在单位根

什么是单位根?你可以把它理解成序列的"记忆性"。如果存在单位根,说明过去的冲击会永久性地影响未来——价差回不去了。

ADF的原假设是:序列存在单位根(非平稳)

备择假设是:序列不存在单位根(平稳)

所以,我们想要的是p值小于显著性水平(比如0.05),拒绝原假设,认为价差平稳。

3.2.2 实战中的代码实现

我习惯用statsmodels库来做ADF检验。代码很简单,但有几个参数需要注意。

import statsmodels.tsa.stattools as ts

# 假设价差序列存在spread这个变量里
result = ts.adfuller(spread, 
                     maxlag=None,    # 自动选择滞后阶数
                     regression='c', # 只包含常数项,最常用
                     autolag='AIC')  # 用AIC准则选最优滞后

# 提取关键结果
adf_statistic = result[0]
p_value = result[1]
critical_values = result[4]

print(f'ADF统计量: {adf_statistic:.4f}')
print(f'p值: {p_value:.6f}')
print(f'临界值: {critical_values}')

if p_value < 0.05:
    print('✅ 拒绝原假设,价差平稳')
else:
    print('❌ 无法拒绝原假设,价差非平稳')

我的经验:regression参数有三个选项——'c'(常数项)、'ct'(常数项+趋势项)、'ctt'(常数项+趋势项+二次趋势)。对于价差序列,我一般用'c'就够了。如果你发现价差有明显的趋势,可以试试'ct',但这种情况通常说明你的配对有问题。

3.2.3 一个容易踩的坑

我曾经犯过一个错误:直接用原始价差做ADF检验,结果p值0.03,欢天喜地以为找到好配对。结果实盘一跑,价差根本不回归。

后来才发现,问题出在滞后阶数的选择上。如果数据有自相关性,默认的滞后阶数可能不够,导致检验结果不可靠。

我的建议是:

  • 用autolag='AIC'让程序自动选最优滞后
  • 或者手动设置maxlag为int(np.sqrt(len(spread)))——这是经验法则
  • 两种方法都试试,结果一致才放心

3.3 KPSS检验:ADF的互补工具

光用ADF够吗?不够。

ADF有个弱点:它的检验功效在样本量小的时候不太行。而且,ADF的原假设是非平稳,有时候我们想反过来验证——假设序列是平稳的,看能不能被拒绝。

这时候,KPSS检验就派上用场了。

3.3.1 KPSS的原理

KPSS检验和ADF正好相反:

  • 原假设:序列是平稳的(趋势平稳)
  • 备择假设:序列存在单位根(非平稳)

所以,我们想要的是KPSS的p值大于0.05,不能拒绝原假设,认为序列平稳。

3.3.2 代码实现

from statsmodels.tsa.stattools import kpss

# KPSS检验
kpss_stat, p_value, lags, critical_values = kpss(spread, 
                                                  regression='c', 
                                                  nlags='auto')

print(f'KPSS统计量: {kpss_stat:.4f}')
print(f'p值: {p_value:.6f}')

if p_value > 0.05:
    print('✅ 不能拒绝原假设,价差平稳')
else:
    print('❌ 拒绝原假设,价差非平稳')

注意:KPSS的regression参数和ADF类似。但要注意,如果序列有确定性趋势(比如缓慢上升),用'ct'会更合适。我一般先用'c',如果结果不理想再试'ct'。

3.4 实战中如何判断价差是否平稳?我的完整流程

说了这么多理论,实战中到底怎么操作?我总结了一套流程,用了好几年,基本没出过问题。

3.4.1 第一步:肉眼观察

别急着跑代码。先画个图看看。

价差图如果看起来像白噪声,围绕零轴上下波动,那大概率是平稳的。如果价差有明显的趋势、漂移、或者波动幅度越来越大,那基本没戏。

3.4.2 第二步:ADF + KPSS双检验

我习惯两个检验都做,然后看组合结果:

ADF结果 KPSS结果 结论
平稳(p<0.05) 平稳(p>0.05) ✅ 强平稳,放心用
平稳(p<0.05) 非平稳(p<0.05) ⚠️ 可能是趋势平稳,需进一步分析
非平稳(p>0.05) 平稳(p>0.05) ⚠️ 数据量可能不够,建议增加样本
非平稳(p>0.05) 非平稳(p<0.05) ❌ 非平稳,放弃这个配对

你看,两个检验一起用,能避免很多误判。我曾经就遇到过ADF说平稳、KPSS说非平稳的情况,后来发现是数据里有个结构性断点。这种情况,要么换数据区间,要么用更复杂的检验(比如Zivot-Andrews检验)。

3.4.3 第三步:看自相关图

检验通过后,我还会看一眼自相关图(ACF)。

平稳序列的自相关应该快速衰减到零附近。如果自相关衰减很慢,或者有明显的周期性,那说明序列可能还有问题。

import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf

plot_acf(spread, lags=40)
plt.show()

3.4.4 第四步:滚动统计量验证

这是我自己加的一道保险。

我会计算滚动窗口(比如60个交易日)的均值和标准差,看看它们是否稳定。如果滚动均值一直在漂移,或者滚动标准差在扩大,那即使检验通过了,我也不会用这个配对。

rolling_mean = spread.rolling(window=60).mean()
rolling_std = spread.rolling(window=60).std()

plt.plot(rolling_mean, label='滚动均值')
plt.plot(rolling_std, label='滚动标准差')
plt.legend()
plt.show()

一个小技巧:如果价差通过了所有检验,但滚动均值偶尔有大的偏移,我会考虑用"去中心化"的价差——也就是减去滚动均值。这样能消除局部的不平稳,让模型更稳健。

3.5 本章知识体系

下面这张图,是我梳理的平稳性检验完整流程。你可以把它当成一个检查清单。

平稳性检验实战流程 价差序列 ① 肉眼观察 ② ADF检验 + KPSS检验 ③ 自相关图(ACF) ④ 滚动统计量验证 ✅ 确认平稳 检验结果组合 ADF平稳 + KPSS平稳 ADF平稳 + KPSS非平稳 两者都非平稳 → 放弃

嗯,这套流程走下来,基本能过滤掉90%以上的伪配对。剩下的10%,要么是数据质量有问题,要么是市场结构发生了变化——这些就不是检验能解决的了。

记住一句话:检验是工具,不是信仰。再漂亮的检验结果,也比不上你对市场和品种的深入理解。


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