4. 价差计算与统计:价差序列构建、均值回归检验、平稳性(ADF)与协整检验

好,咱们进入第四章。这一章可以说是整个跨期套利策略的「地基」。

你想想看,价差算不对,后面所有策略都是空中楼阁。我见过太多人,上来就写策略,结果价差序列本身就不平稳,回测曲线漂亮得像假的一样——嗯,其实就是假的。

4.1 价差序列的构建

价差,说白了就是两个合约价格之间的差值。但这里有个坑:到底用收盘价、成交均价,还是Tick级的最新价?

我个人习惯,做高频策略时用Tick级数据构建价差。因为高频策略吃的是微观结构里的那点「错配」,你用收盘价算,黄花菜都凉了。

核心公式:
价差 = P近月 - P远月 × 合约乘数比

注意那个「合约乘数比」。我曾在螺纹钢上吃过亏——近月和远月合约乘数都是10吨/手,但有的品种近远月乘数不一样,比如焦煤焦炭。你不调整直接减,价差序列就是歪的。

import pandas as pd
import numpy as np

def build_spread(price_near, price_far, multiplier_ratio=1.0):
    """
    构建价差序列
    :param price_near: 近月合约价格序列
    :param price_far: 远月合约价格序列
    :param multiplier_ratio: 合约乘数比(近月/远月)
    """
    spread = price_near - price_far * multiplier_ratio
    return spread

# 举个例子
near = pd.Series([3850, 3852, 3848, 3855, 3851])
far = pd.Series([3820, 3821, 3818, 3825, 3820])
spread = build_spread(near, far)
print(spread)

输出结果:

0    30
1    31
2    30
3    30
4    31
dtype: float64

你看,价差在30-31之间波动。这个序列看起来挺稳的,但到底能不能用?咱们得做检验。

4.2 均值回归检验

跨期套利能赚钱,核心假设就是价差会「均值回归」。说白了,价差高了会跌下来,低了会涨回去。

怎么检验?我一般用两个方法:

  • 自相关函数(ACF)检验:看价差序列是否与自己的滞后项相关
  • 半衰期(Half-life)计算:价差偏离均值后,多久能回归一半
我的经验:半衰期太短(比如小于5个Tick)说明噪声太大,不适合做策略;半衰期太长(比如超过200个Tick)说明回归太慢,资金效率低。我个人偏好半衰期在20-80个Tick之间的品种。
import statsmodels.api as sm

def half_life(spread):
    """
    计算价差序列的半衰期
    """
    spread_lag = spread.shift(1).dropna()
    spread_diff = spread.diff().dropna()
    
    # 对齐数据
    spread_lag = spread_lag.iloc[1:]
    spread_diff = spread_diff.iloc[1:]
    
    # 回归:Δspread = α + β * spread_lag
    X = sm.add_constant(spread_lag)
    model = sm.OLS(spread_diff, X).fit()
    beta = model.params.iloc[1]
    
    # 半衰期 = -ln(2) / β
    hl = -np.log(2) / beta
    return hl

hl = half_life(spread)
print(f"半衰期: {hl:.2f} 个Tick")

4.3 平稳性检验(ADF检验)

均值回归检验是「感觉上」的,而平稳性检验是「数学上」的。ADF检验就是干这个的。

我曾经犯过一个错:用原始价格序列做ADF检验,结果发现不平稳,就放弃了那个品种。后来才发现——价差序列和价格序列是两码事。价差不平稳不代表不能做,你得先看看是不是协整的。

注意:ADF检验的原假设是「序列存在单位根(不平稳)」。p值小于0.05才能拒绝原假设,认为序列平稳。
from statsmodels.tsa.stattools import adfuller

def adf_test(spread):
    """
    ADF平稳性检验
    """
    result = adfuller(spread, autolag='AIC')
    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("结论:价差序列平稳,适合均值回归策略")
    else:
        print("结论:价差序列不平稳,需要进一步检验协整关系")
    
    return result

adf_test(spread)

输出示例:

ADF统计量: -3.4521
p值: 0.0087
临界值:
  1%: -3.432
  5%: -2.862
  10%: -2.567
结论:价差序列平稳,适合均值回归策略

4.4 协整检验

如果价差不平稳怎么办?别急,还有协整检验这条路。

协整检验的核心思想是:两个价格序列本身不平稳,但它们的线性组合(价差)可能是平稳的。说白了,就是两个醉汉走路东倒西歪,但他们的距离始终保持在一定范围内。

我常用的方法是Engle-Granger两步法:

  1. 第一步:用OLS回归估计协整系数
  2. 第二步:对残差做ADF检验
from statsmodels.tsa.stattools import coint

def cointegration_test(price_near, price_far):
    """
    Engle-Granger协整检验
    """
    score, pvalue, _ = coint(price_near, price_far)
    print(f"协整检验统计量: {score:.4f}")
    print(f"p值: {pvalue:.4f}")
    
    if pvalue < 0.05:
        print("结论:两个合约存在协整关系,可以构建价差策略")
    else:
        print("结论:不存在协整关系,建议换品种")
    
    return pvalue

# 模拟数据
np.random.seed(42)
near_price = np.cumsum(np.random.randn(100)) + 100
far_price = near_price * 0.95 + np.random.randn(100) * 0.5

cointegration_test(near_price, far_price)

4.5 本章知识体系

说了这么多,咱们用一张图把逻辑串起来:

价差计算与统计检验流程 近月 & 远月价格序列 价差序列构建 检验 ADF平稳性检验 协整检验 确定策略可行性

4.6 实战中的坑与建议

最后,分享几个我踩过的坑:

  • 数据频率要匹配:近月用Tick数据,远月用1分钟K线,算出来的价差没法用。我建议统一用Tick级数据,实在不行就统一用1分钟。
  • 注意交割日效应:临近交割月,价差会剧烈波动。我一般会提前一周切换到下一个主力合约。
  • 不要过度优化:ADF检验的p值刚好0.049和0.051差别不大,别为了「通过检验」而硬调参数。
我的习惯:每次跑完检验,我都会把价差序列画出来,肉眼看一下。统计检验会骗人,但眼睛不会。如果价差图看起来像随机游走,那再好的p值我也不信。

好了,价差计算与统计检验就讲到这里。记住一句话:检验做扎实了,策略才能站得住

专注资料整理