第十八章:卡尔曼滤波:状态空间模型下的冲击分解

各位同学,今天我们来聊一个硬核话题——卡尔曼滤波。说实话,我第一次接触这东西是在做宏观经济预测的时候,当时被一堆矩阵和协方差搞得头大。但后来我发现,它其实就是个“聪明版的平滑器”,专门用来从噪音中提取真实信号。

咱们这一章的核心任务,就是搞明白:如何用卡尔曼滤波,把时间序列里的永久冲击和临时冲击拆开。你想想看,GDP数据里既有长期趋势(永久冲击),又有短期波动(临时冲击),怎么分?卡尔曼滤波就是干这个的。

18.1 状态空间模型:你的数据其实有“内幕”

先说说状态空间模型。这玩意儿说白了,就是假设你的观测数据背后,藏着一条看不见的“状态变量”。比如你看到的GDP增长率,其实是潜在产出增长率(状态)加上测量误差(噪音)。

我习惯把状态空间模型写成两行公式:

状态方程:x_t = F * x_{t-1} + w_t    (永久冲击在这里)
观测方程:y_t = H * x_t + v_t        (临时冲击在这里)

这里:

  • x_t:状态变量(比如潜在GDP)
  • y_t:观测数据(比如实际GDP)
  • w_t:状态噪声(永久冲击)
  • v_t:观测噪声(临时冲击)

嗯,这里要注意:永久冲击会一直留在状态里,而临时冲击只影响当期观测。这就是分解的关键。

核心思想:卡尔曼滤波通过递归更新,把观测数据中的“永久成分”和“临时成分”实时分离出来。

18.2 卡尔曼滤波的“三步走”

我个人习惯把卡尔曼滤波拆成三步:预测、更新、平滑。咱们一步步看。

18.2.1 预测步骤

这一步很简单:用上一期的状态,猜这一期的状态。公式长这样:

x_{t|t-1} = F * x_{t-1|t-1}
P_{t|t-1} = F * P_{t-1|t-1} * F' + Q

其中Q是永久冲击的方差。你想想看,如果Q很大,说明永久冲击很活跃,状态会跳来跳去。

18.2.2 更新步骤

拿到新数据后,修正预测值。这里有个关键参数——卡尔曼增益K

K_t = P_{t|t-1} * H' * (H * P_{t|t-1} * H' + R)^{-1}
x_{t|t} = x_{t|t-1} + K_t * (y_t - H * x_{t|t-1})
P_{t|t} = (I - K_t * H) * P_{t|t-1}

R是临时冲击的方差。K越大,说明你越相信新数据;K越小,说明你更依赖模型预测。

避坑指南:我曾经在项目中把Q设得太大,结果卡尔曼滤波疯狂追随噪音,完全失去了平滑效果。后来我学乖了,先用EM算法估计Q和R。

18.2.3 平滑步骤

等所有数据都拿到了,回头重新修正每个时间点的状态估计。这叫“事后诸葛亮”,但非常有用。公式我就不写了,你记住一句话:平滑后的状态,才是永久冲击的最佳估计

18.3 冲击分解的实战逻辑

好了,理论讲完,咱们看看怎么用。假设你有一组GDP数据,想拆出永久冲击和临时冲击。流程是这样的:

  1. 设定模型:状态是潜在GDP,观测是实际GDP。状态方程假设潜在GDP是随机游走(永久冲击),观测方程加个临时冲击。
  2. 跑卡尔曼滤波:得到每个时间点的状态估计(潜在GDP)和预测误差。
  3. 分解冲击
    • 永久冲击 = 状态更新量(x_{t|t} - x_{t-1|t-1})
    • 临时冲击 = 预测误差(y_t - H * x_{t|t-1})

说白了,永久冲击是状态方程里的噪声,临时冲击是观测方程里的噪声。卡尔曼滤波帮我们把它们分开了。

注意:如果模型设定错了,分解结果就是垃圾。比如你把永久冲击的方差设成0,那所有波动都会被归为临时冲击——这显然不合理。

18.4 一张图看懂卡尔曼滤波的冲击分解

下面这张SVG图,展示了卡尔曼滤波如何从观测数据中分离永久冲击和临时冲击。我画得比较直观,你一看就懂。

卡尔曼滤波冲击分解流程图 观测数据 y_t 预测:x_{t|t-1} = F * x_{t-1|t-1} 更新:x_{t|t} = 预测 + K * (y_t - 预测) 永久冲击:状态更新量 临时冲击:预测误差 输入 预测 更新 永久 临时

18.5 代码示例:用Python实现冲击分解

光说不练假把式。下面我给出一段简单的Python代码,用卡尔曼滤波分解一个模拟时间序列的永久和临时冲击。你可以在自己的机器上跑跑看。

import numpy as np
import matplotlib.pyplot as plt

# 模拟数据:永久冲击 + 临时冲击
np.random.seed(42)
T = 100
x_true = np.zeros(T)  # 真实状态(永久成分)
y = np.zeros(T)       # 观测数据

# 状态方程:随机游走(永久冲击)
for t in range(1, T):
    x_true[t] = x_true[t-1] + np.random.normal(0, 0.5)  # 永久冲击

# 观测方程:加临时冲击
y = x_true + np.random.normal(0, 1.0, T)  # 临时冲击

# 卡尔曼滤波参数
F = 1.0  # 状态转移
H = 1.0  # 观测矩阵
Q = 0.25  # 永久冲击方差(0.5^2)
R = 1.0   # 临时冲击方差(1.0^2)

# 初始化
x_pred = 0.0
P_pred = 1.0
x_filtered = np.zeros(T)
permanent_shock = np.zeros(T)
temporary_shock = np.zeros(T)

# 滤波循环
for t in range(T):
    # 预测
    x_pred = F * x_pred
    P_pred = F * P_pred * F + Q
    
    # 更新
    K = P_pred * H / (H * P_pred * H + R)
    innovation = y[t] - H * x_pred
    x_filtered[t] = x_pred + K * innovation
    P_pred = (1 - K * H) * P_pred
    
    # 分解冲击
    permanent_shock[t] = x_filtered[t] - (x_filtered[t-1] if t > 0 else 0)
    temporary_shock[t] = innovation

# 画图
plt.figure(figsize=(10, 6))
plt.plot(y, label='观测数据', alpha=0.6)
plt.plot(x_true, label='真实永久成分', linewidth=2)
plt.plot(x_filtered, label='滤波估计', linestyle='--')
plt.legend()
plt.title('卡尔曼滤波:永久冲击与临时冲击分解')
plt.show()

个人经验:我在做实际项目时,Q和R往往不是已知的。这时候我会用EM算法或者最大似然估计来调参。别偷懒,参数估计这一步决定了你的分解质量。

18.6 卡尔曼滤波的优缺点

优点 缺点
实时分解,适合在线应用 模型假设强(线性、高斯)
能处理缺失数据 参数估计困难
理论成熟,数学优美 高维状态时计算量大
永久/临时冲击分离清晰 对异常值敏感

说白了,卡尔曼滤波是个好工具,但别把它当万能药。我见过有人拿它去分解比特币价格,结果因为非线性太强,滤波直接发散。嗯,这时候你可能需要扩展卡尔曼滤波或无迹卡尔曼滤波。

18.7 小结

这一章我们讲了:

  • 状态空间模型是冲击分解的基础框架
  • 卡尔曼滤波通过预测-更新-平滑三步,分离永久和临时冲击
  • 永久冲击来自状态方程,临时冲击来自观测方程
  • 参数Q和R的设定直接影响分解质量

我个人觉得,卡尔曼滤波最迷人的地方在于:它让你在不确定性中,依然能看清数据的“骨架”。下次你看到一组波动剧烈的数据,不妨想想——哪些是永久的变化,哪些只是暂时的噪音?

交易系统化学习资料 微信Strategy888888