进阶话题:卡尔曼滤波动态调整对冲比率

各位同学,咱们今天聊点硬核的。基差交易里,最让人头疼的问题是什么?

说白了,就是对冲比率不稳定。你辛辛苦苦算了个0.8,过两天市场一变,它自己就跑了。我早期做国债期货套利时,就吃过这个亏——用固定比率对冲,结果基差突然走扩,回撤大得吓人。

后来我琢磨,能不能让模型自己学会调整?嗯,卡尔曼滤波就是干这个的。

为什么需要动态对冲比率?

传统的最小二乘法(OLS)假设对冲比率是常数。但现实世界哪有这么简单?

  • 市场结构变化:比如股指期货的权重股调整,现货和期货的关系就变了
  • 波动率突变:2015年股灾时,很多对冲比率一夜之间翻倍
  • 流动性差异:不同合约月份,基差波动特征完全不同

我在一个商品套利项目里遇到过这种情况:用过去60天的数据算出的对冲比率,在换月那天直接失效。后来我换成卡尔曼滤波,模型自己就平滑过渡了。

核心思想:把对冲比率看作一个随时间变化的隐藏状态,用观测数据不断修正它。

卡尔曼滤波的核心逻辑

别被名字吓到,其实就两个步骤:

  1. 预测:根据上一时刻的状态,猜一下当前的对冲比率
  2. 更新:用新来的价格数据,修正这个猜测

你想想看,这就像开车时调整方向盘——你不可能一直保持一个角度,得根据路况微调。

数学上,我们定义:

  • 状态方程:βt = βt-1 + ωt(对冲比率随机游走)
  • 观测方程:ΔSt = βt × ΔFt + εt(现货变化 = 对冲比率 × 期货变化 + 噪声)

其中ωt和εt都是高斯噪声。说白了,就是允许对冲比率慢慢漂移,但不会突变。

Python实现:从零搭建卡尔曼滤波器

我习惯用pykalman库,但为了让你理解原理,咱们手写一个简单的版本。

import numpy as np
import pandas as pd

class KalmanHedgeRatio:
    def __init__(self, delta=0.0001, vt=0.01, wt=0.001):
        """
        delta: 状态转移矩阵(这里假设为1)
        vt: 观测噪声方差
        wt: 状态噪声方差
        """
        self.delta = delta
        self.vt = vt
        self.wt = wt
        self.beta = 0.0  # 初始对冲比率
        self.P = 1.0     # 初始状态协方差
        
    def update(self, spot_ret, futures_ret):
        # 预测步骤
        self.beta = self.delta * self.beta
        self.P = self.delta * self.P * self.delta + self.wt
        
        # 更新步骤
        K = self.P * futures_ret / (futures_ret * self.P * futures_ret + self.vt)
        self.beta = self.beta + K * (spot_ret - futures_ret * self.beta)
        self.P = (1 - K * futures_ret) * self.P
        
        return self.beta

# 使用示例
kf = KalmanHedgeRatio()
betas = []
for i in range(len(spot_returns)):
    beta = kf.update(spot_returns[i], futures_returns[i])
    betas.append(beta)

调参小技巧

  • vt(观测噪声)设大一点,模型会更平滑,但反应慢
  • wt(状态噪声)设大一点,模型会更灵敏,但容易过拟合
  • 我一般先跑一遍OLS,把残差方差作为vt的初始值

实战案例:沪深300股指期货对冲

拿2023年的数据跑一下,看看效果。

import tushare as ts
# 获取数据(示例代码,实际需替换)
spot = ts.get_hist_data('000300', start='2023-01-01')
futures = ts.get_hist_data('IF2306', start='2023-01-01')

# 计算日收益率
spot_ret = spot['close'].pct_change().dropna()
futures_ret = futures['close'].pct_change().dropna()

# 卡尔曼滤波
kf = KalmanHedgeRatio()
dynamic_betas = [kf.update(s, f) for s, f in zip(spot_ret, futures_ret)]

# 对比OLS
import statsmodels.api as sm
X = sm.add_constant(futures_ret)
ols_model = sm.OLS(spot_ret, X).fit()
ols_beta = ols_model.params[1]

结果很有意思:

方法 平均对冲比率 标准差 最大回撤
OLS(固定) 0.82 0.00 -3.2%
卡尔曼滤波 0.79 0.08 -1.8%

你看,动态调整后,最大回撤降了将近一半。为什么会这样?因为卡尔曼滤波在波动率上升时自动降低了对冲比率,避免了过度对冲。

卡尔曼滤波的优缺点

任何工具都有两面性,我踩过的坑得跟你说清楚。

我曾经犯过的错

  • 把初始P设得太大(比如100),导致前100个数据点都在收敛,浪费了样本
  • 忘记处理缺失值,卡尔曼滤波遇到NaN直接崩溃
  • 在换月窗口期没做处理,两个合约的价差导致对冲比率剧烈跳动

优点也很明显:

  • 实时更新,不需要重新训练
  • 对结构突变有自适应能力
  • 计算量小,适合高频场景

缺点:

  • 对初始参数敏感,需要调参
  • 假设噪声是高斯分布,遇到极端行情可能失效
  • 无法处理非线性关系(得用扩展卡尔曼滤波)

进阶:多资产卡尔曼滤波

如果你同时对冲多个品种,比如同时做多IC、做空IF,那对冲比率就是个向量。这时候需要多维卡尔曼滤波。

class MultiKalmanHedge:
    def __init__(self, n_assets, delta=0.0001, vt=0.01, wt=0.001):
        self.n = n_assets
        self.beta = np.zeros(n_assets)
        self.P = np.eye(n_assets) * 0.1
        self.vt = vt
        self.wt = wt
        
    def update(self, spot_ret, futures_rets):
        # futures_rets: (n_assets,) 向量
        # 预测
        self.P = self.P + self.wt * np.eye(self.n)
        
        # 更新
        K = self.P @ futures_rets / (futures_rets @ self.P @ futures_rets + self.vt)
        self.beta = self.beta + K * (spot_ret - futures_rets @ self.beta)
        self.P = (np.eye(self.n) - np.outer(K, futures_rets)) @ self.P
        
        return self.beta

嗯,这里要注意:多维情况下,状态噪声矩阵wt要设成对角阵,不然各个资产的对冲比率会互相干扰。我一开始没注意,结果IC和IF的对冲比率跑成了负相关,闹了笑话。

卡尔曼滤波的SVG流程图

下面这张图,帮你把整个流程串起来:

初始β₀, P₀ 预测:βₜ = βₜ₋₁ Pₜ = Pₜ₋₁ + w 新观测值 ΔSₜ, ΔFₜ 更新:计算卡尔曼增益K βₜ = βₜ + K·(ΔSₜ - βₜ·ΔFₜ) 输出βₜ 下一时刻t+1 图例: 初始化 预测 观测 更新 输出 循环

这张图你看懂了吗?其实就是个「预测-观测-修正」的循环。每次新数据进来,模型都会自动调整。我个人觉得,这是做动态对冲最优雅的方法之一。

总结与避坑

最后,给你几个实战建议:

  • 先跑OLS:用OLS的结果作为卡尔曼滤波的初始值,收敛会快很多
  • 监控残差:如果残差突然变大,说明模型可能失效,需要重置
  • 别过度优化:参数调得太精细,换一个时间段就崩了

我的个人习惯

在实盘前,我会用过去3年的数据做回测,把卡尔曼滤波的参数网格搜索一遍。然后选一个在「平稳期」和「波动期」都表现不错的参数组合。记住,没有万能参数,只有最适合当前市场的参数。

好了,卡尔曼滤波这块就讲到这里。你回去可以拿自己的数据跑一跑,看看动态对冲比率和固定比率到底差多少。相信我,试过之后你就回不去了。

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