3. 价差计算与标准化:价差公式、Z-score、滚动窗口标准化

好,咱们进入第三个核心环节。价差计算与标准化。

说实话,很多新手做跨品种套利,上来就盯着价格差看。价格差涨了,做空;价格跌了,做多。结果呢?亏得莫名其妙。为什么?因为价格本身是有趋势的,两个品种的价格水平可能差好几倍,直接相减得到的价差,根本没法用。

我早年做螺纹钢和热卷的套利时,就吃过这个亏。螺纹钢每吨3000多,热卷每吨3200多,价差200块。后来螺纹涨到5000,热卷涨到5200,价差还是200。但你能说套利机会一样吗?显然不能。因为波动率变了,绝对价差的含义完全变了。

所以,价差必须标准化。今天咱们就聊透这件事。

3.1 价差公式:从原始价差到对数价差

先看最基础的价差公式。

假设你有两个品种,价格序列分别是 P1P2。最简单的价差就是直接相减:

spread = P1 - P2

这个公式在什么情况下能用?我个人的经验是:只有当两个品种的价格水平长期稳定在同一个数量级,且波动率相对稳定时,才勉强能用。比如同一交易所的同一板块品种,像豆粕和菜粕,价格都在3000-4000之间晃悠,直接相减问题不大。

但更多时候,咱们需要的是对数价差:

spread = log(P1) - log(P2) = log(P1 / P2)

为什么用对数?说白了,对数价差衡量的是两个品种的相对比例关系,而不是绝对差值。你想想看,P1=100,P2=90,价差是10;P1=1000,P2=990,价差还是10。但这两个场景的套利意义完全不同。对数价差就能消除这种量纲影响。

我在做股指期货和ETF套利时,一直用对数价差。因为IF合约和ETF的价格水平差很多,直接相减根本没法看。用对数价差后,信号稳定多了。

核心结论: 跨品种套利,优先使用对数价差。除非两个品种价格水平长期一致,否则别用原始价差。

3.2 Z-score:让价差变得可比较

好,价差算出来了。但问题又来了:不同时间段的价差,波动幅度不一样。怎么判断当前价差是偏高还是偏低?

答案是Z-score。

Z-score的公式很简单:

z = (当前价差 - 价差均值) / 价差标准差

它告诉你:当前价差偏离均值多少个标准差。z=2,意味着当前价差比均值高两个标准差,属于极端情况。z=-1.5,意味着比均值低1.5个标准差。

我一般用±2作为阈值。z>2,做空价差;z<-2,做多价差。当然,这个阈值可以调,后面讲参数优化时会细说。

但这里有个坑——均值和标准差怎么算?

如果你用全量历史数据算,那均值和标准差是固定的。但市场在变,固定参数很快会失效。我见过有人用过去5年的数据算Z-score,结果最近一年市场结构变了,Z-score天天在±3以上晃悠,信号完全乱套。

所以,咱们需要滚动窗口。

小技巧: Z-score本身没有单位,所以不同品种、不同时间段的Z-score可以直接比较。这是它最大的优势。

3.3 滚动窗口标准化:让参数自适应

滚动窗口标准化,说白了就是:只取最近N个数据点,计算均值和标准差,然后算Z-score。

公式长这样:

窗口内均值 = mean(spread[-N:])
窗口内标准差 = std(spread[-N:])
滚动Z-score = (当前价差 - 窗口内均值) / 窗口内标准差

N怎么选?我个人的经验是:

  • 短线套利(日内或隔夜): N=20到60,相当于1-3个月的数据
  • 中线套利(几周到几个月): N=60到120,相当于3-6个月
  • 长线套利(半年以上): N=120到250,相当于半年到一年

我曾经在原油和燃料油的套利中,试过N=10。结果呢?窗口太小,均值和标准差跟着价格乱跳,Z-score频繁触发信号,手续费都亏光了。后来改成N=60,信号稳定多了。

嗯,这里要注意:滚动窗口的N值,本质上是一个平滑参数。N越小,对市场变化反应越快,但噪声也越大。N越大,信号越平滑,但可能滞后。

下面这张图,展示了滚动窗口标准化的核心逻辑:

滚动窗口标准化流程图 原始价格序列 对数价差计算 滚动窗口 窗口内均值计算 窗口内标准差计算 Z-score = (价差 - 均值) / 标准差 标准化价差信号 核心逻辑:用最近N个数据点的统计特征,对当前价差进行标准化

3.4 实战中的避坑指南

讲几个我踩过的坑。

坑一:窗口内数据不足。 刚启动策略时,滚动窗口里可能只有几个数据点。这时候算出来的均值和标准差极不稳定。我建议:窗口数据少于N/2时,不产生交易信号。

坑二:极端值污染。 如果窗口内出现一个极端价差(比如数据错误或突发事件),它会拉高标准差,导致后续的Z-score偏小,信号被压制。我曾经在橡胶和20号胶的套利中遇到过,一个异常数据让Z-score连续一周没出信号。解决办法:对窗口内的价差做截尾处理,去掉最大和最小的5%。

坑三:参数过拟合。 滚动窗口的N值,不要用历史数据来回测到最优。因为最优N值往往是对历史噪声的过拟合。我一般用N=60作为默认值,然后根据品种波动特性微调,不会为了追求回测收益去死磕参数。

重要提醒: 滚动窗口标准化不是万能的。如果两个品种的价差出现了结构性变化(比如政策调整、合约规则变更),滚动窗口需要重新初始化。否则,旧窗口里的数据会持续污染新信号。

3.5 代码实现示例

最后,给一段Python代码。这是我在实盘中用过的简化版:

import numpy as np
import pandas as pd

def rolling_zscore(spread, window=60):
    """
    滚动窗口Z-score计算
    spread: 价差序列(建议用对数价差)
    window: 滚动窗口大小
    """
    # 计算滚动均值和标准差
    rolling_mean = spread.rolling(window=window, min_periods=window//2).mean()
    rolling_std = spread.rolling(window=window, min_periods=window//2).std()
    
    # 计算Z-score
    zscore = (spread - rolling_mean) / rolling_std
    
    # 数据不足时不产生信号
    zscore[:window//2] = np.nan
    
    return zscore

# 使用示例
# spread = log(P1) - log(P2)
# z = rolling_zscore(spread, window=60)
# 当z > 2时,做空价差;当z < -2时,做多价差

这段代码里,我用了 min_periods=window//2,意思是窗口数据至少达到一半才开始计算。这就是刚才说的避坑措施。

好了,价差计算与标准化就聊到这儿。核心就三句话:用对数价差、用Z-score、用滚动窗口。把这三点吃透了,跨品种套利的信号质量能上一个台阶。