4. 价差计算:基础价差公式、对数价差、归一化处理与Z-score
价差计算,说白了就是给套利交易找个「锚」。
你想想看,两个市场之间到底有没有套利机会?不是看价格本身,而是看价差。我做了这么多年高频交易,见过太多人一上来就盯着价格看,结果被市场来回打脸。价差才是真正的信号。
4.1 基础价差公式
最朴素的价差,就是两个市场价格的差值。公式很简单:
价差 = P₁ - P₂
其中 P₁ 和 P₂ 分别是两个市场的价格。
举个例子,比特币在 Binance 上卖 50000 USDT,在 OKX 上卖 49980 USDT,那价差就是 20 USDT。这个差值就是你的潜在利润空间。
但这里有个坑。我刚开始做跨市场套利时,直接拿这个公式跑回测,结果发现信号频繁触发,但实盘一跑就亏钱。为什么?因为基础价差没有考虑价格尺度的问题。
4.2 对数价差
为了解决上面那个问题,我建议用对数价差。公式长这样:
对数价差 = ln(P₁) - ln(P₂) = ln(P₁ / P₂)
对数价差的好处是,它天然就是「相对价差」。不管价格是 100 还是 10000,对数价差都能反映真实的偏离程度。
我记得有一次做股指期货的跨期套利,近月合约和远月合约的价格差了好几个数量级。用基础价差根本没法看,换成对数价差后,信号一下子就清晰了。
4.3 归一化处理
对数价差虽然解决了尺度问题,但不同品种的价差波动范围还是不一样。比如黄金的价差波动可能是 0.001,而比特币的价差波动可能是 0.1。你没法用一个统一的阈值去判断。
这时候就需要归一化。我个人习惯用 Min-Max 归一化:
归一化价差 = (当前价差 - 最小价差) / (最大价差 - 最小价差)
这样价差就被压缩到 [0, 1] 区间内。0 表示历史最低,1 表示历史最高。
但这里有个问题——你用什么窗口去算最小值和最大值?
我踩过这个坑。曾经用全量历史数据算,结果窗口太长,归一化后的价差几乎不动,信号完全失效。后来改成滚动窗口,比如过去 100 个 tick,效果就好多了。
4.4 Z-score
Z-score 是我最常用的方法。它本质上也是一种归一化,但用的是均值和标准差:
Z-score = (当前价差 - 均值) / 标准差
Z-score 告诉你当前价差偏离均值多少个标准差。通常 Z-score 超过 2 或低于 -2,就认为存在套利机会。
为什么是 2?因为正态分布下,Z-score 在 [-2, 2] 区间内的概率大约是 95%。超出这个范围,就属于「小概率事件」,大概率会回归。
嗯,这里要注意——价差分布不一定服从正态分布。我见过很多品种的价差分布是尖峰厚尾的,这时候用 2 作为阈值就不太合适。我一般会先做一下分布检验,然后根据实际情况调整阈值。
import numpy as np
def calculate_zscore(spread, window=100):
mean = np.mean(spread[-window:])
std = np.std(spread[-window:])
if std == 0:
return 0
return (spread[-1] - mean) / std
这段代码很简单,但实盘里要注意几个细节:
- 窗口大小要跟你的交易频率匹配。高频交易用 50-100,中低频用 200-500。
- 标准差为 0 时要处理,否则会除零报错。
- Z-score 要实时更新,不能复用旧值。
4.5 四种方法的对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 基础价差 | 简单直观,计算快 | 对价格尺度敏感 | 价格水平稳定的品种 |
| 对数价差 | 消除尺度影响,反映相对偏离 | 计算稍复杂 | 价格波动大的品种 |
| 归一化处理 | 统一到 [0,1] 区间,便于比较 | 依赖窗口选择 | 多品种统一信号 |
| Z-score | 统计意义明确,阈值通用 | 假设分布正态 | 均值回归策略 |
4.6 知识体系结构图
下面这张图把四种方法的关系梳理清楚了:
我个人在实际项目中,通常先用对数价差做基础计算,然后套上 Z-score 做信号判断。归一化处理更多用在多品种信号对比的场景。基础价差嘛,偶尔用来做快速筛查,但不会作为主要信号。
好了,价差计算这块就讲到这里。四种方法各有千秋,关键是要理解它们的数学含义和适用场景。下一节我们会把这些价差信号用到实际的套利策略中。