2. 价差分析:价差的计算方法、价差序列的平稳性检验

好,咱们接着聊。上一章我们把跨品种套利的底层逻辑讲清楚了,说白了就是「买强卖弱,赚价差回归的钱」。那问题来了——这个「价差」到底怎么算?算出来之后,怎么判断它能不能用?

我个人习惯,拿到两个品种的行情数据,第一件事不是跑模型,而是先算价差,然后做平稳性检验。这一步要是没过,后面所有 fancy 的策略都是白搭。你想想看,一个非平稳的价差序列,均值都不固定,你拿什么去做回归?

2.1 价差的计算方法

价差计算,听起来简单,但坑不少。我见过不少新手直接拿价格相减,结果发现价差序列越走越偏,根本没法用。为什么?因为两个品种的绝对价格水平可能差很多,比如螺纹钢3000多,热卷3200多,直接相减得到的价差受价格水平影响太大。

常用的价差计算方式有三种,我按推荐程度排个序:

方法 公式 适用场景 我的评价
简单价差 S = P₁ - P₂ 价格水平相近的品种 简单粗暴,但容易踩坑
对数价差 S = ln(P₁) - ln(P₂) 价格水平差异大 我最常用的方法
标准化价差 S = (P₁ - P₂) / σ 需要统一量纲 适合多品种对比

简单价差:就是直接相减。比如螺纹钢和热卷,价格都在3000-4000之间,直接相减问题不大。但如果是铜和铝,一个7万,一个2万,直接相减得到的价差序列,波动幅度会随着价格水平变化而变化,这就麻烦了。

对数价差:我个人最推荐。取对数后再相减,实际上就是计算两个品种的相对价格关系。这样做的好处是,价差序列的方差相对稳定,不会因为价格水平的变化而剧烈波动。我在做螺纹钢和热卷的套利时,用的就是对数价差。

核心要点:对数价差 = ln(P₁) - ln(P₂),它衡量的是两个品种价格的比例关系,而不是绝对差值。这在统计上更稳健。

标准化价差:把简单价差除以标准差,得到的是一个类似 Z-score 的东西。这个方法的好处是,不同品种对的价差可以直接比较。但要注意,标准差是滚动计算的,窗口期选多少很关键。

来,看一段 Python 代码,把三种方法都实现一下:

import numpy as np
import pandas as pd

def calculate_spread(price1, price2, method='log'):
    """
    计算价差
    method: 'simple' - 简单价差
            'log'    - 对数价差
            'zscore' - 标准化价差
    """
    if method == 'simple':
        spread = price1 - price2
    elif method == 'log':
        spread = np.log(price1) - np.log(price2)
    elif method == 'zscore':
        simple_spread = price1 - price2
        spread = (simple_spread - simple_spread.mean()) / simple_spread.std()
    else:
        raise ValueError("method must be 'simple', 'log', or 'zscore'")
    return spread

# 示例:螺纹钢和热卷的日线数据
rb = pd.Series([3800, 3820, 3790, 3810, 3830])  # 螺纹钢
hc = pd.Series([3950, 3930, 3910, 3940, 3960])  # 热卷

spread_log = calculate_spread(rb, hc, method='log')
print("对数价差:", spread_log.values)
我的经验:在实际项目中,我通常先用对数价差做初步分析。如果发现价差序列的波动率有明显的时间趋势,再考虑用标准化价差。记住,没有万能的方法,关键是要理解你的数据。

2.2 价差序列的平稳性检验

好,价差算出来了。接下来最关键的一步:检验它是不是平稳的

为什么要检验平稳性?因为套利策略的核心假设是「价差会回归均值」。如果价差序列是非平稳的,均值都不固定,那「回归均值」就是个伪命题。你想想看,一个随机游走的序列,你去做均值回归策略,那不是送钱给市场吗?

平稳性检验,最常用的就是 ADF 检验(Augmented Dickey-Fuller Test)。它的原假设是「序列存在单位根,即非平稳」。如果 p 值小于 0.05,就拒绝原假设,认为序列是平稳的。

我曾经在一个项目中,看到有人用两个完全不相关的品种做套利,价差序列的 ADF 检验 p 值高达 0.8,他还硬要跑策略。结果回测看起来很美,实盘一跑就崩。嗯,这就是典型的「过拟合」。

来看代码:

from statsmodels.tsa.stattools import adfuller

def check_stationarity(spread, significance=0.05):
    """
    ADF 平稳性检验
    返回: (is_stationary, p_value, adf_statistic)
    """
    result = adfuller(spread, autolag='AIC')
    adf_stat = result[0]
    p_value = result[1]
    critical_values = result[4]
    
    is_stationary = p_value < significance
    
    print(f"ADF 统计量: {adf_stat:.4f}")
    print(f"p 值: {p_value:.4f}")
    print(f"临界值 (1%): {critical_values['1%']:.4f}")
    print(f"临界值 (5%): {critical_values['5%']:.4f}")
    print(f"临界值 (10%): {critical_values['10%']:.4f}")
    print(f"是否平稳: {'是' if is_stationary else '否'}")
    
    return is_stationary, p_value, adf_stat

# 用刚才的对数价差做检验
is_stat, p_val, adf = check_stationarity(spread_log)
注意:ADF 检验对滞后阶数的选择比较敏感。statsmodels 默认用 AIC 自动选择,但有时候也会选错。我建议你手动试几个不同的滞后阶数,看看结果是否稳健。如果 p 值在 0.05 附近摇摆不定,那就要小心了。

除了 ADF 检验,还有几个辅助方法:

  • KPSS 检验:原假设是「序列平稳」,和 ADF 正好相反。两个检验结合使用,结果更可靠。
  • 自相关图(ACF):平稳序列的自相关函数会快速衰减到零。如果衰减很慢,说明可能有单位根。
  • 滚动统计量:计算滚动均值和滚动标准差,如果它们随时间变化明显,那大概率是非平稳的。

下面这张图,是我自己画的一个价差分析流程,你可以参考一下:

价差分析流程图 获取两个品种价格数据 计算价差(推荐对数价差) ADF + KPSS 平稳性检验 平稳? 进入策略设计 尝试协整检验 如果价差非平稳,说明两个品种可能不存在长期均衡关系,套利策略风险较高

你看这个流程,其实很清晰。先算价差,再检验平稳性。如果平稳,直接进入策略设计。如果不平稳,也别急着放弃——可以试试协整检验,看看两个品种是否存在长期均衡关系。协整检验我们下一章会详细讲。

避坑指南:我曾经犯过一个错误——用日线数据做 ADF 检验,p 值显示平稳,但换成 5 分钟线就不平稳了。为什么?因为不同时间频率下,价差的统计特性可能完全不同。所以,一定要在你实际交易的频率上做检验。做日线策略就用日线数据检验,做小时线策略就用小时线数据检验。

最后,总结一下本章的核心要点:

  • 价差计算:推荐对数价差,它衡量的是相对价格关系,统计性质更稳定。
  • 平稳性检验:ADF 检验是标配,p 值小于 0.05 才算平稳。
  • 多方法验证:不要只依赖一个检验,结合 ACF 图和滚动统计量一起看。
  • 频率匹配:在目标交易频率上做检验,别拿日线数据去指导分钟线策略。

嗯,价差分析这块,说白了就是「算出来,验一验」。算对了,验过了,后面的事情就顺了。如果这一步没做好,后面再怎么折腾也是白费力气。


公众号:蓝海资料掘金营,微信deep3321