第二十讲:高频数据下的曲面构建——微观结构噪声处理与快速拟合算法
说实话,高频数据做波动率曲面,是我个人觉得整个课程里最“刺激”的一章。
为什么?因为低频数据你还能慢慢调参数,高频数据根本不给机会。每秒几百笔交易进来,你还没算完,市场已经变了。而且,高频数据里全是“脏东西”——微观结构噪声、买卖价差、报价跳跃……这些玩意儿如果不处理,你拟合出来的曲面就是个笑话。
20.1 微观结构噪声:你看到的不一定是真的
先说说噪声。高频数据里,价格并不是“真实价格”。
举个例子。你看到一笔成交价是100.01,另一笔是99.99。这能说明价格波动了2分钱吗?不一定。可能只是买卖价差导致的来回跳。这种噪声,我们叫它“微观结构噪声”。
我在做实盘高频策略时,就吃过这个亏。当时直接用原始tick数据算波动率,结果曲面形状完全不对,IV曲面像锯齿一样。后来才发现,是买卖价差导致的伪波动。
20.1.1 噪声的来源
- 买卖价差反弹(Bid-Ask Bounce): 价格在买一和卖一之间来回跳,产生虚假波动
- 离散报价: 价格只能以最小变动单位变化(比如0.01),导致价格路径不连续
- 订单流不平衡: 大单冲击造成的短暂价格偏离
- 数据延迟与异步: 不同股票/期权的报价时间戳不一致
20.1.2 噪声的数学刻画
我们通常假设观测价格 = 真实价格 + 噪声。即:
P_obs(t) = P_true(t) + ε(t)
其中 ε(t) 是微观结构噪声,通常假设它是均值为0的序列相关过程。
为什么序列相关很重要?因为如果噪声是白噪声,那还好处理。但实际中,噪声往往有负的自相关——你想想看,价格从100跳到100.01,下一笔很可能又跳回100。这种负相关会严重扭曲波动率估计。
20.2 噪声处理方法:从简单到实用
处理噪声的方法很多,我挑几个真正实用的讲。
20.2.1 预平均法(Pre-averaging)
这个方法说白了就是:别用单笔价格,用一小段时间内的平均价格。
def pre_average(prices, window=5):
"""
预平均处理:用滑动窗口平均替代原始价格
"""
import numpy as np
weights = np.ones(window) / window
avg_prices = np.convolve(prices, weights, mode='valid')
return avg_prices
窗口大小怎么选?我建议用5-10笔。太小了去不掉噪声,太大了会抹掉真实波动。
20.2.2 两时间尺度法(Two-Scale Realized Volatility)
这是学术界比较推崇的方法。核心思想:用高频数据算一个“粗糙”的波动率,再用低频数据算一个“平滑”的波动率,然后做差来估计噪声方差。
def two_scale_rv(prices, k1=1, k2=10):
"""
两时间尺度已实现波动率
k1: 高频采样间隔(1表示用所有数据)
k2: 低频采样间隔(比如每10笔取一笔)
"""
n = len(prices)
# 高频RV
rv_fast = np.sum(np.diff(prices[::k1])**2)
# 低频RV
rv_slow = np.sum(np.diff(prices[::k2])**2)
# 调整项
adj = (n - k1 + 1) / (n - k2 + 1) * rv_slow
# 最终估计
rv_ts = rv_fast - adj
return max(rv_ts, 0) # 不能为负
20.2.3 核方法(Kernel-Based Estimator)
这个方法更优雅。它给不同时间间隔的收益赋予不同权重——间隔越近,权重越低(因为噪声相关性高)。
def kernel_rv(prices, kernel='bartlett', bandwidth=5):
"""
核方法已实现波动率
kernel: 'bartlett' 或 'epanechnikov'
"""
returns = np.diff(prices)
n = len(returns)
# 计算所有滞后的自协方差
gamma = np.zeros(bandwidth)
for h in range(bandwidth):
gamma[h] = np.sum(returns[h:] * returns[:n-h]) / n
# 核权重
if kernel == 'bartlett':
weights = 1 - np.arange(bandwidth) / bandwidth
elif kernel == 'epanechnikov':
weights = 1 - (np.arange(bandwidth) / bandwidth)**2
# 加权求和
rv_kernel = gamma[0] + 2 * np.sum(weights[1:] * gamma[1:])
return rv_kernel
我个人习惯用Bartlett核,简单稳定。带宽选多少?一般选5-15,具体要看你的数据频率。
20.3 快速拟合算法:时间就是金钱
噪声处理完了,接下来要快速拟合曲面。高频环境下,你不可能用SVI慢慢优化——那太慢了。
20.3.1 为什么不能用传统方法?
传统方法(比如SVI参数优化)需要迭代求解,一次拟合可能要几百毫秒。高频数据每秒来几百个新报价,你根本来不及。
我见过有人用SVI拟合高频数据,结果曲面更新速度比市场慢了好几秒——等曲面画出来,行情已经变了。这就像开车看后视镜,看到的都是过去。
20.3.2 快速拟合的核心思路
说白了就一句话:用解析解代替数值解,用递推代替全量计算。
具体来说,有两条路:
- 参数化简化: 用更简单的曲面形式(比如二次曲面),牺牲一点精度换速度
- 递推更新: 每次新数据来了,只更新局部参数,不重新拟合全局
20.3.3 递推最小二乘法(RLS)
这是我最喜欢的方法。假设你的曲面模型是线性的(或者可以线性化),那么每次新数据来了,只需要O(n²)的计算量就能更新参数,而不是O(n³)。
class RLS_Surface:
"""
递推最小二乘法拟合波动率曲面
假设模型: IV = a0 + a1*K + a2*K^2 + a3*T + a4*T^2 + a5*K*T
"""
def __init__(self, n_features=6, lambda_=0.99):
self.n = n_features
self.lambda_ = lambda_ # 遗忘因子
self.P = np.eye(n_features) * 100 # 初始协方差矩阵
self.theta = np.zeros(n_features) # 参数向量
def update(self, K, T, IV):
"""在线更新:来一个新数据点就更新一次"""
# 构造特征向量
x = np.array([1, K, K**2, T, T**2, K*T])
# 计算增益
g = self.P @ x / (self.lambda_ + x @ self.P @ x)
# 更新参数
self.theta += g * (IV - x @ self.theta)
# 更新协方差
self.P = (self.P - np.outer(g, x @ self.P)) / self.lambda_
return self.theta
def predict(self, K, T):
"""预测任意点上的IV"""
x = np.array([1, K, K**2, T, T**2, K*T])
return x @ self.theta
20.3.4 局部加权回归(LOWESS)
另一种思路:不拟合全局曲面,而是对每个需要预测的点,只取它附近的期权数据做局部拟合。
def local_surface_fit(K_target, T_target, data, bandwidth=0.1):
"""
局部加权回归拟合曲面
data: 包含K, T, IV的DataFrame
"""
# 计算每个数据点的权重(距离越近权重越大)
distances = np.sqrt((data['K'] - K_target)**2 +
(data['T'] - T_target)**2)
weights = np.exp(-distances**2 / (2 * bandwidth**2))
# 局部线性回归
X = np.column_stack([np.ones(len(data)),
data['K'], data['T'],
data['K']**2, data['T']**2,
data['K']*data['T']])
W = np.diag(weights)
y = data['IV'].values
# 加权最小二乘
theta = np.linalg.inv(X.T @ W @ X) @ (X.T @ W @ y)
# 预测
x_target = np.array([1, K_target, T_target,
K_target**2, T_target**2,
K_target*T_target])
return x_target @ theta
这个方法的好处是:你不需要维护一个全局模型,每次预测都是“就地取材”。缺点是计算量稍大,但如果你只预测几个关键点(比如ATM附近的点),速度还是很快的。
20.4 实战中的取舍
讲到这里,你可能想问:到底用哪种方法?
我的建议是分场景:
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| 做市商高频报价 | RLS递推 + 预平均去噪 | 速度最快,能跟上tick级别更新 |
| 盘中波动率监控 | 核方法RV + LOWESS曲面 | 精度更高,适合分钟级更新 |
| 盘后分析/回测 | 两时间尺度法 + SVI | 精度最高,不要求实时性 |
嗯,这里要注意一点:没有万能的方法。我在实盘中,白天用RLS做快速报价,收盘后用核方法做精细分析。工具是死的,人是活的。
20.5 本章知识体系
下面这张图总结了高频数据下曲面构建的完整流程:
这张图把整个流程串起来了。从原始tick数据开始,先做噪声处理,再算波动率,然后用快速算法拟合曲面。每一步都有多种方法可选,关键是根据你的场景做取舍。
好了,这一讲的内容就到这里。高频数据下的曲面构建,说白了就是跟噪声赛跑、跟时间赛跑。方法学会了,剩下的就是多练、多踩坑——嗯,我自己也是踩了无数坑才总结出这些经验的。