7. 套利信号设计:基于Z-score的阈值开仓法,动态阈值与固定阈值对比

套利信号怎么设计?说白了,就是回答一个问题:价差偏离到什么程度,我们该动手?

我个人习惯用Z-score来做这个判断。为什么?因为它把不同量纲的价差序列,统一到了一个标准化的尺度上。你想想看,螺纹钢和热卷的价差,跟豆粕和菜粕的价差,数值范围完全不一样。但Z-score一算,大家就都在同一个坐标系里了。

7.1 Z-score的核心逻辑

Z-score的公式很简单:

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

它衡量的是:当前价差偏离均值多少个标准差。

举个例子。假设螺纹钢和热卷的价差均值是100,标准差是20。如果当前价差是140,那Z-score就是(140-100)/20 = 2.0。这意味着价差已经偏离均值2个标准差,属于比较极端的水平了。

我在项目中遇到过一个问题:直接用原始价差设阈值,比如价差超过150就开仓。结果行情一变,这个阈值就失效了。改用Z-score之后,阈值就稳定多了。

7.2 固定阈值法

固定阈值,就是设定一个固定的Z-score值作为开仓信号。比如:

  • 当Z-score > 2.0,开空头套利(价差过高,预期回归)
  • 当Z-score < -2.0,开多头套利(价差过低,预期回归)
  • 当Z-score回到0附近,平仓

代码实现也很直接:

import numpy as np
import pandas as pd

def fixed_zscore_signal(spread, window=20, threshold=2.0):
    """
    固定阈值Z-score信号生成
    """
    mean = spread.rolling(window).mean()
    std = spread.rolling(window).std()
    zscore = (spread - mean) / std
    
    signal = pd.Series(0, index=spread.index)
    signal[zscore > threshold] = -1  # 开空
    signal[zscore < -threshold] = 1  # 开多
    # 回归平仓
    signal[(zscore > -0.5) & (zscore < 0.5)] = 0
    
    return signal, zscore

固定阈值的好处是简单、直观、回测结果容易解释。但问题也很明显:市场在变,阈值不变

我曾经踩过这个坑:在2018年的螺纹钢热卷套利中,2.0的阈值表现很好。但到了2020年,市场波动率翻了一倍,2.0的阈值频繁触发,导致大量无效交易。后来我改成动态阈值,才解决了这个问题。

7.3 动态阈值法

动态阈值,就是让阈值随着市场波动率的变化而调整。核心思路是:波动大的时候,阈值放宽;波动小的时候,阈值收紧

具体做法有很多种,我常用的是基于滚动窗口的百分位数法:

def dynamic_zscore_signal(spread, window=20, percentile=95):
    """
    动态阈值Z-score信号生成
    阈值基于滚动窗口的百分位数动态调整
    """
    mean = spread.rolling(window).mean()
    std = spread.rolling(window).std()
    zscore = (spread - mean) / std
    
    # 动态计算阈值
    upper_threshold = zscore.rolling(window).quantile(percentile/100)
    lower_threshold = zscore.rolling(window).quantile(1 - percentile/100)
    
    signal = pd.Series(0, index=spread.index)
    signal[zscore > upper_threshold] = -1
    signal[zscore < lower_threshold] = 1
    signal[(zscore > -0.5) & (zscore < 0.5)] = 0
    
    return signal, zscore, upper_threshold, lower_threshold

还有一种更精细的做法,是用GARCH模型预测波动率,然后动态调整阈值。不过说实话,对于大多数商品套利,滚动百分位数法已经够用了。

7.4 两种方法的对比

对比维度 固定阈值 动态阈值
实现难度 低,一行代码搞定 中等,需要滚动计算
适应性 差,市场变了就失效 好,自动适应波动率变化
回测稳定性 参数敏感,容易过拟合 相对稳健,参数容忍度高
交易频率 波动大时频繁触发 自动调节,频率更合理
适用场景 波动率稳定的品种 波动率变化大的品种

我的建议:先用固定阈值做快速回测,验证策略逻辑是否成立。如果逻辑成立但回测结果不稳定,再切换到动态阈值。不要一上来就搞复杂的动态模型,容易陷入过度优化的陷阱。

7.5 核心逻辑流程图

下面这张图,把整个信号设计的流程串起来了:

Z-score套利信号设计流程 价差序列输入 计算Z-score 固定阈值判断 动态阈值判断 Z > 2.0 或 Z < -2.0 Z > 滚动95%分位 生成开仓/平仓信号

7.6 实际应用中的避坑指南

嗯,这里要注意几个容易翻车的地方:

  • 窗口长度的选择:窗口太短,Z-score会频繁跳动;窗口太长,对市场变化反应迟钝。我个人习惯用20-60个交易日,具体看品种的波动周期。
  • 极端值的处理:价差数据偶尔会出现极端值,比如交割月前的异常波动。我建议先做3倍标准差截断,再算Z-score。
  • 阈值不要对称:很多人的第一反应是上下阈值对称,比如±2.0。但实际中,价差的分布往往有偏。我曾经在豆粕菜粕套利中,发现上阈值2.5、下阈值1.8效果更好。

一个小技巧:在回测时,把固定阈值和动态阈值的结果放在一起对比。如果两者差异不大,说明市场波动率比较稳定,用固定阈值就够了。如果差异很大,那动态阈值大概率是更优的选择。

最后说一句:信号设计只是套利策略的第一步。信号再好,如果执行环节出了问题,照样赚不到钱。下一节我们会聊到仓位管理和风险控制,那才是真正决定盈亏的关键。


公众号:蓝海资料掘金营,微信deep3321