3. 价差统计特征:均值回复性检验、ADF检验、自相关分析、价差分布形态

做跨期套利,说白了就是赌价差会回归。但你怎么知道它一定会回归?

我见过太多人,看到两张K线长得像,就冲进去做套利。结果价差越走越远,最后爆仓出局。嗯,这里有个关键问题——你得先证明价差有回归的“基因”

这一节,我们就来解剖价差的统计特征。我会带你走一遍我平时做策略前必做的四个检验:均值回复性、ADF、自相关、分布形态。每一步都有坑,我会把踩过的坑也告诉你。

核心观点:价差不是天然会回归的。只有通过统计检验确认其平稳性,套利策略才有数学基础。否则,你做的不是套利,是赌博。

3.1 均值回复性检验——价差会“回家”吗?

均值回复,就是价差偏离均值后,有拉回来的倾向。怎么量化?我习惯用均值回复半衰期(Half-Life of Mean Reversion)

半衰期的意思是:价差偏离均值后,回到一半所需的时间。比如半衰期是5天,那偏离后大约5天能回来一半。

计算逻辑:

  1. 对价差序列做滞后一期的自回归:Δy(t) = α + β * y(t-1) + ε(t)
  2. 半衰期 = -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 知识体系总览

下面这张图,是我做价差分析时的完整流程。你可以把它当作一个检查清单。

价差统计特征分析流程 价差序列计算 均值回复性检验 ADF平稳性检验 自相关分析 分布形态分析 半衰期计算 回复速度评估 p值 < 0.05 多频率验证 ACF/PACF图 滞后阶数判断 偏度/峰度 分位数阈值 综合判断:是否适合套利 ✅ 通过 → 进入策略设计 ❌ 未通过 → 寻找新标的

你看,整个流程其实就三步:算价差 → 做四个检验 → 综合判断。但每一步都有细节,忽略任何一个都可能翻车。

总结一下:

  • 均值回复性:看半衰期,5-20天最佳
  • ADF检验:p值 < 0.05,多频率验证
  • 自相关分析:ACF快速衰减,无长期记忆
  • 分布形态:不要假设正态,用分位数设阈值

这四个检验都过了,你的价差才算有了套利的“资格”。接下来,我们才能谈怎么进场、怎么止损。

专注资料整理