3. 平稳性检验:ADF检验原理与Python实现
做统计套利,最怕什么?
怕你找到的价差序列是个“假关系”。
两个资产价格一起涨、一起跌,你以为找到了套利机会。结果一算,全是巧合。说白了,就是序列不平稳,回归结果全是“伪回归”。
我刚开始做跨市场套利时,就吃过这个亏。当时盯着螺纹钢和热卷的价差,觉得稳了。回测漂亮得不行。实盘一跑,亏得我头皮发麻。后来才发现,那段时间的价差序列压根不平稳。
所以,今天咱们就聊聊平稳性检验。重点讲ADF检验——这个在量化里用得最多的工具。
3.1 什么是平稳性?
平稳性,说白了就是一个时间序列的统计性质不随时间变化。
具体来说,均值不变、方差不变、自协方差只跟时间间隔有关,跟起点无关。
你想想看,如果价差序列的均值一直在漂移,那你设定的开仓阈值还有什么意义?今天均值是0,明天均值变成5,你的策略不亏才怪。
核心要点:只有平稳序列,才能用历史数据推断未来。这是统计套利的数学基础。
3.2 ADF检验的原理
ADF检验,全称是Augmented Dickey-Fuller检验。名字挺长,但原理不复杂。
它要回答一个问题:这个序列是不是随机游走的?
随机游走,就是今天的变化完全随机,跟过去没关系。比如股价,很多人认为它就是随机游走。
ADF检验的原假设是:序列存在单位根,也就是不平稳。
备择假设是:序列平稳。
检验统计量跟t统计量类似,但临界值不同。如果计算出的ADF统计量小于临界值,就拒绝原假设,认为序列平稳。
嗯,这里要注意:ADF检验的模型有三种形式:
- 无截距无趋势:最简单,但很少用
- 有截距无趋势:最常用,适合均值不为零的平稳序列
- 有截距有趋势:适合有确定性趋势的序列
我个人习惯,先用带截距的模型。如果明显看到趋势,再换带趋势的模型。
3.3 用SVG画一张ADF检验流程图
先给你看一张图,把整个流程理清楚。后面写代码时,你就知道每一步在干什么了。
3.4 Python实现:手把手写代码
理论讲完了,咱们直接上代码。我用的是statsmodels库,里面封装好了ADF检验。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller
# 生成一个平稳序列和一个非平稳序列做对比
np.random.seed(42)
# 平稳序列:白噪声
n = 200
stationary = np.random.randn(n)
# 非平稳序列:随机游走
non_stationary = np.cumsum(np.random.randn(n))
# 画图看看
fig, axes = plt.subplots(2, 1, figsize=(10, 6))
axes[0].plot(stationary, color='#17a2b8')
axes[0].set_title('平稳序列(白噪声)')
axes[0].axhline(y=0, color='gray', linestyle='--')
axes[1].plot(non_stationary, color='#dc3545')
axes[1].set_title('非平稳序列(随机游走)')
axes[1].axhline(y=0, color='gray', linestyle='--')
plt.tight_layout()
plt.show()
小提示:实际项目中,我一般先画图看一眼。如果序列有明显的趋势或波动幅度在变化,大概率不平稳。但最终还是要靠ADF检验说话。
接下来,写一个函数来做ADF检验:
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'临界值:')
for key, value in result[4].items():
print(f' {key}: {value:.6f}')
if result[1] <= 0.05:
print('结论:拒绝原假设,序列平稳')
else:
print('结论:无法拒绝原假设,序列不平稳')
print('')
return result
# 检验平稳序列
adf_test(stationary, '白噪声')
# 检验非平稳序列
adf_test(non_stationary, '随机游走')
输出结果大概长这样:
===== ADF检验结果:白噪声 =====
ADF统计量: -6.123456
p值: 0.000001
临界值:
1%: -3.463
5%: -2.876
10%: -2.574
结论:拒绝原假设,序列平稳
===== ADF检验结果:随机游走 =====
ADF统计量: -0.987654
p值: 0.753210
临界值:
1%: -3.463
5%: -2.876
10%: -2.574
结论:无法拒绝原假设,序列不平稳
关键解读:
- p值小于0.05,说明在95%置信水平下拒绝原假设,序列平稳
- ADF统计量越负,越倾向于平稳
- 实际中,我一般看p值就够了。p值小于0.05,就认为平稳
3.5 实战中的坑:我曾经踩过的雷
讲几个我实际项目中遇到的坑,你以后碰到了能少走弯路。
坑一:滞后阶数选不对
ADF检验需要指定滞后阶数。选少了,自相关没消除;选多了,检验功效下降。
我曾经用默认的滞后阶数去检验一个日频价差序列,结果p值0.08,不平稳。后来换成AIC自动选择,p值变成0.01,平稳了。
所以,我建议用autolag='AIC',让程序自动选最优滞后阶数。
坑二:模型形式选错
序列有明显趋势时,要用带趋势项的模型。否则检验结果会偏向于不平稳。
反过来,序列没有趋势时,用了带趋势的模型,检验功效会下降。
我的做法是:先画图,肉眼判断有没有趋势。拿不准的时候,两种模型都跑一遍,看结果是否一致。
坑三:样本量太小
ADF检验对样本量敏感。样本太少,检验结果不可靠。
一般来说,至少要有50个数据点。我个人的底线是100个。
曾经有个学员拿20个数据点做ADF检验,问我为什么结果总是不平稳。我说,你这不是检验,是猜硬币。
3.6 价差序列的平稳性检验实战
回到咱们的跨市场套利场景。假设你找到了两个相关资产,算出了价差序列。接下来怎么做?
第一步:画价差图,肉眼观察。
第二步:做ADF检验。
第三步:如果不平稳,尝试一阶差分。如果一阶差分后平稳,说明价差是I(1)序列,可以做协整检验。
# 模拟两个相关资产的价格
np.random.seed(123)
T = 500
# 共同趋势
trend = np.cumsum(np.random.randn(T))
# 资产A = 趋势 + 噪声
asset_a = trend + np.random.randn(T) * 0.5
# 资产B = 趋势 + 噪声(与A的噪声相关)
asset_b = trend + np.random.randn(T) * 0.5
# 价差
spread = asset_a - asset_b
# 检验价差平稳性
result = adf_test(spread, '价差序列')
# 如果不平稳,试试一阶差分
if result[1] > 0.05:
spread_diff = np.diff(spread)
adf_test(spread_diff, '价差一阶差分')
实战建议:我一般会同时看ADF统计量和p值。如果ADF统计量远小于1%临界值,即使p值略大于0.05,我也会倾向于认为序列平稳。毕竟统计检验不是绝对的,要结合业务逻辑判断。
3.7 小结
平稳性检验,是统计套利的入门第一关。ADF检验是最常用的工具,但也不是万能的。
记住三点:
- 选对模型形式(截距/趋势)
- 用AIC自动选滞后阶数
- 样本量别太少
做到这三点,你的平稳性检验基本不会出大问题。
下一章,咱们聊聊协整检验。那是把两个不平稳序列变成平稳组合的关键技术。
公众号:蓝海资料掘金营,微信deep3321