第4章:价差计算与标准化
各位同学,今天我们来聊聊配对交易里最核心的一环——价差计算与标准化。说实话,我见过不少新手,策略逻辑想得挺美,结果在价差处理上翻了车。嗯,这章咱们就把这块硬骨头啃下来。
4.1 价差序列的构建
配对交易的本质,就是赌两个品种的价差会回归均值。那价差怎么算?最直接的办法就是相减。
原始价差公式:
spread = P1 - hedge_ratio * P2
这里的 hedge_ratio 就是对冲系数。我习惯用线性回归去算,简单粗暴。但要注意,这个系数不是一成不变的。我在实盘里遇到过,某天两个品种的相关性突然变了,系数得重新校准。
构建步骤:
- 数据对齐:两个品种的时间戳必须一致。别小看这一步,不同交易所的 tick 数据经常错位。
- 计算对冲系数:用最近60个交易日的收盘价做 OLS 回归。
- 生成价差序列:用最新系数算出每天的价差。
重要提醒:价差序列必须是平稳的。如果不平稳,你做的配对交易就是伪回归,亏钱是大概率事件。
4.2 Z-Score 计算
价差算出来了,但不同品种的价差绝对值差异很大。比如螺纹钢和热卷的价差可能是几十块,而豆粕和菜粕的价差可能只有几块钱。怎么统一比较?
答案就是 Z-Score。说白了,就是把价差标准化,变成「偏离均值多少个标准差」。
Z-Score 公式:
z_score = (当前价差 - 均值) / 标准差
我个人习惯用滚动窗口来计算均值和标准差。窗口长度我一般选20天或60天。为什么?20天对应一个月的交易周期,60天对应一个季度。你想想看,太短的窗口噪音大,太长的窗口反应迟钝。
代码示例:
import pandas as pd
import numpy as np
def calc_zscore(spread, window=20):
mean = spread.rolling(window).mean()
std = spread.rolling(window).std()
z = (spread - mean) / std
return z
我的小技巧:如果数据量不够,可以用指数加权移动平均(EWMA)代替简单移动平均。这样近期的数据权重更大,反应更快。
4.3 阈值设定逻辑
Z-Score 算出来了,那什么时候开仓?什么时候平仓?这就涉及到阈值设定。
常见的阈值设定方法:
| 方法 | 阈值 | 适用场景 |
|---|---|---|
| 固定阈值 | ±2.0 或 ±1.5 | 历史回测表现稳定 |
| 动态阈值 | 基于历史分位数 | 市场波动剧烈 |
| 自适应阈值 | 根据波动率调整 | 多品种通用 |
我曾经犯过一个错误:固定用 ±2.0 做阈值,结果在2015年股灾期间,价差动不动就跑到 ±3.0 以上,频繁止损。后来我改用动态阈值,根据过去100天的 Z-Score 分位数来设定,效果好了很多。
动态阈值实现逻辑:
def dynamic_threshold(z_score, lookback=100, quantile=0.95):
upper = z_score.rolling(lookback).quantile(quantile)
lower = z_score.rolling(lookback).quantile(1 - quantile)
return upper, lower
避坑指南:我曾经在实盘里直接用历史全量数据算阈值,结果忽略了市场结构的变化。记住,阈值要定期更新,我一般每周重新校准一次。
4.4 知识体系图
下面这张图,是我自己总结的价差计算与标准化的完整流程。你把它存下来,做策略时对照着看,能少走很多弯路。
4.5 实战中的坑与经验
最后,我分享几个实战中踩过的坑:
- 数据频率不一致:我曾经用1分钟K线做配对,结果两个品种的成交量差异太大,价差噪音多到没法看。后来我改用15分钟K线,效果明显改善。
- 忽略交易成本:Z-Score 到了阈值就开仓?别急。你得算上手续费和滑点。我一般会在阈值基础上加一个缓冲带,比如阈值是 ±2.0,我实际在 ±2.2 才开仓。
- 过度优化:回测时把阈值调得特别精准,一到实盘就崩。记住,阈值不是越精确越好,留点余量。
我的习惯:每次调整阈值后,我都会用过去3个月的数据做一次快速回测。如果夏普比率低于1.5,我就重新调。这招帮我避开了不少坑。
好了,价差计算与标准化就讲到这里。记住,这套流程是配对交易的骨架,骨架正了,后面的策略才能站得稳。