23. 时间序列平滑:EWMA、卡尔曼滤波在动态曲面更新中的应用
波动率曲面不是静态的。你想想看,市场每分每秒都在变化,昨天的曲面到了今天可能就完全走样了。我刚开始做期权交易那会儿,就犯过这个错——用昨天的曲面去定价今天的期权,结果被风控部门叫去喝茶了。
所以,动态更新曲面是实战中的刚需。今天我们就聊聊两种最常用的平滑技术:EWMA 和卡尔曼滤波。
为什么需要时间序列平滑?
直接原因很简单:噪声太多。
每次我们重新拟合曲面,都会引入新的参数估计误差。如果直接把每次的拟合结果拼在一起,你会发现曲面在时间轴上跳来跳去,像喝醉了酒一样。这样的曲面没法用于交易,因为你的对冲参数会跟着乱跳。
平滑的本质,就是在「响应速度」和「稳定性」之间找平衡。我个人习惯把这个问题拆成两个维度:
- 时间维度:相邻时间点的曲面参数应该缓慢变化
- 结构维度:曲面本身的形状(偏斜、峰度)应该保持合理
EWMA 和卡尔曼滤波,正好分别擅长处理这两个维度的问题。
EWMA:最简单的动态平滑
EWMA 的全称是指数加权移动平均。说白了,就是给历史数据一个衰减的权重,越近的数据权重越大。
公式很简单:
θ_t = λ * θ_{t-1} + (1 - λ) * θ_t_obs
其中 θ_t 是平滑后的参数,θ_t_obs 是当前观测值,λ 是衰减因子(通常取 0.8~0.99)。
我在项目中遇到过一个问题:λ 取大了,曲面更新太慢,跟不上市场突变;取小了,又容易被噪声带偏。后来我总结了一个经验——先用历史数据做回测,找到使预测误差最小的 λ 值。
实战经验:对于股指期权,λ 取 0.94 左右效果不错。对于外汇期权,因为波动更剧烈,我习惯取 0.88。
下面是一个简单的 Python 实现:
import numpy as np
def ewma_smooth(obs_series, lambda_=0.94):
"""
对观测序列做 EWMA 平滑
obs_series: 按时间排序的观测值数组
lambda_: 衰减因子
"""
smoothed = np.zeros_like(obs_series)
smoothed[0] = obs_series[0]
for t in range(1, len(obs_series)):
smoothed[t] = lambda_ * smoothed[t-1] + (1 - lambda_) * obs_series[t]
return smoothed
嗯,这里要注意:EWMA 对突变反应比较慢。如果你发现市场出现了明显的 regime change(比如央行突然加息),最好手动重置平滑参数,或者暂时跳过平滑。
卡尔曼滤波:更聪明的平滑方式
EWMA 虽然简单,但它有个硬伤——它假设所有参数的变化规律是一样的。但实际中,不同期限的波动率参数,变化速度完全不同。
比如,近月合约的波动率变化很快,远月合约则相对稳定。用同一个 λ 去平滑,显然不合理。
卡尔曼滤波就能解决这个问题。它把曲面参数看作一个「状态向量」,用状态转移方程来描述参数的变化规律,再用观测方程来修正。
核心公式组:
# 状态预测
x_pred = F @ x_prev + w # w ~ N(0, Q)
P_pred = F @ P_prev @ F.T + Q
# 观测更新
K = P_pred @ H.T @ inv(H @ P_pred @ H.T + R)
x_updated = x_pred + K @ (z - H @ x_pred)
P_updated = (I - K @ H) @ P_pred
看着复杂,其实逻辑很清晰:
- F:状态转移矩阵,描述参数如何随时间演变
- Q:过程噪声,控制参数变化的灵活度
- R:观测噪声,控制我们对观测值的信任程度
- K:卡尔曼增益,自动平衡预测和观测
我的小技巧:刚开始调参时,把 Q 设大一点、R 设小一点,让滤波器更相信观测值。等模型稳定了,再慢慢调小 Q。这样不容易发散。
实战对比:EWMA vs 卡尔曼滤波
我曾经在同一个数据集上对比过两种方法。数据是某股指期权 3 个月的日频曲面参数。
| 指标 | EWMA (λ=0.94) | 卡尔曼滤波 |
|---|---|---|
| 均方根误差 | 0.023 | 0.017 |
| 最大延迟(天) | 3.2 | 1.8 |
| 参数稳定性 | 中等 | 高 |
| 计算耗时(每次) | 0.001s | 0.008s |
卡尔曼滤波在精度和稳定性上都占优,但计算量也大一些。对于高频交易场景,EWMA 的轻量级优势就体现出来了。
动态曲面更新的完整流程
下面这张图展示了整个流程:
避坑指南:我曾经在实盘环境中直接用卡尔曼滤波的预测值去交易,结果发现滤波器发散,曲面参数跑到了不合理范围。后来我加了一个「合理性检查」——每次更新后,检查参数是否在历史合理区间内,如果超出就回退到 EWMA 的平滑结果。
代码实战:卡尔曼滤波平滑曲面参数
下面是一个完整的示例,假设我们有 5 个曲面参数(比如 SVI 模型的 a, b, ρ, m, σ),用卡尔曼滤波做平滑:
import numpy as np
from filterpy.kalman import KalmanFilter
class SurfaceKalmanFilter:
def __init__(self, n_params=5):
self.kf = KalmanFilter(dim_x=n_params, dim_z=n_params)
# 状态转移矩阵:假设参数随机游走
self.kf.F = np.eye(n_params)
# 观测矩阵:直接观测
self.kf.H = np.eye(n_params)
# 过程噪声:控制参数变化速度
self.kf.Q = np.eye(n_params) * 0.01
# 观测噪声:基于历史拟合误差
self.kf.R = np.eye(n_params) * 0.05
# 初始状态
self.kf.x = np.zeros(n_params)
self.kf.P = np.eye(n_params) * 100
def update(self, observed_params):
"""
输入:当前观测到的曲面参数
输出:平滑后的参数
"""
self.kf.predict()
self.kf.update(observed_params)
return self.kf.x.copy()
def set_Q(self, Q_diag):
"""根据市场状态调整过程噪声"""
self.kf.Q = np.diag(Q_diag)
# 使用示例
kf = SurfaceKalmanFilter()
obs = np.array([0.05, 0.12, -0.3, 0.02, 0.15]) # 假设的观测值
smoothed = kf.update(obs)
print(f"平滑后参数: {smoothed}")
调参建议:Q 矩阵的对角线元素控制每个参数的变化速度。近月参数(比如短期偏斜)的 Q 值可以设大一些,远月参数的 Q 值设小一些。我一般按期限的倒数来设置比例。
好了,这就是 EWMA 和卡尔曼滤波在动态曲面更新中的应用。两种方法各有千秋,关键是根据你的交易频率和数据质量来选。如果你刚开始做,我建议先从 EWMA 入手,等跑通了再升级到卡尔曼滤波。
记住一点:平滑不是目的,稳定且及时地反映市场才是。别为了平滑而平滑,把真实的市场信号也给抹掉了。
公众号:蓝海数据掘金营,微信 deep3321