2. 价差分析:价差的计算方法、价差序列的平稳性检验
好,咱们接着聊。上一章我们把跨品种套利的底层逻辑讲清楚了,说白了就是「买强卖弱,赚价差回归的钱」。那问题来了——这个「价差」到底怎么算?算出来之后,怎么判断它能不能用?
我个人习惯,拿到两个品种的行情数据,第一件事不是跑模型,而是先算价差,然后做平稳性检验。这一步要是没过,后面所有 fancy 的策略都是白搭。你想想看,一个非平稳的价差序列,均值都不固定,你拿什么去做回归?
2.1 价差的计算方法
价差计算,听起来简单,但坑不少。我见过不少新手直接拿价格相减,结果发现价差序列越走越偏,根本没法用。为什么?因为两个品种的绝对价格水平可能差很多,比如螺纹钢3000多,热卷3200多,直接相减得到的价差受价格水平影响太大。
常用的价差计算方式有三种,我按推荐程度排个序:
| 方法 | 公式 | 适用场景 | 我的评价 |
|---|---|---|---|
| 简单价差 | S = P₁ - P₂ | 价格水平相近的品种 | 简单粗暴,但容易踩坑 |
| 对数价差 | S = ln(P₁) - ln(P₂) | 价格水平差异大 | 我最常用的方法 |
| 标准化价差 | S = (P₁ - P₂) / σ | 需要统一量纲 | 适合多品种对比 |
简单价差:就是直接相减。比如螺纹钢和热卷,价格都在3000-4000之间,直接相减问题不大。但如果是铜和铝,一个7万,一个2万,直接相减得到的价差序列,波动幅度会随着价格水平变化而变化,这就麻烦了。
对数价差:我个人最推荐。取对数后再相减,实际上就是计算两个品种的相对价格关系。这样做的好处是,价差序列的方差相对稳定,不会因为价格水平的变化而剧烈波动。我在做螺纹钢和热卷的套利时,用的就是对数价差。
标准化价差:把简单价差除以标准差,得到的是一个类似 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 检验,还有几个辅助方法:
- KPSS 检验:原假设是「序列平稳」,和 ADF 正好相反。两个检验结合使用,结果更可靠。
- 自相关图(ACF):平稳序列的自相关函数会快速衰减到零。如果衰减很慢,说明可能有单位根。
- 滚动统计量:计算滚动均值和滚动标准差,如果它们随时间变化明显,那大概率是非平稳的。
下面这张图,是我自己画的一个价差分析流程,你可以参考一下:
你看这个流程,其实很清晰。先算价差,再检验平稳性。如果平稳,直接进入策略设计。如果不平稳,也别急着放弃——可以试试协整检验,看看两个品种是否存在长期均衡关系。协整检验我们下一章会详细讲。
最后,总结一下本章的核心要点:
- 价差计算:推荐对数价差,它衡量的是相对价格关系,统计性质更稳定。
- 平稳性检验:ADF 检验是标配,p 值小于 0.05 才算平稳。
- 多方法验证:不要只依赖一个检验,结合 ACF 图和滚动统计量一起看。
- 频率匹配:在目标交易频率上做检验,别拿日线数据去指导分钟线策略。
嗯,价差分析这块,说白了就是「算出来,验一验」。算对了,验过了,后面的事情就顺了。如果这一步没做好,后面再怎么折腾也是白费力气。
公众号:蓝海资料掘金营,微信deep3321