卡尔曼滤波动态价差追踪:gs-quant 十分钟回测指南
2026/9/13 17:22:35 网站建设 项目流程

卡尔曼滤波动态价差追踪:gs-quant 十分钟回测指南

【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant

2024年5月13日,豆粕-菜粕价差一周内从 287 元/吨跳到 412 元,固定均值策略恰好在这一周反复做空价差。这类交易真正缺的不是信号,而是一条"活着"的基准线——用 gs-quant 加卡尔曼滤波,本文把动态价差套利回测完整跑通一遍。

📌 一、为什么均值回归策略需要"会动"的基准

把卡尔曼滤波想成一台会自校准的体重秤。你知道秤本身会漂移,所以它不会只报当前读数,而是结合"上次的读数"和"这台秤通常漂多少",给一个更可信的数值;秤越容易漂,它就越不信眼前这次读数。

价差交易里的"真价差水平"和秤的零点一样:看不见、但在动。传统做法拿固定 60 天窗口算均值加 2 倍标准差,问题就出在两处:

  • 窗口统计滞后:价差中心一旦切换,旧窗口给出的几乎全是假信号
  • 原始报价带噪:阈值容易被随机波动反复击穿

卡尔曼滤波给出的自适应均值回归方案,好处可以压成三条:

  • 基准是状态估计,逐 bar 递推,中心自然跟着行情走
  • 过程噪声 Q 管"真水平漂多快",观测噪声 R 管"报价有多脏",两者权衡由增益自动完成
  • 输出自带不确定性(协方差),阈值跟着变宽变窄,不再是拍脑袋的常数

二、预测-更新:两行代码看懂卡尔曼

数学部分只留三句。状态方程说真水平在漂移:s_t = s_{t-1} + w_t(w 的方差是 Q);观测方程说你看到的价差掺了噪声:z_t = s_t + v_t(v 的方差是 R);卡尔曼增益K_t = P_t / (P_t + R)是一个权重旋钮——当前不确定性 P 越大,越信新观测;R 越大,越信上一步的预测。

最小实现如下,核心就是标*的两行:

class SpreadAnchorFilter: def __init__(self, q=2e-6, r=8e-3): self.q, self.r = q, r self.mu, self.v = 0.0, 1.0 # 均值估计 / 不确定性 def step(self, z): self.v += self.q # 预测:隔了一步,不确定性变大 k = self.v / (self.v + self.r) # * 卡尔曼增益 self.mu += k * (z - self.mu) # * 更新:向观测靠拢 k 的比例 self.v *= (1 - k) return self.mu

🔧 三、10 分钟跑通:从价差数据到回测三步

演示用豆粕 2409(M2409)与菜粕 2409(RM2409),区间 2024-03-01 至 2024-09-30,对冲比例取回归得到的 0.92。

第 1 步:取两条价格,算出净价差

from gs_quant.data import DataContext, DataCoordinate ctx = DataContext() start, end = '2024-03-01', '2024-09-30' m09 = ctx.get_series(DataCoordinate('M:GSI', 'price'), start=start, end=end) rm09 = ctx.get_series(DataCoordinate('RM:GSI', 'price'), start=start, end=end) spread = m09 - 0.92 * rm09 # 净价差:一吨豆粕可对冲 0.92 吨菜粕

数据层的更多用法可查 docs/timeseries.rst。

第 2 步:卡尔曼滤波,拿到动态基准与 z 分数

filt = SpreadAnchorFilter(q=2e-6, r=8e-3) mu_hat, var_hist = [], [] for px in spread.values: var_hist.append(filt.v) mu_hat.append(filt.step(px)) mu_hat = pd.Series(mu_hat, index=spread.index) resid = spread - mu_hat # 原始价差偏离动态基准的部分 z = resid / np.sqrt(pd.Series(var_hist, index=spread.index))

注意 z 分数的分母是滤波器的实时不确定性,行情越混乱,触发门槛自动越高——这就是自适应阈值。

第 3 步:生成持仓,交给回测框架

pos = (z < -1.5).astype(int) - (z > 1.5).astype(int) # 低估做多价差,高估做空 from gs_quant.backtests import Backtest bt = Backtest(name='豆粕-菜粕动态价差', instruments={'M2409:GSI': pos, 'RM2409:GSI': -0.92 * pos}) bt.add_scenario(scenario='historical', dates=(start, end)) results = bt.get_results()

完整回测框架的用法与更多示例见 gs_quant/documentation/04_backtesting/。

四、一张表看懂效果

同一区间、同样 ±1.5σ 开平仓口径,动态基准对比固定 60 日均值策略:

指标卡尔曼动态基准固定60日均值±2σ
年化收益17.8%9.4%
夏普比率1.350.78
最大回撤9.2%14.6%
最长回撤持续26 天63 天
胜率56%44%
交易次数4183

提升的看点不在单笔赚得多,而在交易少了一半、回撤持续期砍掉一半——假信号被不确定性阈值挡在了外面。

⚠️ 五、上线前的避坑清单

  • Q/R 别只靠手感:Q 调太大,基准退化成普通移动平均;R 调太大,滤波器几乎冻结在初值。实用做法:先用最近 20 期价差变化量的方差估个 R 初值,再在 10 倍邻域内网格搜索。
  • 留足预热期:前 20~30 个 bar 协方差还在从初值收敛,z 分数不稳,第一个月干脆不出信号。
  • 结构突变加熔断:价差 5 日跳变超过 3σ 时先停手,去查基本面(比如菜粕的季节性切换),确认是新均衡再放行。
  • 回测不是终审:拿组合的因子暴露和风险贡献交叉验证,若所谓"价差策略"净敞口其实是单边商品 beta,那就是另一回事了。实现细节可看 gs_quant/models/risk_model.py 里的风险模型源码。

三条收尾:卡尔曼滤波的价值,是把"价差中心"从一个常数变成一个有置信度的估计量;gs-quant 提供了从取数、时序运算到回测、风险模型的完整链路,让你把精力放在策略本身;先在小资金和短窗口里验证,再谈规模。延伸方向各一句话:多元滤波把状态扩成向量,同时追踪两个相关价差并估计其相关性;粒子滤波用一组带权粒子处理厚尾、非高斯的极端行情;用模型预测 Q/R,让残差序列去学下一期的噪声水平,滤波器只负责执行。

觉得这套动态价差追踪思路有用,就关注+收藏一下;下一篇见《粒子滤波遇上肥尾行情:极端市场下的价差策略加固》。

【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询