3. 动态调整策略:均值回归模型、协整关系、卡尔曼滤波、Z-score阈值

各位同学,今天我们来聊聊动态调整策略。说实话,静态仓位管理就像开手动挡车——换挡时机全凭感觉。而动态调整,就是给套利系统装上了自动变速箱。我做了这么多年套利,最大的体会就是:市场在变,你的仓位必须跟着变

3.1 均值回归模型:套利的基本信仰

先问大家一个问题:为什么价差会回归?说白了,两个同产业链的品种,比如螺纹钢和热卷,它们的价格长期看是联动的。短期可能因为供需错配走偏,但最终会被套利者拉回来。这就是均值回归的底层逻辑。

我个人习惯用滚动窗口均值来建模。比如取过去60个交易日的价差均值,作为当前回归的锚点。代码很简单:

import pandas as pd
import numpy as np

def rolling_mean_spread(spread, window=60):
    return spread.rolling(window).mean()

嗯,这里要注意:窗口大小很关键。我在项目中遇到过,窗口太小(比如20天),模型太敏感,频繁交易;窗口太大(比如120天),反应迟钝,错过机会。一般我建议用60-90天,具体看品种的波动周期。

实战技巧: 如果价差在均值附近震荡,说明模型有效。如果价差长期偏离不回,那就要怀疑协整关系是否破裂了。

3.2 协整关系:别被伪回归骗了

很多新手犯的错误:看到两个品种走势像,就以为能套利。其实那可能是伪回归。我刚开始做套利时也吃过这个亏——看着豆粕和菜粕走势几乎一样,结果一开仓就亏。后来才明白,必须用协整检验来验证。

协整的核心思想:两个非平稳序列,它们的线性组合是平稳的。说白了,就是它们虽然各自乱跑,但距离是稳定的。我用的是Engle-Granger两步法

from statsmodels.tsa.stattools import coint

def check_coint(y1, y2):
    score, pvalue, _ = coint(y1, y2)
    return pvalue < 0.05  # 显著则存在协整关系

我曾经踩过一个坑:用日线数据检验协整,结果很显著。但换到小时线,完全不成立。为什么?因为协整关系有时效性。不同时间频率下,品种间的联动强度不一样。我建议至少用两种频率验证,比如日线和小时线都通过,才放心。

避坑指南: 协整关系不是永恒的。我见过一个案例,两个品种协整了两年,突然某天就断了。所以建议每周重新检验一次,一旦p值超过0.05,立即平仓。

3.3 卡尔曼滤波:让模型自己学习

均值回归模型有个硬伤:它假设回归参数是固定的。但市场会变啊!比如螺纹钢和热卷的价差均值,去年是100,今年可能变成150。这时候卡尔曼滤波就派上用场了。

卡尔曼滤波说白了,就是让模型动态更新参数。它有两个核心步骤:预测和更新。预测是根据历史估计当前状态,更新是用新数据修正估计。我画了个流程图,帮大家理解:

初始状态估计 预测步骤 更新步骤 输出新估计 反馈循环

代码实现也不复杂,我用的是pykalman库:

from pykalman import KalmanFilter

def kalman_spread(spread):
    kf = KalmanFilter(
        transition_matrices=[1],
        observation_matrices=[1],
        initial_state_mean=spread[0],
        initial_state_covariance=1,
        observation_covariance=1,
        transition_covariance=0.01
    )
    state_means, _ = kf.filter(spread.values)
    return state_means.flatten()

这里有个参数很关键:transition_covariance。它控制模型对变化的敏感度。设大了,模型反应快但噪声多;设小了,平滑但滞后。我一般先设0.01,然后根据回测结果调整。

核心要点: 卡尔曼滤波不是万能药。它适合参数缓慢变化的市场,如果市场突然断裂(比如政策突变),它也会失效。所以还是要结合协整检验一起用。

3.4 Z-score阈值:什么时候开仓平仓

有了动态的价差均值,接下来就是确定交易信号。我用的就是Z-score。公式很简单:

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

Z-score告诉你当前价差偏离了多少个标准差。我一般设两个阈值:

Z-score范围 操作 仓位比例
|Z| < 1.0 不操作 0%
1.0 ≤ |Z| < 2.0 开仓 50%
2.0 ≤ |Z| < 3.0 加仓 100%
|Z| ≥ 3.0 平仓(止损) 0%

你想想看,为什么设三档?因为市场极端情况很少见,如果Z-score到3以上还持仓,大概率是协整关系破裂了。我吃过这个亏——有一次Z-score到了3.5,我还加仓,结果价差继续扩大,亏了不少。后来我加了硬止损:Z-score超过3.0,无条件平仓。

def zscore_signal(spread, mean, std, z_entry=1.0, z_add=2.0, z_stop=3.0):
    z = (spread - mean) / std
    if abs(z) >= z_stop:
        return 'close'
    elif abs(z) >= z_add:
        return 'add'
    elif abs(z) >= z_entry:
        return 'open'
    else:
        return 'hold'
个人经验: 阈值不是固定的。波动率高的品种(比如原油),我建议用1.5和2.5;波动率低的品种(比如国债),用0.8和1.8。先跑回测,找到最优参数。

3.5 实战中的动态调整流程

好了,我们把前面讲的串起来。一个完整的动态调整流程是这样的:

  1. 每天开盘前:重新检验协整关系。如果p值大于0.05,清仓休息。
  2. 计算动态均值:用卡尔曼滤波或滚动窗口,得到当前价差的合理中枢。
  3. 计算Z-score:用动态均值和动态标准差,判断当前偏离程度。
  4. 调整仓位:根据Z-score阈值,决定开仓、加仓、减仓或平仓。
  5. 收盘后复盘:检查模型参数是否需要调整,比如卡尔曼滤波的transition_covariance。

我建议把这个流程写成自动化脚本,每天定时运行。手动操作太容易出错了——我刚开始做的时候,有次忘了更新协整检验,结果持仓过夜,第二天价差崩了。

重要提醒: 动态调整不是越频繁越好。我见过有人用分钟级数据做卡尔曼滤波,结果被噪声折磨得死去活来。建议至少用小时线或日线,给模型一点反应时间。

最后说一句:这套策略不是稳赚的。它适合震荡市,在趋势行情里会亏钱。所以一定要结合市场环境判断。嗯,今天就到这里,大家回去可以拿历史数据跑一跑,看看效果。


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