4、Python中的ADF检验实战:使用statsmodels进行ADF检验、结果解读、代码示例
聊到协整关系,就绕不开一个核心问题——时间序列到底平不平稳。
我个人习惯,拿到任何金融时间序列数据,第一件事就是做ADF检验。为什么?因为后续的建模、回归、协整检验,全都建立在“序列平稳”这个前提上。如果序列不平稳,你算出来的相关系数、回归系数,很可能都是假的——这就是所谓的“伪回归”。
好,咱们直接上干货。这一节,我带你手把手跑一遍ADF检验,用Python的statsmodels库。代码不多,但每一步都有坑,我会把我在项目中踩过的雷都告诉你。
4.1 什么是ADF检验?一句话讲清楚
ADF检验,全称Augmented Dickey-Fuller检验。说白了,就是检验你的时间序列是否存在“单位根”。
有单位根 = 不平稳 = 随机游走 = 没法直接做回归。
没有单位根 = 平稳 = 均值回归 = 可以继续往下玩。
你想想看,如果一只股票的价格是随机游走的,那它今天涨了明天可能跌,没有任何规律可循。但如果你发现两只股票的价格差是平稳的,那就说明它们之间存在协整关系——这就是配对交易的基础。
4.2 安装与导入:别漏了关键包
嗯,这里要注意。很多人只装了statsmodels,但忘了装pandas和numpy。虽然它们不是ADF检验的直接依赖,但数据处理少不了它们。
# 安装(如果还没装的话)
# pip install statsmodels pandas numpy
import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import adfuller
import warnings
warnings.filterwarnings('ignore') # 忽略一些烦人的警告
我个人习惯把adfuller函数单独拎出来,因为后面要反复调用。你如果只做一次检验,直接写statsmodels.tsa.stattools.adfuller()也行,但代码会显得啰嗦。
4.3 生成模拟数据:先造个“不平稳”的序列
为了让你看得更清楚,我先生成两组数据:一组是随机游走(不平稳),一组是白噪声(平稳)。
# 设置随机种子,保证结果可复现
np.random.seed(42)
# 生成100个时间点
n = 100
t = np.arange(n)
# 1. 随机游走(不平稳)
random_walk = np.cumsum(np.random.randn(n))
# 2. 白噪声(平稳)
white_noise = np.random.randn(n)
# 3. 带趋势的平稳序列(实际中常见)
trend_stationary = 0.5 * t + np.random.randn(n)
# 把它们放进DataFrame方便查看
data = pd.DataFrame({
'random_walk': random_walk,
'white_noise': white_noise,
'trend_stationary': trend_stationary
})
print(data.head())
你看,random_walk是累加生成的,所以它没有均值回归的特性。而white_noise是纯随机,均值始终在0附近晃悠。
4.4 执行ADF检验:一行代码搞定
好,检验来了。用adfuller函数,传入一个序列就行。
# 对随机游走做ADF检验
result_rw = adfuller(data['random_walk'])
print("=== 随机游走(不平稳)===")
print(f"ADF统计量: {result_rw[0]:.4f}")
print(f"p值: {result_rw[1]:.4f}")
print(f"临界值: {result_rw[4]}")
print("\n")
# 对白噪声做ADF检验
result_wn = adfuller(data['white_noise'])
print("=== 白噪声(平稳)===")
print(f"ADF统计量: {result_wn[0]:.4f}")
print(f"p值: {result_wn[1]:.4f}")
print(f"临界值: {result_wn[4]}")
输出结果大概长这样:
=== 随机游走(不平稳)===
ADF统计量: -1.2345
p值: 0.6543
临界值: {'1%': -3.498, '5%': -2.891, '10%': -2.582}
=== 白噪声(平稳)===
ADF统计量: -9.8765
p值: 0.0000
临界值: {'1%': -3.498, '5%': -2.891, '10%': -2.582}
看到了吗?随机游走的p值0.65,远大于0.05,不能拒绝零假设——不平稳。白噪声的p值接近0,拒绝零假设——平稳。
4.5 结果解读:别只看p值
adfuller返回的是一个元组,包含6个元素。很多人只取p值,其实浪费了信息。我列个表给你看:
| 索引 | 字段 | 说明 |
|---|---|---|
| 0 | ADF统计量 | 值越小越平稳,通常与临界值比较 |
| 1 | p值 | 小于0.05拒绝零假设 |
| 2 | 滞后阶数 | 自动选择的滞后阶数,用于消除自相关 |
| 3 | 观测样本数 | 实际用于检验的样本量 |
| 4 | 临界值 | 字典,包含1%、5%、10%三个水平的临界值 |
| 5 | 最大信息准则值 | 用于选择滞后阶数的参考值,一般用不到 |
我在项目中遇到过这样的情况:p值显示0.04,刚好小于0.05,但ADF统计量只比5%临界值小一点点。这时候我会多留个心眼——可能序列只是“边缘平稳”。建议你再做一次KPSS检验交叉验证。
4.6 封装成函数:以后直接复用
你想想看,如果每次都要手动写adfuller、手动打印结果,多麻烦。我习惯封装成一个函数,顺便把结果格式化成DataFrame,方便批量处理。
def adf_test(series, title=''):
"""
对时间序列执行ADF检验,并打印详细结果
"""
result = adfuller(series, autolag='AIC')
print(f'=== ADF检验结果: {title} ===')
print(f'ADF统计量: {result[0]:.6f}')
print(f'p值: {result[1]:.6f}')
print(f'滞后阶数: {result[2]}')
print(f'观测样本数: {result[3]}')
print('临界值:')
for key, value in result[4].items():
print(f' {key}: {value:.4f}')
# 判断是否平稳
if result[1] <= 0.05:
print('结论: 拒绝零假设,序列平稳')
else:
print('结论: 不能拒绝零假设,序列不平稳')
print('-' * 40)
return result
# 测试
adf_test(data['random_walk'], '随机游走')
adf_test(data['white_noise'], '白噪声')
adf_test(data['trend_stationary'], '带趋势的平稳序列')
maxlag参数,比如adfuller(series, maxlag=20),让模型多考虑一些滞后项。
4.7 实战案例:检验两只股票价差的平稳性
好,咱们来点真实的。假设你找到了两只股票,觉得它们可能有协整关系。第一步,就是检验它们的价差是否平稳。
# 模拟两只股票的价格序列
np.random.seed(123)
n = 200
# 股票A:随机游走
stock_a = np.cumsum(np.random.randn(n)) + 100
# 股票B:与A协整(价差平稳)
spread = np.random.randn(n) * 0.5 # 平稳的价差
stock_b = stock_a + spread
# 计算价差
price_spread = stock_a - stock_b
# 对价差做ADF检验
adf_test(price_spread, '股票A与股票B的价差')
如果价差的p值小于0.05,恭喜你,找到了一个潜在的配对交易机会。接下来就可以做协整检验、计算半衰期、设计交易信号了——这些是后面章节的内容。
4.8 常见坑与避坑指南
- 数据量太少: ADF检验对样本量敏感。我建议至少用50个以上的数据点,100个以上更稳妥。少于30个,检验结果基本不可信。
- 忽略结构性断点: 如果序列在某个时间点发生了突变(比如政策变化、金融危机),ADF检验可能会误判。这时候可以考虑Zivot-Andrews检验,它允许存在一个结构性断点。
- 过度依赖p值: 我见过有人p值0.049就说平稳,0.051就说不平稳。别这么干。结合统计量和临界值一起看,更靠谱。
- 忘记去趋势: 如果序列有明显的上升或下降趋势,ADF检验可能会把“趋势平稳”误判为“不平稳”。这时候可以设置
regression='ct'参数,让模型包含常数项和趋势项。
# 带趋势项的ADF检验示例
result_trend = adfuller(data['trend_stationary'], regression='ct')
print(f"带趋势项的ADF检验p值: {result_trend[1]:.4f}")
4.9 知识体系流程图
下面这张图,帮你理清ADF检验在整个协整分析中的位置:
这张图你看懂了吗?ADF检验就像一道分水岭。平稳了,往前走做协整;不平稳,先差分再说。别跳过这一步,否则后面全是白费功夫。
好了,ADF检验的实战部分就到这里。代码不多,但每个参数、每个输出都有它的意义。我建议你拿真实数据跑一跑,比如沪深300和上证50的日收益率,看看它们是否平稳。实践出真知。