10. 波动率微观结构:已实现波动率、微观结构噪声、最优采样频率
波动率这东西,做量化的没人不碰。但你要是用高频数据算波动率,很快就会发现——数据越细,结果越离谱。这不是你算错了,是市场微观结构在捣乱。
我刚开始做高频策略那会儿,拿着1秒一笔的Tick数据算已实现波动率,结果出来一个年化200%的数字。我当时还兴奋了一下,以为发现了什么大机会。后来才发现,那根本不是波动,是噪声。
这一节,我们就来拆解这个核心问题:如何从高频数据中提取真实的波动率。
10.1 已实现波动率:最直观的高频波动度量
先说说已实现波动率(Realized Volatility, RV)。它的思想很简单——把一天切成很多小段,每段算一个收益率,然后把这些收益率的平方加起来。
数学上长这样:
RV = Σ (r_i)² (i = 1, 2, ..., n)
其中 r_i 是第 i 个时间间隔内的对数收益率,n 是一天内的采样次数。
举个例子。假设你每5分钟采样一次,一天有288个5分钟段。你算出288个收益率,平方求和,就得到了当天的已实现波动率。
理论上,采样频率越高,RV 越接近真实波动率。但现实呢?
核心矛盾:采样频率越高,微观结构噪声越严重。你以为是波动,其实是买卖价差、订单簿跳动、数据延迟这些乱七八糟的东西。
10.2 微观结构噪声:藏在Tick数据里的陷阱
什么是微观结构噪声?说白了,就是价格在真实价值附近的随机抖动。
我举个例子你就明白了。你看一只股票,真实价值可能是100.00元。但因为买卖价差的存在,你看到的成交价可能是100.01元(买方主动吃单),下一秒又变成99.99元(卖方主动砸盘)。
这两笔交易之间,股票的基本面没有任何变化。但如果你用这两笔数据算收益率,会得到一个不小的波动值。这就是噪声。
噪声的来源主要有几个:
- 买卖价差(Bid-Ask Spread):价格在买一和卖一之间来回跳
- 订单簿不连续:价格只能以最小变动单位跳动
- 交易异步性:不同股票的交易时间不同步
- 数据错误:交易所的撮合延迟、数据商的记录错误
我曾经处理过一组美股Tick数据,发现某只股票在1秒内出现了3笔完全相同的成交价。后来查了一下,是交易所的重复推送。这种噪声如果不处理,你的波动率模型直接报废。
注意:微观结构噪声不是白噪声。它往往有自相关性,而且和真实波动率相关。这就意味着,你不能简单地用滤波方法把它去掉。
10.3 最优采样频率:Merton's Rule 与实战选择
既然采样太密有噪声,采样太疏又丢失信息,那到底该用多高的频率?
这就引出了最优采样频率的问题。学术界有个经典结论叫 Merton's Rule。
Merton's Rule 的核心思想是:采样频率应该与市场的流动性相匹配。具体来说,最优采样间隔应该大于等于市场的「有效交易间隔」。
怎么理解?
你看,如果一只股票平均每10秒成交一次,那你用1秒采样一次,大部分采样点其实没有新交易,价格是前一笔的重复。这些重复点会引入虚假的零收益率,压低波动率估计。
反过来,如果一只股票每秒成交100次,你每5分钟采样一次,那中间的大量信息就全丢了。
所以,Merton's Rule 给出的建议是:采样频率应该和交易频率匹配。
实战中,我一般这样操作:
- 先统计每只股票的平均成交间隔(比如过去20天的中位数)
- 把采样间隔设在这个中位数的1.5到2倍之间
- 对于流动性极好的股票(比如SPY),我习惯用1分钟采样
- 对于流动性差的股票,可能用到10分钟甚至更长
下面这个表格是我个人总结的经验值:
| 股票流动性 | 日均成交笔数 | 推荐采样频率 | 典型例子 |
|---|---|---|---|
| 极高 | > 10万笔 | 1分钟 | SPY, AAPL |
| 高 | 1万 - 10万笔 | 5分钟 | 大部分标普500成分股 |
| 中等 | 1000 - 1万笔 | 10分钟 | 中小盘股 |
| 低 | < 1000笔 | 30分钟或更长 | 冷门股 |
我的经验:别死磕一个固定频率。我习惯用「多频率对比法」——同时算1分钟、5分钟、15分钟的RV,如果三个结果差异很大,说明噪声污染严重,需要降频。
10.4 噪声校正方法:从理论到代码
光选对采样频率还不够。有时候你不得不使用高频数据(比如做高频策略),这时候就需要对噪声进行校正。
常用的方法有几种:
- 子采样平均法(Subsampling):用多个偏移的采样网格,分别算RV,然后取平均
- 核估计法(Kernel-based):给不同滞后的自协方差加权,类似Newey-West调整
- 两时间尺度法(Two-Scales):同时用高频和低频数据,把噪声部分分离出来
我个人最常用的是子采样平均法。实现起来简单,效果也稳定。下面是一个Python示例:
import numpy as np
import pandas as pd
def subsampled_rv(prices, base_freq='1min', n_subsamples=5):
"""
子采样平均法计算已实现波动率
prices: 包含datetime和price的DataFrame
base_freq: 基础采样频率
n_subsamples: 子采样网格数量
"""
# 生成n_subsamples个偏移的采样网格
offsets = np.linspace(0, 1, n_subsamples, endpoint=False)
rv_list = []
for offset in offsets:
# 按偏移量采样
sampled = prices.resample(
base_freq, on='datetime',
offset=f'{offset}s'
).last().dropna()
# 计算收益率
returns = sampled['price'].pct_change().dropna()
# 计算RV
rv = np.sum(returns ** 2)
rv_list.append(rv)
# 取平均
return np.mean(rv_list)
# 使用示例
# rv_clean = subsampled_rv(tick_data, '1min', 5)
这段代码的核心思路是:不只用整点采样,而是用5个不同的偏移量(比如0秒、12秒、24秒、36秒、48秒)分别采样,然后取平均。这样能有效降低噪声的影响。
关键点:子采样平均法不是万能的。如果噪声本身有强烈的自相关结构,效果会打折扣。这时候可以考虑用核估计法,但实现起来复杂一些。
10.5 知识体系总览
说了这么多,我把这一节的核心逻辑画成了一张图,方便你理解:
这张图把三个核心概念串起来了。你从「高频数据中的波动率估计」出发,会遇到三个分支问题。每个分支都有自己的坑,但最终都要汇聚到同一个解决方案上。
嗯,这一节的内容就到这里。波动率微观结构是个大话题,我们只讲了最核心的部分。记住一句话:高频数据不是越细越好,关键是找到那个「恰到好处」的频率。