2. 平稳性与单位根检验:为什么均值回归策略只对平稳时间序列有效?ADF检验与KPSS检验的实战应用
做均值回归策略,第一个绕不开的问题就是:这个品种到底能不能回归?
我刚开始做量化那会儿,踩过一个坑。当时看到某只股票价格跌到历史低位,觉得「跌多了肯定涨回来」,直接重仓杀入。结果呢?它一路跌到退市。后来复盘才发现,那根本不是均值回归,而是趋势反转——或者说,它根本就不是一个平稳序列。
所以今天咱们就来聊聊,为什么平稳性是均值回归策略的命根子,以及怎么用ADF检验和KPSS检验来给数据「体检」。
2.1 什么是平稳时间序列?
先给个直观理解。平稳时间序列,说白了就是:统计性质不随时间变化。
具体来说,一个序列如果满足以下三个条件,就称为弱平稳(也叫宽平稳):
- 均值恒定:序列的期望值不随时间变化,比如始终围绕0上下波动
- 方差恒定:波动幅度不随时间变化,不会出现「前期平稳、后期剧烈震荡」
- 协方差只与时间间隔有关:两个时间点的相关性只取决于它们相隔多远,与具体时刻无关
举个例子:白噪声序列就是最完美的平稳序列。它均值是0,方差恒定,任意两个时间点都不相关。
那非平稳序列长什么样?
- 趋势性:比如股价长期上涨,均值一直在变
- 季节性:比如气温数据,冬天低夏天高,均值周期性变化
- 随机游走:比如大多数股票价格,今天的价格 = 昨天的价格 + 随机扰动
嗯,这里要注意:随机游走是非平稳的典型代表。而均值回归策略,恰恰要求序列是平稳的——因为只有平稳序列,价格才会围绕某个均值来回摆动。
核心结论:均值回归策略的本质,是赌价格偏离均值后会「拉回来」。如果序列不平稳,均值本身都在漂移,你赌什么回归?
2.2 为什么均值回归策略只对平稳序列有效?
我习惯用一个比喻来解释这个问题:
想象你手里牵着一只狗,狗绳长度固定。狗会跑来跑去,但始终被你牵着——这就是平稳序列,狗(价格)围绕你(均值)来回摆动。你可以在狗跑远时拉回来(做空),在狗跑近时放出去(做多)。
但如果狗绳断了呢?狗越跑越远,你再也拉不回来——这就是非平稳序列。你之前以为的「回归」,其实只是趋势中的一次回调。
从数学角度看,均值回归策略通常基于以下假设:
- 价格序列
X_t是平稳的,存在长期均值μ - 当
X_t偏离μ时,有向μ回复的倾向 - 偏离程度越大,回复力度越强
如果序列非平稳,比如 X_t 是随机游走:
X_t = X_{t-1} + ε_t
那么任何一次「偏离」都可能是永久性的。你基于历史均值做的交易决策,很可能就是错的。
我曾经在回测某商品期货的均值回归策略时,发现夏普比率高达3.0,兴奋得不行。结果实盘一个月就亏了15%。后来一查,原来回测期间该品种处于震荡市(平稳),而实盘时进入了趋势行情(非平稳)。教训就是:回测时一定要检验平稳性,而且要用滚动窗口检验。
2.3 单位根检验:判断平稳性的核心工具
怎么判断一个序列是否平稳?最常用的方法就是单位根检验。
先简单说说单位根是什么。考虑一个一阶自回归模型:
X_t = ρ * X_{t-1} + ε_t
如果 |ρ| < 1,序列是平稳的(冲击会衰减)。如果 ρ = 1,就是随机游走(冲击永久存在)。这个 ρ = 1 的情况,就叫「存在单位根」。
所以单位根检验,本质上就是检验 ρ 是否等于1。
常用的两种检验方法:
| 检验方法 | 原假设 H0 | 备择假设 H1 | 适用场景 |
|---|---|---|---|
| ADF检验 | 存在单位根(非平稳) | 不存在单位根(平稳) | 检测趋势平稳或差分平稳 |
| KPSS检验 | 不存在单位根(平稳) | 存在单位根(非平稳) | 检测趋势平稳或水平平稳 |
你发现没有?这两个检验的假设正好相反。所以我的习惯是:两个检验一起做,交叉验证。
我个人习惯:先用ADF检验,如果p值小于0.05,拒绝「非平稳」的原假设,初步认为序列平稳。然后再用KPSS检验,如果p值大于0.05,不能拒绝「平稳」的原假设,那就更放心了。如果两个检验结果矛盾,我会进一步检查数据是否有结构性突变。
2.4 ADF检验实战应用
ADF(Augmented Dickey-Fuller)检验是最常用的单位根检验。它扩展了DF检验,加入了滞后项来处理自相关。
直接上代码:
import numpy as np
import pandas as pd
import yfinance as yf
from statsmodels.tsa.stattools import adfuller, kpss
import matplotlib.pyplot as plt
# 下载数据
ticker = 'SPY'
data = yf.download(ticker, start='2020-01-01', end='2023-12-31')
price = data['Close']
# ADF检验
def adf_test(series, title=''):
result = adfuller(series.dropna(), autolag='AIC')
print(f'=== ADF检验: {title} ===')
print(f'ADF统计量: {result[0]:.4f}')
print(f'p值: {result[1]:.4f}')
print(f'临界值:')
for key, value in result[4].items():
print(f' {key}: {value:.4f}')
if result[1] < 0.05:
print('结论: 拒绝原假设,序列平稳')
else:
print('结论: 不能拒绝原假设,序列非平稳')
return result
# 对价格序列做ADF检验
adf_test(price, 'SPY收盘价')
# 对收益率序列做ADF检验
returns = price.pct_change().dropna()
adf_test(returns, 'SPY日收益率')
运行结果通常是这样:
- 价格序列:p值很大(比如0.5),不能拒绝非平稳——说明价格本身不平稳
- 收益率序列:p值很小(比如0.0001),拒绝非平稳——说明收益率是平稳的
这解释了为什么很多均值回归策略用收益率或价差,而不是直接用价格。
2.5 KPSS检验实战应用
KPSS(Kwiatkowski-Phillips-Schmidt-Shin)检验是ADF的互补工具。它的原假设是「序列平稳」。
def kpss_test(series, title=''):
result = kpss(series.dropna(), regression='c', nlags='auto')
print(f'\n=== KPSS检验: {title} ===')
print(f'KPSS统计量: {result[0]:.4f}')
print(f'p值: {result[1]:.4f}')
print(f'临界值:')
for key, value in result[3].items():
print(f' {key}: {value:.4f}')
if result[1] < 0.05:
print('结论: 拒绝原假设,序列非平稳')
else:
print('结论: 不能拒绝原假设,序列平稳')
return result
# 对价格序列做KPSS检验
kpss_test(price, 'SPY收盘价')
# 对收益率序列做KPSS检验
kpss_test(returns, 'SPY日收益率')
结果通常:
- 价格序列:p值很小,拒绝平稳——与ADF结论一致
- 收益率序列:p值很大,不能拒绝平稳——也与ADF结论一致
实战建议:我一般会写一个组合函数,同时输出ADF和KPSS的结果,并给出综合判断。如果两者结论一致,那就放心用。如果不一致,我会检查数据是否有趋势项或季节性,或者考虑用差分后的数据。
2.6 组合检验:让结论更可靠
单独用ADF或KPSS都可能误判。比如:
- ADF对趋势平稳的序列可能误判为非平稳
- KPSS对接近单位根的序列可能误判为平稳
所以我的标准流程是:
def stationarity_test(series, title=''):
"""组合检验:ADF + KPSS"""
print(f'\n{"="*50}')
print(f'平稳性检验: {title}')
print(f'{"="*50}')
# ADF检验
adf_result = adfuller(series.dropna(), autolag='AIC')
adf_pvalue = adf_result[1]
# KPSS检验
kpss_result = kpss(series.dropna(), regression='c', nlags='auto')
kpss_pvalue = kpss_result[1]
# 综合判断
if adf_pvalue < 0.05 and kpss_pvalue > 0.05:
conclusion = '✅ 序列平稳(ADF和KPSS一致)'
elif adf_pvalue < 0.05 and kpss_pvalue < 0.05:
conclusion = '⚠️ 可能存在趋势平稳(ADF说平稳,KPSS说非平稳)'
elif adf_pvalue > 0.05 and kpss_pvalue > 0.05:
conclusion = '⚠️ 数据量不足或存在结构性突变'
else:
conclusion = '❌ 序列非平稳(ADF和KPSS一致)'
print(f'ADF p值: {adf_pvalue:.4f}')
print(f'KPSS p值: {kpss_pvalue:.4f}')
print(f'综合结论: {conclusion}')
return {
'adf_pvalue': adf_pvalue,
'kpss_pvalue': kpss_pvalue,
'conclusion': conclusion
}
# 使用示例
result = stationarity_test(price, 'SPY收盘价')
result = stationarity_test(returns, 'SPY日收益率')
2.7 知识体系图:平稳性检验的核心逻辑
下面这张图,是我自己梳理的平稳性检验决策流程,分享给你:
2.8 实战中的避坑指南
最后分享几个我踩过的坑:
- 坑一:只看ADF不看KPSS。有一次ADF说序列平稳,我直接用了,结果策略回测很好、实盘很差。后来用KPSS一查,发现序列其实是趋势平稳,均值在缓慢漂移。
- 坑二:不检查滞后阶数。ADF检验的滞后阶数选择很关键。用AIC自动选择通常没问题,但如果你发现结果不稳定,可以手动试试不同阶数。
- 坑三:忽略结构性突变。如果序列在某个时间点发生了结构性变化(比如政策变化、公司重组),那么整个序列可能分段平稳。这时候要用Zivot-Andrews检验或分段检验。
- 坑四:样本量太小。ADF和KPSS对样本量敏感,一般建议至少100个数据点。少于50个时,检验结果基本不可信。
我的经验:在实际项目中,我通常会用滚动窗口做平稳性检验。比如每60个交易日检验一次,看看序列的平稳性是否随时间变化。如果发现平稳性不稳定,那这个品种就不适合做均值回归。
好了,关于平稳性和单位根检验,今天就聊到这儿。记住一句话:没有平稳性,就没有均值回归。下次做策略之前,先给你的数据做个「体检」吧。