4、价差计算与标准化:价差公式、Z-score标准化、布林带、阈值设定

好,我们进入第四章。价差计算与标准化。

说实话,很多做跨品种套利的新手,上来就盯着价格差看。但价格本身是有量纲的,不同品种的价格水平可能差十倍甚至百倍。你拿螺纹钢和热卷比,一个三千多,一个四千多,直接算差值,那波动幅度完全被高价品种牵着鼻子走。所以,标准化处理是套利策略的基石。

4.1 价差公式:从原始数据到核心信号

价差,说白了就是两个相关品种价格之间的差值或比值。我个人习惯用差值,因为逻辑直观,回测时也容易理解。

最基本的价差公式:

# 假设我们有两个价格序列
price_a = [100, 102, 101, 103, ...]
price_b = [98, 99, 100, 101, ...]

# 简单价差
spread = price_a - price_b

但这里有个坑。如果两个品种的合约乘数不同,比如一手螺纹钢是10吨,一手热卷也是10吨,那直接减没问题。可要是做豆粕和豆油呢?合约乘数不一样,持仓比例就得调整。我在项目中遇到过这种情况,当时没注意,回测曲线漂亮得很,实盘一跑就亏。后来才发现,是价差计算时没考虑合约乘数。

避坑指南: 我曾经因为忽略合约乘数,导致价差信号完全失真。记住,价差公式必须包含持仓比例因子。

带持仓比例的价差公式:

# 假设合约乘数:品种A为10,品种B为20
# 我们想构建一个市值中性的组合
ratio_a = 20  # 做多1手A,需要做空2手B来对冲
ratio_b = 10

# 调整后的价差
adjusted_spread = price_a * ratio_a - price_b * ratio_b

你想想看,如果不做这个调整,价差的波动范围会很大,而且没有经济意义。调整之后,价差就变成了一个围绕零轴波动的序列,方便我们后续做标准化。

4.2 Z-score标准化:让价差变得可比较

价差算出来了,但不同时间段的价差波动幅度可能不一样。牛市里价差波动大,熊市里波动小。直接用一个固定阈值去判断,肯定不行。

Z-score就是干这个的。它把价差序列转换成均值为0、标准差为1的标准正态分布。说白了,就是告诉你当前价差偏离均值多少个标准差。

Z-score公式:

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

嗯,这里要注意。均值和标准差的计算窗口很关键。用太短的窗口,信号太敏感,容易频繁交易;用太长的窗口,反应迟钝,抓不住机会。

我的经验: 我个人习惯用60期滚动窗口(日线就是60个交易日,约3个月)。这个长度既能捕捉到中期趋势,又不会对短期噪音过度反应。

Python实现:

import pandas as pd
import numpy as np

def calculate_zscore(spread, window=60):
    """
    计算价差的Z-score
    """
    rolling_mean = spread.rolling(window=window).mean()
    rolling_std = spread.rolling(window=window).std()
    z_score = (spread - rolling_mean) / rolling_std
    return z_score

# 示例
spread_series = pd.Series([...])  # 你的价差数据
z_scores = calculate_zscore(spread_series)

Z-score的好处是,它把不同时间段的价差都映射到同一个尺度上。这样我们就可以用统一的阈值来判断开仓和平仓了。

4.3 布林带:可视化价差的边界

布林带其实就是Z-score的图形化表达。它由三条线组成:中轨(均值)、上轨(均值+ k倍标准差)、下轨(均值- k倍标准差)。

为什么用布林带?因为直观。一眼就能看出当前价差处于什么位置。我个人习惯把k值设为2,也就是上下轨各偏离2个标准差。理论上,正态分布下,95%的数据会落在这个区间内。

布林带计算:

def bollinger_bands(spread, window=60, num_std=2):
    """
    计算布林带
    """
    rolling_mean = spread.rolling(window=window).mean()
    rolling_std = spread.rolling(window=window).std()
    upper_band = rolling_mean + num_std * rolling_std
    lower_band = rolling_mean - num_std * rolling_std
    return upper_band, rolling_mean, lower_band

upper, middle, lower = bollinger_bands(spread_series)
核心逻辑: 当价差触及上轨时,说明价差过高,应该做空价差(卖高买低);触及下轨时,说明价差过低,应该做多价差(买高卖低)。

但这里有个问题。布林带的上下轨是固定的吗?不是。随着新数据的加入,均值和标准差会动态更新,布林带也会跟着移动。所以,它是一个自适应系统。

4.4 阈值设定:从统计到交易决策

阈值,就是决定什么时候开仓、什么时候平仓的那个数字。Z-score给了我们一个标准化的信号,布林带给了我们可视化的边界,但具体怎么用,还得靠阈值。

常见的阈值设定方法:

阈值类型 典型值 说明
开仓阈值 Z-score > 2 或 < -2 价差偏离2个标准差以上时开仓
平仓阈值 Z-score 回归到 0 附近 价差回归均值时平仓
止损阈值 Z-score > 3 或 < -3 防止价差继续扩大造成巨大亏损

你可能会问,为什么开仓阈值是2,不是1.5或2.5?这其实取决于你的风险偏好和品种特性。我记得有一次做螺纹钢和热卷的套利,历史回测显示阈值设为1.8效果最好,但实盘时市场结构变了,阈值必须调到2.2才能避免频繁止损。

避坑指南: 我曾经直接套用教科书上的2倍标准差阈值,结果在某个品种上连续止损了5次。后来发现,那个品种的价差分布是尖峰厚尾的,2倍标准差根本不够用。所以,阈值一定要根据具体品种的历史数据来优化。

动态阈值调整:

# 基于历史分位数设定阈值
def dynamic_threshold(z_scores, percentile=95):
    """
    根据历史分位数动态设定阈值
    """
    upper_threshold = np.percentile(z_scores, percentile)
    lower_threshold = np.percentile(z_scores, 100 - percentile)
    return upper_threshold, lower_threshold

# 示例
upper_th, lower_th = dynamic_threshold(z_scores, percentile=95)

动态阈值的好处是,它能适应市场的变化。当波动率变大时,阈值会自动放宽;波动率变小时,阈值会收紧。这样就不会因为市场环境变化而频繁误判。

知识体系结构图

下面这张图,把价差计算到标准化再到阈值设定的整个流程串起来了。你可以把它当作一个检查清单,每次做策略时对照着走一遍。

价差计算与标准化流程 原始价格数据 价差计算(含合约乘数调整) Z-score标准化(滚动窗口) 布林带可视化(上下轨) 阈值设定(开仓/平仓/止损) Step 1 Step 2 Step 3 Step 4 Step 5

嗯,到这里,价差计算与标准化的核心内容就讲完了。记住,标准化不是目的,目的是让价差信号变得稳定、可比较。布林带和阈值只是工具,真正决定策略好坏的是你对品种特性的理解和对参数的调试。

最后提醒: 我个人建议,每次调整阈值后,一定要做样本外测试。别让回测的漂亮曲线骗了你。实盘才是最终的检验场。

专注资料整理