3. 单位根检验(上):ADF检验的原理与Python实现

各位同学,咱们今天聊聊单位根检验。说实话,这玩意儿是统计套利的入门砖,也是很多人容易翻车的地方。我当年刚做量化那会儿,就因为单位根检验没搞明白,回测跑得飞起,实盘亏得亲妈都不认识。嗯,今天咱们就把这块硬骨头啃下来。

3.1 为什么需要单位根检验?

先问个问题:你手上有两只股票的价格序列,直接算相关系数,发现高达0.95。是不是就能做配对交易了?

别急。我见过太多新手直接拿价格序列算相关性,然后兴冲冲地建仓。结果呢?两个随机游走的序列,相关系数也可能很高。这就是所谓的「伪回归」——看着相关,其实屁关系没有。

单位根检验,说白了就是检查你的时间序列是不是「随机游走」的。如果是,那它就没有均值回归的特性,不适合做配对交易。如果不是,恭喜你,找到了一个可以下手的标的。

核心要点: 只有平稳序列或者协整序列,才具备统计套利的基础。单位根检验就是帮你判断序列是否平稳的第一道关卡。

3.2 ADF检验的原理

ADF检验,全称是Augmented Dickey-Fuller检验。名字挺唬人,其实逻辑很简单。

咱们先回忆一下,一个时间序列如果是随机游走的,它的表达式是这样的:

y_t = y_{t-1} + ε_t

这里的系数是1,所以叫「单位根」。ADF检验要做的,就是检验这个系数是不是真的等于1。

具体来说,ADF检验会跑这样一个回归:

Δy_t = α + βt + γy_{t-1} + δ₁Δy_{t-1} + ... + δₚΔy_{t-p} + ε_t

其中:

  • Δy_t 是 y_t 的一阶差分
  • α 是常数项
  • βt 是时间趋势项
  • γ 就是我们要检验的核心系数
  • δ₁到δₚ 是滞后项的系数,用来消除自相关

原假设 H₀:γ = 0(存在单位根,序列非平稳)
备择假设 H₁:γ < 0(不存在单位根,序列平稳)

如果检验统计量小于临界值,就拒绝原假设,认为序列是平稳的。反之,就不能拒绝原假设,序列非平稳。

我的经验: 在实际项目中,我一般会同时跑三种形式的ADF检验:带常数项、带常数项和趋势项、什么都不带。然后看哪个结果更合理。有时候带趋势项的检验通过了,但去掉趋势项就不行,这说明序列是「趋势平稳」的,做配对交易时要小心。

3.3 ADF检验的Python实现

好了,理论讲完了,咱们直接上代码。Python里实现ADF检验,最常用的就是statsmodels库。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller
import yfinance as yf

# 下载数据
ticker = 'AAPL'
data = yf.download(ticker, start='2020-01-01', end='2023-12-31')
price = data['Close']

# 执行ADF检验
result = adfuller(price, autolag='AIC')

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('不能拒绝原假设,序列非平稳')

这段代码跑完之后,你会看到类似这样的输出:

ADF统计量: -1.2345
p值: 0.6543
临界值:
  1%: -3.4321
  5%: -2.8623
  10%: -2.5678
不能拒绝原假设,序列非平稳

嗯,意料之中。股票价格序列通常都是非平稳的。那咱们试试看差分后的序列:

# 对一阶差分做ADF检验
price_diff = price.diff().dropna()
result_diff = adfuller(price_diff, autolag='AIC')

print(f'差分后ADF统计量: {result_diff[0]:.4f}')
print(f'差分后p值: {result_diff[1]:.4f}')

if result_diff[1] < 0.05:
    print('差分后序列平稳')
else:
    print('差分后序列仍非平稳')

一般来说,金融时间序列一阶差分后就会变成平稳序列。这就是所谓的「一阶单整」,记作 I(1)。

避坑指南: 我曾经在某个项目中,用日频数据做ADF检验,p值显示平稳。结果换成周频数据,p值就不显著了。后来发现是数据频率的问题——日频数据有太多噪声,导致检验结果不可靠。建议至少用周频或月频数据做单位根检验,或者用多个频率交叉验证。

3.4 滞后阶数的选择

ADF检验里有个参数叫「滞后阶数」,就是前面公式里的 p。这个参数怎么选?

statsmodels的adfuller函数默认用AIC准则自动选择最优滞后阶数。我个人习惯用autolag='AIC',但有时候也会对比一下BIC的结果。

# 手动指定滞后阶数
for lag in range(1, 6):
    result = adfuller(price, maxlag=lag, autolag=None)
    print(f'滞后阶数={lag}, p值={result[1]:.4f}')

你可能会发现,不同滞后阶数下,p值可能不一样。这时候怎么办?我的建议是:

  • 如果所有滞后阶数下结果一致,那没问题
  • 如果结果不一致,优先相信AIC选出来的结果
  • 如果还是拿不准,用KPSS检验交叉验证一下

3.5 知识体系总览

为了让大家更直观地理解本章的知识结构,我画了一张图:

单位根检验(ADF)知识体系 为什么需要单位根检验? 核心问题:序列是否平稳?是否存在单位根? ADF检验原理:检验 γ=0 是否成立 三种检验形式 常数项/趋势项/无 滞后阶数选择 AIC/BIC/手动指定 结果解读 p值 vs 临界值 结论:平稳 → 可直接建模 | 非平稳 → 差分或协整

3.6 实战中的注意事项

最后,分享几个我在实战中踩过的坑:

  1. 数据长度问题: ADF检验对样本量敏感。我一般要求至少100个数据点,少于50个基本不可靠。
  2. 结构性断点: 如果序列在某个时间点发生了结构性变化(比如股灾、政策变化),ADF检验可能会失效。这时候可以考虑用Zivot-Andrews检验。
  3. 多重检验问题: 如果你同时检验100个序列,即使所有序列都是平稳的,也会有大约5个被误判为非平稳。记得做多重检验校正。
  4. 不要只看p值: p值小于0.05就说平稳?不一定。还要看ADF统计量是否小于临界值。有时候p值很小,但统计量其实没超过临界值,这种情况要警惕。
我的习惯: 每次做ADF检验,我都会把原始序列、差分序列、对数序列都跑一遍。然后对比结果,看看哪个更合理。有时候对数收益率序列反而是最平稳的,虽然它理论上应该是白噪声。

好了,ADF检验的原理和实现就讲到这里。记住,单位根检验是统计套利的第一步,但不是唯一的一步。下一节咱们会讲KPSS检验,两者结合使用效果更好。


公众号:蓝海数据掘金营,微信deep3321