3. 统计套利中的卡尔曼滤波动态调整

任务目标

在统计套利中,价差的均值和方差往往随时间漂移,固定阈值法会因参数失配而产生错误信号。本案例使用卡尔曼滤波对价差的时变均值和方差进行在线估计,并基于滤波结果动态生成交易信号,使策略能自适应市场结构变化。

实现思路

将价差序列建模为随机游走加噪声:spread_t = spread_{t-1} + w_t,观测方程 y_t = spread_t + v_t。卡尔曼滤波递归更新状态(价差水平)及其方差,滤波方差即价差方差的实时估计。交易信号基于标准化价差 z = (y_t - state_t) / sqrt(var_t) 生成:|z| > 2 开仓,回归至 |z| < 0.5 平仓。

完整代码

import numpy as np
import pandas as pd

def kalman_filter_spread(spread, delta=1e-4, r=1e-2):
    """
    卡尔曼滤波估计时变价差均值与方差
    状态: 价差水平 x
    观测: y = x + v, v~N(0,r)
    状态转移: x_t = x_{t-1} + w, w~N(0,delta)
    """
    n = len(spread)
    x = np.zeros(n)      # 滤波均值
    P = np.zeros(n)      # 滤波方差
    x[0] = spread[0]
    P[0] = r

    for t in range(1, n):
        # 预测
        x_pred = x[t-1]
        P_pred = P[t-1] + delta

        # 更新
        K = P_pred / (P_pred + r)
        x[t] = x_pred + K * (spread[t] - x_pred)
        P[t] = (1 - K) * P_pred

    return x, P

def generate_signals(spread, entry_thresh=2.0, exit_thresh=0.5):
    """基于卡尔曼滤波的标准化价差生成交易信号"""
    x, P = kalman_filter_spread(spread)
    std = np.sqrt(P)
    z = (spread - x) / std

    signals = np.zeros(len(spread))
    position = 0  # 0:空仓, 1:多头, -1:空头

    for t in range(len(spread)):
        if position == 0:
            if z[t] < -entry_thresh:
                position = 1    # 价差低于均值2倍标准差,做多价差
            elif z[t] > entry_thresh:
                position = -1   # 价差高于均值2倍标准差,做空价差
        else:
            if abs(z[t]) < exit_thresh:
                position = 0    # 回归均值附近,平仓
        signals[t] = position

    return signals, z, x, P

# ===== 最小可复现示例 =====
if __name__ == "__main__":
    # 构造带漂移的模拟价差(均值在t=300处跳变)
    np.random.seed(42)
    n = 600
    true_mean = np.concatenate([np.full(300, 0.0), np.full(300, 2.0)])
    spread = true_mean + np.random.normal(0, 1, n)

    signals, z, x, P = generate_signals(spread)

    # 输出关键统计
    trades = np.diff(signals) != 0
    n_trades = int(trades.sum())
    print(f"总交易次数: {n_trades}")
    print(f"信号分布: 多头={int((signals==1).sum())}, 空头={int((signals==-1).sum())}, 空仓={int((signals==0).sum())}")
    print(f"滤波末期均值估计: {x[-1]:.3f} (真实均值: 2.0)")
    print(f"滤波末期标准差估计: {np.sqrt(P[-1]):.3f} (真实标准差: 1.0)")

运行与验证

运行方式:直接执行 python kalman_spread.py(需安装 numpy)。

验证方法:

  • 观察输出中滤波末期均值估计应接近2.0(因t=300后真实均值跳变为2),标准差估计接近1.0,说明滤波器成功追踪了时变均值。
  • 交易次数应明显大于0,且信号在均值跳变后仍能正常触发——若用固定历史均值,跳变后会产生大量错误信号,而卡尔曼滤波能快速适应。
  • 可打印 z 数组观察:在t=300附近,z值会短暂偏离但迅速回归正常范围,证明自适应能力。

关键说明

  • 参数 delta 控制状态漂移速度(越大对变化响应越快,但噪声越大),r 为观测噪声方差。实际应用中可用最大似然或经验值调参。
  • 本实现输出信号为1/-1/0,实际交易时需结合具体价差构造(如配对股票的对数价差)确定多空方向。
  • 滤波方差 P 直接作为价差方差的实时估计,替代固定窗口滚动方差,避免了窗口长度选择问题。
卡尔曼滤波动态估计价差均值与方差 真实价差 滤波均值 ±2σ带 -3 -2 -1 0 1 2 0 150 300 450 600 均值跳变点 滤波均值快速追踪跳变 ±2σ自适应带宽 跳变后信号仍有效