平稳性检验基础:单位根检验(ADF)的原理与Python实现
各位同学,今天我们来聊聊平稳性检验。说实话,我刚入行做量化交易那会儿,对「平稳性」这三个字完全没概念。直到有一次,我用两组完全不相关的价格序列跑回归,居然跑出了0.85的R²——当时我还以为自己发现了什么交易圣杯,结果被老交易员一句话点醒:「你这是在用两个醉汉的脚印做回归分析。」
嗯,这个比喻很形象。两个醉汉在街上乱晃,他们的轨迹可能看起来「相关」,但本质上毫无关系。这就是伪回归的典型场景。而平稳性检验,就是帮我们识别「醉汉」和「清醒的人」的关键工具。
什么是平稳性?
平稳性,说白了就是一个时间序列的统计性质不随时间变化。具体来说,如果序列的均值、方差、自协方差都是常数(不依赖于时间t),我们就说它是平稳的。
我习惯把平稳序列想象成一个「有记忆但不会漂移」的过程。比如白噪声,它是最简单的平稳序列——均值恒为0,方差恒定,不同时刻之间没有相关性。再比如一个围绕固定均值波动的价格序列,如果它的波动幅度基本稳定,那它也可能是平稳的。
但金融数据里,大部分原始价格序列都是非平稳的。你想想看,上证指数从1990年的100点涨到现在的3000多点,均值明显在变化,对吧?这就是典型的非平稳——它有趋势,有漂移。
单位根:非平稳的「罪魁祸首」
为什么序列会非平稳?根源在于「单位根」。
我们来看一个简单的一阶自回归模型 AR(1):
y_t = ρ * y_{t-1} + ε_t
其中 ε_t 是白噪声。这个模型描述的是:当前值等于上一期值乘以系数ρ,再加上一个随机扰动。
关键来了:
- 如果 |ρ| < 1:序列是平稳的。冲击会逐渐衰减,序列会回归均值。
- 如果 ρ = 1:这就是单位根过程。冲击是永久性的,序列会随机游走,不会回归均值。
- 如果 |ρ| > 1:序列是爆炸性的,这在金融中很少见。
所以,检验平稳性,本质上就是检验 ρ 是否等于 1。这就是单位根检验的核心思想。
ADF检验的原理
ADF检验(Augmented Dickey-Fuller Test)是最常用的单位根检验方法。它是在DF检验的基础上扩展而来的,主要解决了DF检验中残差自相关的问题。
ADF检验的回归方程是这样的:
Δy_t = α + β*t + γ*y_{t-1} + δ_1*Δy_{t-1} + δ_2*Δy_{t-2} + ... + δ_p*Δy_{t-p} + ε_t
其中:
- Δy_t = y_t - y_{t-1},是一阶差分
- α 是截距项(漂移项)
- β*t 是时间趋势项
- γ 是我们最关心的系数——如果 γ=0,说明存在单位根
- δ_1 到 δ_p 是滞后差分项,用来消除残差的自相关
原假设 H₀:γ = 0(存在单位根,序列非平稳)
备择假设 H₁:γ < 0(序列平稳)
检验统计量是 γ 的t统计量。ADF检验会把这个统计量和临界值比较,来判断是否拒绝原假设。
p-value与统计量的关系
这里我要重点讲一下p-value和统计量的关系。很多初学者会搞混,我当年也踩过这个坑。
简单来说:
- ADF统计量:计算出来的检验值,越小(越负)越好
- p-value:在统计量对应的概率值,越小越能拒绝原假设
- 临界值:不同显著性水平下的阈值(1%、5%、10%)
判断规则:
- 如果 ADF统计量 < 临界值,且 p-value < 0.05:拒绝原假设,序列平稳
- 如果 ADF统计量 > 临界值,且 p-value > 0.05:不能拒绝原假设,序列非平稳
举个例子:假设我们算出来的ADF统计量是 -3.5,1%临界值是 -3.43,5%临界值是 -2.86。那么 -3.5 < -3.43,说明在1%显著性水平下可以拒绝原假设,序列是平稳的。对应的p-value应该小于0.01。
Python实现:从零开始写ADF检验
好了,理论讲完了,我们来动手写代码。我个人习惯先用statsmodels库,因为它封装得很好,但为了让大家理解原理,我们先手动实现一个简化版。
import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller
import matplotlib.pyplot as plt
# 生成示例数据:平稳序列 vs 非平稳序列
np.random.seed(42)
n = 200
# 平稳序列:AR(1) with ρ=0.5
y_stationary = np.zeros(n)
for t in range(1, n):
y_stationary[t] = 0.5 * y_stationary[t-1] + np.random.randn()
# 非平稳序列:随机游走 (ρ=1)
y_nonstationary = np.cumsum(np.random.randn(n))
# 可视化
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(y_stationary)
plt.title('平稳序列 (ρ=0.5)')
plt.subplot(1, 2, 2)
plt.plot(y_nonstationary)
plt.title('非平稳序列 (随机游走)')
plt.tight_layout()
plt.show()
接下来,我们用statsmodels的adfuller函数来做ADF检验:
# ADF检验函数
def adf_test(series, title=''):
result = adfuller(series, autolag='AIC')
print(f'=== {title} ===')
print(f'ADF统计量: {result[0]:.4f}')
print(f'p-value: {result[1]:.4f}')
print(f'临界值:')
for key, value in result[4].items():
print(f' {key}: {value:.4f}')
print(f'结论: {"平稳" if result[1] < 0.05 else "非平稳"}')
print()
# 检验平稳序列
adf_test(y_stationary, '平稳序列')
# 检验非平稳序列
adf_test(y_nonstationary, '非平稳序列')
输出结果大概是这样:
=== 平稳序列 ===
ADF统计量: -5.2341
p-value: 0.0000
临界值:
1%: -3.4632
5%: -2.8759
10%: -2.5744
结论: 平稳
=== 非平稳序列 ===
ADF统计量: -1.2345
p-value: 0.6543
临界值:
1%: -3.4632
5%: -2.8759
10%: -2.5744
结论: 非平稳
你看,平稳序列的ADF统计量是 -5.23,远小于1%临界值 -3.46,p-value接近0,所以拒绝原假设,序列平稳。而非平稳序列的ADF统计量是 -1.23,大于所有临界值,p-value是0.65,不能拒绝原假设,序列非平稳。
实际应用中的注意事项
在实际项目中,有几点我想特别提醒大家:
- 滞后阶数的选择:ADF检验需要选择滞后阶数p。statsmodels默认用AIC自动选择,但我建议你手动检查一下。如果滞后阶数太少,残差可能有自相关;如果太多,检验功效会下降。
- 趋势项的选择:ADF检验有三种形式——无截距无趋势、有截距无趋势、有截距有趋势。选择哪种?看数据特征。如果序列有明显的时间趋势,就选有趋势项;如果没有,就选只有截距项。
- 样本量的影响:样本量太小(比如少于50个),ADF检验的功效会很低,可能把平稳序列误判为非平稳。我一般要求至少100个数据点。
知识体系图
下面我用一张SVG图来总结本章的核心逻辑:
这张图把ADF检验的流程梳理得很清楚:输入数据 → 构建回归方程 → 计算统计量和p-value → 根据规则判断平稳性。每一步都环环相扣,缺一不可。
小结
平稳性检验是协整分析的基石。ADF检验通过检验单位根是否存在,帮我们判断序列是否平稳。记住三个关键点:
- ADF统计量越小(越负),越可能平稳
- p-value越小,越能拒绝非平稳的原假设
- 两个指标要结合着看,不能只看一个
下一章我们会深入协整检验的具体方法,到时候这些基础概念会反复用到。嗯,今天就到这里,大家回去可以拿自己的数据跑一跑ADF检验,看看哪些序列是平稳的,哪些不是。