第三章:价差序列构建——从原始数据到交易信号的桥梁

做统计套利的人都知道,价差才是真正的核心。不是价格,不是收益率,是价差。

我刚开始做高频套利那会儿,犯过一个低级错误——直接用两个品种的价格相减当价差。结果呢?回测曲线漂亮得不行,实盘一跑就崩。后来才明白,价差不是简单减一减就完事的。

这一章,我们就来聊聊价差到底该怎么算,怎么处理才能用。

3.1 价差计算方式

价差计算,说白了就是找到两个品种之间的「合理关系」。常用的方法有两种:回归残差法和比率法。

3.1.1 回归残差法

这个方法的核心思想是:假设两个品种的价格存在线性关系。我们用历史数据拟合出这个关系,然后看当前价格偏离了多少。

具体来说,就是做一元线性回归:

# Python 示例:回归残差法计算价差
import numpy as np
import statsmodels.api as sm

def calc_spread_ols(price_a, price_b):
    """
    用OLS回归计算价差
    price_a, price_b: 两个品种的价格序列
    """
    # 添加常数项
    X = sm.add_constant(price_b)
    model = sm.OLS(price_a, X).fit()
    
    # 回归系数
    beta = model.params[1]  # 斜率
    alpha = model.params[0] # 截距
    
    # 价差 = 实际价格 - 预测价格
    spread = price_a - (alpha + beta * price_b)
    
    return spread, alpha, beta

这里有个坑要注意。我在项目中遇到过,直接用原始价格做回归,结果残差序列非平稳,根本没法用。后来我养成了一个习惯:先做协整检验,再决定要不要用回归残差法

⚠️ 避坑指南

我曾经在螺纹钢和热卷的套利上吃过亏。两个品种明明是同产业链的,回归残差看起来也很漂亮,但实盘就是赚不到钱。后来发现,是因为样本期内有过一次大的政策冲击,导致回归系数发生了结构性变化。

所以,我建议:滚动窗口回归,别用全样本。窗口大小我一般取60-120个交易日,具体看品种的波动特性。

3.1.2 比率法

比率法就简单多了。直接算两个价格的比值或者对数比值。

# 比率法计算价差
def calc_spread_ratio(price_a, price_b):
    """
    比率法:直接计算价格比
    """
    # 价格比
    ratio = price_a / price_b
    
    # 或者用对数比(我更喜欢这个)
    log_ratio = np.log(price_a / price_b)
    
    return ratio, log_ratio

比率法有个好处——它天然消除了量纲的影响。你想想看,如果A的价格是100,B的价格是50,比值就是2。如果A涨到200,B涨到100,比值还是2。这比回归残差法稳定多了。

但比率法也有缺点。它假设两个品种的价格关系是成比例的,这在很多情况下并不成立。比如股指期货和现货之间,基差就不是一个固定的比例。

方法 优点 缺点 适用场景
回归残差法 能捕捉线性关系,灵活 对参数敏感,需协整检验 同产业链品种、期现套利
比率法 简单稳定,无量纲 假设过强,不适用非线性关系 同品种跨期、ETF套利

💡 我的经验

我个人习惯:先用比率法快速筛选,再用回归残差法精细建模。比率法算得快,适合做初筛。回归残差法精度高,适合做最终的交易信号。

3.2 价差标准化处理(Z-score)

算出了价差,还不能直接用。为什么?因为价差的数值大小没有意义,有意义的是它偏离均值的程度。

举个例子:螺纹钢和热卷的价差可能是50,而IF和IC的价差可能是500。这两个数字没法直接比较。我们需要一个统一的尺度——Z-score。

3.2.1 Z-score 的计算

def calc_zscore(spread, window=60):
    """
    计算价差的Z-score
    spread: 原始价差序列
    window: 滚动窗口大小
    """
    # 滚动均值
    mean = spread.rolling(window=window).mean()
    
    # 滚动标准差
    std = spread.rolling(window=window).std()
    
    # Z-score
    zscore = (spread - mean) / std
    
    return zscore

Z-score 的含义很直观:当前价差偏离均值多少个标准差。一般来说:

  • |Z-score| > 2:价差偏离较大,可以考虑开仓
  • |Z-score| > 3:价差严重偏离,大概率会回归
  • |Z-score| < 1:价差在正常范围内,观望

🔑 核心要点

Z-score 的窗口选择很关键。窗口太小,均值不稳定,信号太多假突破。窗口太大,均值反应迟钝,容易错过机会。

我一般这样选:高频数据用20-30个周期,日线数据用60-120个周期。具体还要看品种的均值回归速度。

3.2.2 标准化后的信号生成

def generate_signals(zscore, entry_threshold=2.0, exit_threshold=0.5):
    """
    根据Z-score生成交易信号
    """
    signals = np.zeros_like(zscore)
    
    # 开仓信号
    signals[zscore > entry_threshold] = -1  # 做空价差
    signals[zscore < -entry_threshold] = 1   # 做多价差
    
    # 平仓信号(回归到均值附近)
    signals[np.abs(zscore) < exit_threshold] = 0
    
    return signals

这里有个细节:开仓阈值和平仓阈值要分开。我见过很多人用同一个阈值,结果频繁开平仓,手续费都亏没了。

我个人习惯:开仓阈值设2.0,平仓阈值设0.5。这样既能捕捉到大的偏离,又不会太早平仓。

3.3 价差序列的预处理

在计算价差之前,数据预处理是必不可少的。我踩过的坑太多了,这里列几个关键点:

  1. 时间对齐:两个品种的交易时间可能不同,必须对齐到同一时间戳。我一般用最近邻插值。
  2. 异常值处理:涨跌停、停牌、数据错误,这些都会导致价差突变。我习惯用3倍标准差剔除。
  3. 缺失值填充:高频数据经常有缺失,我一般用前向填充,最多填充3个周期。

⚠️ 重要提醒

千万不要在价差计算完之后再做预处理!顺序很重要:先清洗数据,再算价差,最后标准化。顺序错了,结果全错。

3.4 本章知识体系

下面这张图总结了价差序列构建的完整流程:

价差序列构建流程 原始价格数据 数据预处理 回归残差法 比率法 Z-score 标准化 最终输出:标准化的价差序列(Z-score)

从这张图可以看得很清楚:价差序列构建不是一步到位的。先有数据,再预处理,然后选择计算方法,最后标准化。每一步都有讲究。

💡 实战建议

刚开始做统计套利的朋友,我建议先从比率法+Z-score开始。简单、稳定、不容易出错。等跑通了整个流程,再尝试回归残差法做精细化优化。

记住:在量化交易里,简单的东西往往更可靠


好了,价差序列构建的核心内容就这些。下一章我们会聊到协整检验——怎么判断两个品种的价差是否真的会回归。嗯,那是另一个有意思的话题。

蓝海数据掘金营 | 专注资料整理