3. 价差统计特征:均值回复性检验、ADF检验、自相关分析、价差分布形态
做跨期套利,说白了就是赌价差会回归。但你怎么知道它一定会回归?
我见过太多人,看到两张K线长得像,就冲进去做套利。结果价差越走越远,最后爆仓出局。嗯,这里有个关键问题——你得先证明价差有回归的“基因”。
这一节,我们就来解剖价差的统计特征。我会带你走一遍我平时做策略前必做的四个检验:均值回复性、ADF、自相关、分布形态。每一步都有坑,我会把踩过的坑也告诉你。
核心观点:价差不是天然会回归的。只有通过统计检验确认其平稳性,套利策略才有数学基础。否则,你做的不是套利,是赌博。
3.1 均值回复性检验——价差会“回家”吗?
均值回复,就是价差偏离均值后,有拉回来的倾向。怎么量化?我习惯用均值回复半衰期(Half-Life of Mean Reversion)。
半衰期的意思是:价差偏离均值后,回到一半所需的时间。比如半衰期是5天,那偏离后大约5天能回来一半。
计算逻辑:
- 对价差序列做滞后一期的自回归:Δy(t) = α + β * y(t-1) + ε(t)
- 半衰期 = -ln(2) / β
β是负的,半衰期才是正数。β越负,回复越快。
import numpy as np
import statsmodels.api as sm
def half_life(spread):
# 构建滞后项
spread_lag = spread.shift(1).dropna()
spread_diff = spread.diff().dropna()
# 对齐数据
y = spread_diff.values
x = spread_lag.values
# 加常数项
x = sm.add_constant(x)
model = sm.OLS(y, x).fit()
beta = model.params[1]
# 计算半衰期
hl = -np.log(2) / beta
return hl, model
我的经验:半衰期在5-20天之间,是比较理想的套利标的。太短(<3天)说明噪音大,容易频繁交易磨损手续费。太长(>30天)说明回复太慢,资金占用成本高。我曾经用半衰期2天的品种做套利,结果手续费吃掉了一半利润,得不偿失。
3.2 ADF检验——价差是平稳的吗?
ADF检验,全称Augmented Dickey-Fuller检验。名字听着唬人,其实就一件事:判断序列有没有单位根。
有单位根 = 非平稳 = 价差可能一去不回头。没有单位根 = 平稳 = 价差会围绕均值波动。
怎么用?
- 原假设H0:序列有单位根(非平稳)
- 备择假设H1:序列平稳
- p值 < 0.05,拒绝H0,认为平稳
from statsmodels.tsa.stattools import adfuller
def adf_test(spread):
result = adfuller(spread.dropna(), autolag='AIC')
adf_stat = result[0]
p_value = result[1]
critical_values = result[4]
print(f'ADF统计量: {adf_stat:.4f}')
print(f'p值: {p_value:.4f}')
print(f'临界值: {critical_values}')
if p_value < 0.05:
print('结论:价差序列平稳,适合套利')
else:
print('结论:价差非平稳,不建议直接套利')
return result
避坑指南:我曾经用日线数据做ADF检验,p值0.03,觉得稳了。结果实盘时价差一路漂移,亏了不少。后来才发现——ADF检验对数据频率敏感。日线平稳不代表小时线也平稳。我现在的习惯是:用多个时间频率(日线、小时线、30分钟线)分别做ADF,都通过了才放心。
3.3 自相关分析——价差有“记忆”吗?
自相关,就是看今天的价差和昨天的价差有没有关系。如果自相关很强,说明价差有趋势性,不适合均值回复策略。
我常用的两个工具:
- ACF(自相关函数):看不同滞后期的相关系数
- PACF(偏自相关函数):剔除中间变量后的净相关
对于均值回复策略,我们希望ACF快速衰减到0。如果ACF在滞后多期后仍然显著,说明序列有长期记忆,可能不是纯均值回复。
import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
def autocorr_analysis(spread, lags=20):
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6))
plot_acf(spread.dropna(), lags=lags, ax=ax1)
ax1.set_title('价差自相关函数 (ACF)')
plot_pacf(spread.dropna(), lags=lags, ax=ax2)
ax2.set_title('价差偏自相关函数 (PACF)')
plt.tight_layout()
plt.show()
一个小技巧:如果ACF在滞后1期就掉到置信区间内,说明序列接近白噪声,均值回复很快。如果ACF缓慢衰减,比如滞后5期还显著,那就要小心了——可能不是纯均值回复,而是有趋势成分。我一般会结合ARIMA模型进一步分析。
3.4 价差分布形态——你的止损设对了吗?
最后一步,看价差的分布。这直接关系到你的开仓阈值和止损设置。
主要看三点:
| 指标 | 含义 | 对策略的影响 |
|---|---|---|
| 偏度(Skewness) | 分布是否对称 | 偏度≠0时,开仓阈值应不对称 |
| 峰度(Kurtosis) | 尾部厚度 | 高峰度意味着极端值多,止损要放宽 |
| 正态性检验 | 是否符合正态分布 | 不符合时,用分位数而非标准差设阈值 |
from scipy import stats
def distribution_analysis(spread):
spread_clean = spread.dropna()
skew = stats.skew(spread_clean)
kurt = stats.kurtosis(spread_clean, fisher=True) # 超额峰度
normality = stats.jarque_bera(spread_clean)
print(f'偏度: {skew:.4f}')
print(f'超额峰度: {kurt:.4f}')
print(f'Jarque-Bera检验p值: {normality.pvalue:.4f}')
# 建议阈值
q1 = spread_clean.quantile(0.25)
q3 = spread_clean.quantile(0.75)
iqr = q3 - q1
print(f'建议开仓上界: {q3 + 1.5*iqr:.4f}')
print(f'建议开仓下界: {q1 - 1.5*iqr:.4f}')
return skew, kurt, normality
我曾经犯过的错:刚开始做套利时,我默认价差服从正态分布,用2倍标准差设阈值。结果遇到一个品种,价差分布是尖峰厚尾的,2倍标准差外频繁出现极端值,导致我频繁止损。后来改用基于分位数的阈值(比如95%分位数),情况好多了。记住:不要假设正态,用数据说话。
3.5 知识体系总览
下面这张图,是我做价差分析时的完整流程。你可以把它当作一个检查清单。
你看,整个流程其实就三步:算价差 → 做四个检验 → 综合判断。但每一步都有细节,忽略任何一个都可能翻车。
总结一下:
- 均值回复性:看半衰期,5-20天最佳
- ADF检验:p值 < 0.05,多频率验证
- 自相关分析:ACF快速衰减,无长期记忆
- 分布形态:不要假设正态,用分位数设阈值
这四个检验都过了,你的价差才算有了套利的“资格”。接下来,我们才能谈怎么进场、怎么止损。