4. 价差计算与标准化:点差计算、对数价差、Z-score标准化

好,咱们进入正题。价差计算,说白了就是给两个品种“配对”。你想想看,螺纹钢和热卷,豆粕和菜粕,它们之间总有个合理价差。我的任务,就是把这个价差算出来,再把它变成能用的交易信号。

我个人习惯把价差计算分成三步走:原始价差 → 对数价差 → Z-score标准化。每一步都有它的道理,咱们一个一个说。

4.1 点差计算:最朴素的价差

点差,就是直接拿价格相减。简单粗暴,但有效。

# 点差计算示例
import pandas as pd
import numpy as np

# 假设我们有两份价格数据
price_a = pd.Series([3500, 3520, 3510, 3530, 3540])
price_b = pd.Series([3400, 3410, 3405, 3420, 3430])

# 直接相减
spread = price_a - price_b
print(spread)
# 输出: 100, 110, 105, 110, 110

嗯,这里要注意。点差有个大问题——它受价格水平影响。螺纹钢3000块的时候价差100,跟螺纹钢5000块的时候价差100,意义完全不一样。我在项目中遇到过这种情况:明明价差没变,但波动率变了,导致信号频繁出错。

避坑指南: 我曾经用点差直接做回测,结果发现2015年和2020年的价差阈值完全没法通用。原因很简单——价格中枢变了。所以,如果你做的是长周期策略,点差可能不太靠谱。

4.2 对数价差:解决价格水平问题

对数价差,就是取对数后再相减。公式很简单:ln(price_a) - ln(price_b)

为什么要用对数?说白了,它把绝对价差变成了相对价差。你想想看,3000块的螺纹钢和3100块的螺纹钢,价差100;6000块的时候价差200,其实比例是一样的。对数价差就能捕捉这种比例关系。

# 对数价差计算
log_spread = np.log(price_a) - np.log(price_b)
print(log_spread)
# 输出: 0.029, 0.032, 0.030, 0.032, 0.032

我建议做跨品种套利时,优先用对数价差。尤其是那些价格波动大的品种,比如原油和燃料油,对数价差能让你的策略更稳定。

小技巧: 对数价差还有一个好处——它天然具有正态分布的特性。这对后续的统计检验非常有利。我做过对比,用对数价差做Z-score,比用点差做Z-score,信号质量高出一截。

4.3 Z-score标准化:让价差变成交易信号

价差算出来了,但怎么用?直接看绝对值?不行。你得知道当前价差处于历史什么位置。Z-score就是干这个的。

公式:Z = (当前价差 - 均值) / 标准差

Z-score告诉你:当前价差偏离均值多少个标准差。一般我们设阈值±2,超过就认为价差异常,可以开仓。

# Z-score标准化
def zscore_calc(spread_series, window=20):
    mean = spread_series.rolling(window).mean()
    std = spread_series.rolling(window).std()
    zscore = (spread_series - mean) / std
    return zscore

# 假设我们有100天的对数价差数据
log_spread_series = pd.Series(np.random.randn(100) * 0.03 + 0.03)
zscore = zscore_calc(log_spread_series, window=20)

# 交易信号
zscore > 2  # 做空价差
zscore < -2 # 做多价差
核心要点: Z-score的窗口选择很关键。窗口太小,信号太多;窗口太大,反应太慢。我个人习惯用20天或60天,具体看品种的波动周期。比如螺纹钢和热卷,我用20天;豆粕和菜粕,我用60天。

4.4 完整流程:从数据到信号

咱们把整个流程串起来,看看实际怎么操作。

# 完整流程示例
def spread_analysis(price_a, price_b, window=20):
    # 1. 计算对数价差
    log_spread = np.log(price_a) - np.log(price_b)
    
    # 2. 计算Z-score
    mean = log_spread.rolling(window).mean()
    std = log_spread.rolling(window).std()
    zscore = (log_spread - mean) / std
    
    # 3. 生成信号
    signals = pd.DataFrame(index=price_a.index)
    signals['zscore'] = zscore
    signals['signal'] = 0
    signals.loc[zscore > 2, 'signal'] = -1  # 做空价差
    signals.loc[zscore < -2, 'signal'] = 1   # 做多价差
    
    return signals

你看,代码其实不复杂。但真正难的是参数调优。我做过一个项目,螺纹钢和热卷的价差,用20天窗口效果很好,但换到焦煤焦炭就不行了。为什么?因为两个品种的波动特性不一样。

经验之谈: 我建议你做一个滚动窗口测试。把窗口从10天到100天都跑一遍,看看哪个窗口的夏普比率最高。别偷懒,这一步值得花时间。

4.5 知识体系总览

下面这张图,把咱们刚才讲的内容串起来了。你可以看到,从原始价格到最终交易信号,每一步都有明确的目的。

价差计算与标准化流程 原始价格数据 Price_A, Price_B 点差计算 Spread = A - B 对数价差 ln(A) - ln(B) Z-score标准化 (当前 - 均值) / 标准差 交易信号 Z > 2 做空, Z < -2 做多 关键参数 • 窗口期:20-60天 • 阈值:±2 标准差 • 数据频率:日线/小时线 • 品种选择:相关性 > 0.8 • 回测周期:至少2年

这张图把整个流程讲得很清楚。从原始价格开始,经过点差计算、对数变换、Z-score标准化,最后生成交易信号。左侧的参数是咱们做策略时必须考虑的。

总结一下: 价差计算不是简单的数学运算,它背后是对市场逻辑的理解。点差适合短期、价格稳定的品种;对数价差适合长期、价格波动大的品种;Z-score则是把价差变成可执行的交易信号。三者缺一不可。

好了,这一章的内容就到这里。记住,工具是死的,市场是活的。多测试、多调整,才能找到最适合你策略的参数。

专注资料整理