第4章:价格序列的平稳性检验
做统计套利,第一步就是看数据稳不稳。
你想想看,如果两个品种的价格序列像醉汉走路一样,东倒西歪没个规律,那你还怎么指望它们能回归均值?我刚开始做套利那会儿,就吃过这个亏——拿了两组非平稳数据硬跑回归,结果回测曲线漂亮得不行,实盘一跑直接崩了。
后来我才明白:平稳性检验,是所有统计套利模型的基石。
核心观点:只有平稳的时间序列,才能用统计模型去预测和套利。非平稳序列会带来"伪回归"问题,让你看到假的相关性。
4.1 什么是平稳性?
平稳性说白了就是:统计性质不随时间变化。
具体来说,一个平稳序列满足三个条件:
- 均值恒定——长期来看,数据围绕一个固定值波动
- 方差恒定——波动幅度不会越变越大或越变越小
- 协方差只与时间间隔有关——两个时间点的相关性只取决于它们相隔多远,跟具体在哪个时间点无关
举个例子:白噪声就是最完美的平稳序列。而股票价格,通常是非平稳的——它可能一路涨上去,均值一直在变。
我的经验:在实际交易中,我们很少遇到天然平稳的价格序列。价差序列倒是经常能通过一些处理变成平稳的。这就是套利机会所在。
4.2 ADF检验原理
ADF检验,全称是Augmented Dickey-Fuller检验。名字挺长,但原理不复杂。
它的核心思路是:检验序列是否存在单位根。
什么叫单位根?我简单解释一下:
- 如果一个序列的当前值 = 前一个值 + 随机扰动,那它就有一个单位根
- 有单位根的序列,就是非平稳的
- ADF检验的原假设H₀:序列存在单位根(非平稳)
- 备择假设H₁:序列不存在单位根(平稳)
检验结果看p值:
- p值 < 0.05 → 拒绝原假设 → 序列平稳
- p值 ≥ 0.05 → 不能拒绝原假设 → 序列非平稳
避坑指南:我曾经遇到一个序列,ADF检验p值0.04,刚好小于0.05,我就以为它平稳了。结果回测一塌糊涂。后来发现,样本量太小导致检验功效不足。记住:样本量至少要有50个以上,最好100+,ADF检验才靠谱。
4.3 Python实现ADF检验
代码其实很简单,statsmodels库已经封装好了。
import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import adfuller
import matplotlib.pyplot as plt
# 模拟一个非平稳序列(随机游走)
np.random.seed(42)
n = 200
random_walk = np.cumsum(np.random.randn(n)) + 100
# 模拟一个平稳序列(白噪声)
white_noise = np.random.randn(n) * 5
# ADF检验函数
def adf_test(series, name=''):
result = adfuller(series, autolag='AIC')
print(f'=== {name} ADF检验结果 ===')
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('结论: 序列平稳 (拒绝H₀)')
else:
print('结论: 序列非平稳 (不能拒绝H₀)')
print('-' * 40)
return result[1] # 返回p值
# 检验随机游走(非平稳)
p1 = adf_test(random_walk, '随机游走')
# 检验白噪声(平稳)
p2 = adf_test(white_noise, '白噪声')
# 可视化对比
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(random_walk)
plt.title(f'随机游走 (p值={p1:.4f})')
plt.subplot(1, 2, 2)
plt.plot(white_noise)
plt.title(f'白噪声 (p值={p2:.4f})')
plt.tight_layout()
plt.show()
运行结果你会看到:随机游走的p值很大(比如0.5以上),白噪声的p值很小(接近0)。
我个人习惯:除了看p值,我还会对比ADF统计量和临界值。如果ADF统计量比1%的临界值还小,那基本可以放心了。这比单看p值更稳妥。
4.4 KPSS检验
ADF检验有个问题:它的原假设是"非平稳"。有时候我们想要反过来验证——假设序列是平稳的,看能不能被拒绝。
这就是KPSS检验的作用。
| 检验方法 | 原假设H₀ | 备择假设H₁ | 适用场景 |
|---|---|---|---|
| ADF | 非平稳(有单位根) | 平稳 | 怀疑序列非平稳时 |
| KPSS | 平稳 | 非平稳(有单位根) | 怀疑序列平稳时 |
你可能会问:那到底用哪个?
我的建议是:两个都做。
- ADF显著 + KPSS不显著 → 序列平稳
- ADF不显著 + KPSS显著 → 序列非平稳
- 两个都显著或都不显著 → 数据量不够,或者序列有结构性断点
from statsmodels.tsa.stattools import kpss
def kpss_test(series, name=''):
result = kpss(series, regression='c')
print(f'=== {name} KPSS检验结果 ===')
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('结论: 序列非平稳 (拒绝H₀)')
else:
print('结论: 序列平稳 (不能拒绝H₀)')
print('-' * 40)
return result[1]
# 对同一个序列做两种检验
print('=== 综合检验 ===')
p_adf = adf_test(random_walk, '随机游走')
p_kpss = kpss_test(random_walk, '随机游走')
if p_adf < 0.05 and p_kpss >= 0.05:
print('✅ 综合判断: 序列平稳')
elif p_adf >= 0.05 and p_kpss < 0.05:
print('✅ 综合判断: 序列非平稳')
else:
print('⚠️ 结果不一致,建议增加数据量或检查结构性断点')
我曾经踩过的坑:有一次两个检验结果都不显著,我以为是数据问题,折腾了半天。后来发现,原来是序列中间有一个明显的断点(政策变化导致的)。遇到这种情况,可以分段检验,或者用Zivot-Andrews检验来处理结构性断点。
4.5 如何处理非平稳序列:差分法
如果序列非平稳,怎么办?
最常用的方法就是差分。
一阶差分的公式很简单:
Δyₜ = yₜ - yₜ₋₁
说白了,就是把今天的值减去昨天的值,看变化量。很多金融时间序列,价格本身是非平稳的,但价格的变化(收益率)往往是平稳的。
# 对非平稳序列做差分
def difference(series, order=1):
diff_series = series.copy()
for i in range(order):
diff_series = diff_series.diff().dropna()
return diff_series
# 对随机游走做一阶差分
diff_rw = difference(pd.Series(random_walk), order=1)
# 检验差分后的序列
print('差分后的ADF检验:')
adf_test(diff_rw.values, '差分后序列')
# 可视化对比
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(random_walk)
plt.title('原始序列(非平稳)')
plt.subplot(1, 2, 2)
plt.plot(diff_rw.values)
plt.title('一阶差分后(平稳)')
plt.tight_layout()
plt.show()
你会看到:差分后的序列,均值基本在0附近波动,方差也稳定了。ADF检验的p值会变得很小。
关键点:
- 大多数金融价格序列需要1阶差分就能变平稳
- 如果1阶差分后还不平稳,试试2阶差分
- 但别超过2阶——高阶差分会让数据失去经济含义
- 在统计套利中,我们通常对价差序列做检验,而不是对单个价格
4.6 本章知识体系
下面这张图,是我自己梳理的平稳性检验流程。每次做套利模型前,我都会走一遍这个流程。
我的工作流:拿到一组价差数据,我一般先画个时序图看看趋势。如果明显有趋势,直接差分。如果没有明显趋势,再做ADF+KPSS检验。这样能省不少时间。
好了,平稳性检验就讲到这里。记住一句话:不检验平稳性,就别谈统计套利。这是血的教训换来的经验。
公众号:蓝海数据掘金营,微信deep3321