4、平稳性检验:ADF检验原理、KPSS检验、如何解读检验结果、实战:对股票对进行平稳性检验

做统计套利的朋友,咱们今天聊个绕不开的话题——平稳性检验。

说实话,我刚开始做配对交易那会儿,踩过最大的坑就是没认真做平稳性检验。直接拿两个股票的价格序列去算回归、算残差,结果回测曲线漂亮得不行,一上实盘就崩了。后来才明白,很多所谓的"统计套利机会",其实就是两个非平稳序列的"伪回归"。

嗯,咱们今天就把这事彻底讲透。

4.1 为什么非要检验平稳性?

先问个问题:你拿两个随机游走的价格序列去做回归,会得到什么?

答案是——你会得到看起来非常显著的回归结果。t统计量很大,R²很高,但全是假的。这就是著名的"伪回归"问题。

我有个朋友,当年用茅台和五粮液做配对,没做平稳性检验,直接上模型。回测年化收益30%+,他差点就辞职了。后来我让他跑个ADF检验,结果两个序列都是非平稳的。说白了,那30%的收益就是运气,跟策略没关系。

所以,平稳性检验是统计套利的"安检门"。过不了这关,后面的一切都是空中楼阁。

核心要点:只有平稳的时间序列,才能用统计模型做推断和预测。非平稳序列的统计推断,结果不可靠。

4.2 ADF检验:最常用的平稳性检验

ADF检验,全称是Augmented Dickey-Fuller检验。名字挺长,但原理其实不复杂。

4.2.1 原理一句话说清楚

ADF检验的核心思想:检验时间序列是否存在单位根。

什么叫单位根?简单说,就是序列的当前值完全由上一期的值决定,加上一个随机扰动。比如:

Y_t = Y_{t-1} + ε_t

这种序列就是随机游走,它没有"均值回归"的特性。你今天涨了10%,明天可能继续涨,也可能跌,但不会因为今天涨了就必然回调。

ADF检验的原假设H₀:序列存在单位根(非平稳)。

备择假设H₁:序列不存在单位根(平稳)。

所以,p值越小,越拒绝原假设,序列越可能是平稳的。

我的经验:ADF检验的p值通常要小于0.05才算显著。但我在实际项目中,一般要求p值小于0.01才放心。因为金融数据太"脏"了,0.05的阈值有时候会误判。

4.2.2 代码实现

用Python做ADF检验,其实就几行代码。我习惯用statsmodels库:

import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller

# 模拟一个随机游走序列(非平稳)
np.random.seed(42)
n = 200
random_walk = np.cumsum(np.random.randn(n))

# 模拟一个白噪声序列(平稳)
white_noise = np.random.randn(n)

# ADF检验函数
def adf_test(series, name=''):
    result = adfuller(series, autolag='AIC')
    print(f'序列: {name}')
    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}')
    print(f'结论: {"平稳" if result[1] < 0.05 else "非平稳"}')
    print('-' * 40)
    return result

# 检验
adf_test(random_walk, '随机游走')
adf_test(white_noise, '白噪声')

输出结果大概长这样:

序列: 随机游走
ADF统计量: -1.2345
p值: 0.6543
临界值:
  1%: -3.4567
  5%: -2.8765
  10%: -2.5678
结论: 非平稳

序列: 白噪声
ADF统计量: -5.6789
p值: 0.0000
临界值:
  1%: -3.4567
  5%: -2.8765
  10%: -2.5678
结论: 平稳

你看,随机游走的p值0.65,远大于0.05,不能拒绝原假设,所以是非平稳的。白噪声的p值接近0,拒绝原假设,是平稳的。

4.3 KPSS检验:ADF的"互补"检验

ADF检验有个问题——它的原假设是"非平稳"。这就意味着,如果数据不够多,或者信号不够强,我们可能"无法拒绝"原假设,但并不能证明序列就是平稳的。

说白了,ADF检验的"证据"是反着来的。你只能证明"非平稳",不能直接证明"平稳"。

这时候,KPSS检验就派上用场了。

4.3.1 KPSS检验的原理

KPSS检验的原假设正好相反:序列是平稳的。

所以,KPSS检验的p值如果很小(小于0.05),就说明序列是非平稳的。如果p值很大,说明没有证据拒绝"平稳"这个假设。

最佳实践:我个人的习惯是,ADF和KPSS两个检验一起做。如果ADF说平稳,KPSS也说平稳,那基本稳了。如果ADF说非平稳,KPSS也说非平稳,那肯定不稳。如果两个结果矛盾,就需要进一步分析。

4.3.2 代码实现

from statsmodels.tsa.stattools import kpss

def kpss_test(series, name=''):
    result = kpss(series, regression='c')
    print(f'序列: {name}')
    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}')
    print(f'结论: {"非平稳" if result[1] < 0.05 else "平稳"}')
    print('-' * 40)
    return result

kpss_test(random_walk, '随机游走')
kpss_test(white_noise, '白噪声')

4.4 如何解读检验结果?

很多新手拿到检验结果就懵了。我给大家一个简单的判断矩阵:

ADF结果 KPSS结果 结论 怎么办?
平稳 平稳 序列平稳 直接使用
非平稳 非平稳 序列非平稳 需要差分或变换
平稳 非平稳 可能存在趋势平稳 检查确定性趋势
非平稳 平稳 可能存在单位根但均值回归 进一步分析
我曾经踩过的坑:有一次,我对一个价差序列做ADF检验,p值0.03,我以为是平稳的,直接拿去建模了。结果回测一塌糊涂。后来补做了KPSS检验,发现p值0.01,说明序列其实是非平稳的。两个检验结果矛盾,我仔细一看,原来序列存在一个明显的趋势项。去掉趋势后,两个检验才一致。所以,千万别只看一个检验的结果。

4.5 实战:对股票对进行平稳性检验

好了,理论说完了,咱们来点实战。假设我们要检验"招商银行"和"兴业银行"的价差是否平稳。

4.5.1 获取数据

import yfinance as yf
import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import adfuller, kpss

# 下载数据
tickers = ['600036.SS', '601166.SS']  # 招商银行、兴业银行
data = yf.download(tickers, start='2020-01-01', end='2023-12-31')['Adj Close']
data.columns = ['CMB', 'CIB']

# 计算对数价格
log_prices = np.log(data)

# 计算价差(假设协整向量为[1, -1])
spread = log_prices['CMB'] - log_prices['CIB']

4.5.2 检验价差的平稳性

# ADF检验
adf_result = adfuller(spread.dropna(), autolag='AIC')
print('=== ADF检验结果 ===')
print(f'统计量: {adf_result[0]:.4f}')
print(f'p值: {adf_result[1]:.4f}')
print(f'结论: {"平稳" if adf_result[1] < 0.05 else "非平稳"}')

# KPSS检验
kpss_result = kpss(spread.dropna(), regression='c')
print('\n=== KPSS检验结果 ===')
print(f'统计量: {kpss_result[0]:.4f}')
print(f'p值: {kpss_result[1]:.4f}')
print(f'结论: {"非平稳" if kpss_result[1] < 0.05 else "平稳"}')

4.5.3 可视化价差

import matplotlib.pyplot as plt

plt.figure(figsize=(12, 6))
plt.plot(spread.index, spread.values, label='价差', color='#2E86AB')
plt.axhline(y=0, color='red', linestyle='--', alpha=0.5)
plt.axhline(y=spread.mean(), color='green', linestyle='--', alpha=0.5, label='均值')
plt.title('招商银行 vs 兴业银行 价差序列')
plt.xlabel('日期')
plt.ylabel('价差')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
我的建议:在实际项目中,我一般会先画个图,肉眼看看价差是否围绕均值波动。如果价差有明显的趋势或者漂移,那大概率是非平稳的。然后再用ADF和KPSS做定量检验。定性和定量结合,判断更准确。

4.6 本章知识体系

下面这张图,是我自己总结的平稳性检验的完整流程,大家可以保存下来:

平稳性检验知识体系 原始时间序列 平稳性检验方法 ADF检验 原假设:存在单位根(非平稳) p值 < 0.05 → 平稳 KPSS检验 原假设:序列平稳 p值 < 0.05 → 非平稳 综合判断:两个检验结果对照

嗯,这张图把整个流程串起来了。从原始序列开始,到ADF和KPSS两个检验,再到综合判断。你想想看,是不是比单纯看文字清晰多了?

最后说一句:平稳性检验是统计套利的"地基"。地基没打好,楼盖得再高也得塌。我见过太多人,花大量时间优化参数、调模型,却忽略了最基础的平稳性检验。结果呢?白忙活一场。所以,别嫌麻烦,该做的检验一个都不能少。

专注资料整理