波动率曲面构建(一):数据清洗与预处理、期权筛选标准、插值方法对比
做波动率曲面,说白了就是给期权市场拍一张「三维照片」。横轴是行权价,纵轴是到期日,竖轴是隐含波动率。这张照片拍得清不清楚,关键看两个事:原始数据干不干净,插值手法到不到位。
我个人习惯,拿到期权数据的第一件事不是跑模型,而是先盯着原始数据看十分钟。为什么?因为交易所来的数据,脏得能让你怀疑人生。我见过有人把深度虚值期权的报价直接扔进模型,结果曲面长成了「心电图」——全是锯齿。
一、数据清洗与预处理
数据清洗,听着像体力活,其实是最容易翻车的地方。我踩过的坑,一个一个说。
1.1 缺失值与异常值处理
期权数据常见的缺失情况有两种:一是某个行权价全天没有成交,二是某个到期日只有零星几笔。我的处理原则很简单——
- 日内缺失:用前后两个交易日的同一合约数据填充,别用当天其他合约的平均值。不同行权价的波动率结构差异很大,平均会抹平特征。
- 跨期缺失:如果某个到期日的数据量少于10笔,我建议直接剔除。强行插值出来的曲面,近月端会扭曲得厉害。
异常值呢?我最常遇到的是「胖手指」——有人把0.05的波动率敲成了0.5。怎么识别?我一般用两步:
- 计算每个到期日下,所有行权价的波动率中位数和绝对偏差(MAD)。
- 把偏离中位数超过3倍MAD的点标记为异常,直接剔除。
核心原则:宁可少一个数据点,也不要让一个脏数据污染整个曲面。
1.2 时间戳对齐与交易日统一
嗯,这里要注意。不同交易所的期权数据,时间戳格式可能不一样。比如上交所是毫秒级时间戳,而有些数据商给的是字符串。我习惯把所有时间统一转换成UTC+8的datetime格式,然后按交易日对齐。
我曾经犯过一个低级错误:把下午3点收盘后的数据也算进了当天。结果曲面在尾盘附近出现了一个「翘尾」——因为收盘后的大宗交易波动率偏低,把整个曲面拉歪了。后来我加了一条规则:只取交易时段内(9:30-11:30, 13:00-15:00)的数据。
1.3 代码示例:数据清洗流水线
import pandas as pd
import numpy as np
from datetime import datetime
def clean_option_data(df):
# 1. 时间戳统一
df['trade_time'] = pd.to_datetime(df['timestamp'], unit='ms')
df = df[(df['trade_time'].dt.hour >= 9) & (df['trade_time'].dt.hour <= 15)]
# 2. 剔除交易量小于10手的合约
df = df[df['volume'] >= 10]
# 3. 异常值检测(基于MAD)
for expiry in df['expiry'].unique():
mask = df['expiry'] == expiry
med = df.loc[mask, 'implied_vol'].median()
mad = np.abs(df.loc[mask, 'implied_vol'] - med).median()
df = df[~((df['expiry'] == expiry) &
(np.abs(df['implied_vol'] - med) > 3 * mad))]
return df
小技巧:清洗完数据后,我习惯画一张散点图——把每个合约的波动率按行权价和到期日标出来。一眼就能看出有没有「飞点」。肉眼检查永远比代码检查靠谱。
二、期权筛选标准
数据洗干净了,接下来要挑「好」的期权。不是所有期权都值得放进曲面。你想想看,一个还有3天到期的深度虚值期权,流动性差得一天只成交5手,它的报价能信吗?
2.1 流动性筛选
流动性是命根子。我定过一套硬性标准:
| 筛选维度 | 标准 | 原因 |
|---|---|---|
| 日成交量 | ≥ 100手 | 低于这个数,买卖价差太大,报价失真 |
| 持仓量 | ≥ 500张 | 持仓量低说明市场关注度不够,容易被操纵 |
| 买卖价差 | ≤ 中间价的5% | 价差太宽,隐含波动率的计算误差会放大 |
我记得有一次做50ETF期权曲面,发现近月合约的买卖价差普遍在2%以内,但远月合约到了10%以上。后来我直接把远月合约的价差阈值放宽到8%,才保留了足够的数据点。说白了,远月流动性天生差,标准要适当调整。
2.2 到期日筛选
到期日太近的合约,时间价值衰减快,波动率容易「发疯」。我一般剔除到期日小于7天的合约。为什么?因为临近到期,Gamma会变得极大,稍微一点价格波动就能把隐含波动率推到天上。
反过来,到期日太远的合约(比如超过1年),流动性差,而且远期波动率的预测意义不大。我个人习惯只保留30天到365天之间的合约。
2.3 行权价筛选
行权价这块,核心是剔除「深度实值」和「深度虚值」的合约。怎么定义「深度」?我用的标准是:
- 实值侧:行权价偏离标的价格超过20%的,剔除。
- 虚值侧:行权价偏离标的价格超过30%的,剔除。
为什么实值侧比虚值侧更严格?因为实值期权的时间价值低,隐含波动率对模型假设(比如分红、利率)特别敏感。虚值期权虽然也有噪音,但至少还能反映尾部风险。
注意:筛选标准不是死的。如果市场波动率特别高(比如VIX > 30),我会把行权价的筛选范围放宽到±40%。因为高波动环境下,极端行权价也有交易价值。
三、插值方法对比
数据选好了,接下来要在行权价和到期日这两个维度上「填空」。插值方法选不对,曲面就会变形。我试过三种主流方法,一个一个说。
3.1 线性插值
线性插值最简单,也最「暴力」。在两个已知点之间画一条直线,中间的值按比例算。
优点:速度快,不会出现「过冲」——也就是插出来的值不会跑到已知点的范围之外。
缺点:在波动率曲面上,线性插值会抹掉「微笑」特征。你想想看,波动率在平值附近是凹的,线性插值硬生生把它拉成了直线,曲面看起来就像「塌了」。
我什么时候用线性?只在数据点非常密集的时候用。比如近月合约,行权价间隔只有50点,线性插值的误差可以接受。
3.2 三次样条插值
三次样条是我最常用的方法。它在每两个已知点之间拟合一个三次多项式,并且保证连接处的一阶和二阶导数连续。
优点:曲线光滑,能很好地保留波动率的「微笑」和「偏斜」特征。
缺点:容易「过冲」。如果数据点分布不均匀,三次样条会在两端「翘起来」。我遇到过最夸张的一次,插出来的波动率变成了负数——这显然不合理。
怎么解决?加边界条件。我习惯用「自然样条」——强制两端点的二阶导数为0。这样能有效抑制过冲。
3.4 多项式插值
多项式插值,就是用一条高次多项式穿过所有已知点。听起来很完美,对吧?但实际用起来,问题很大。
优点:理论上,如果数据点足够多,多项式可以完美拟合任何形状。
缺点:高次多项式在边界处会剧烈震荡——这就是著名的「龙格现象」。我试过用5次多项式拟合10个数据点,结果两端的值直接飞到了天上。
说实话,我在实际项目中几乎不用多项式插值。除非数据点少于5个,而且你知道这些点大致在一条平滑曲线上。
3.5 三种方法对比表
| 方法 | 光滑度 | 过冲风险 | 计算速度 | 适用场景 |
|---|---|---|---|---|
| 线性插值 | 低 | 无 | 极快 | 数据密集、近月合约 |
| 三次样条 | 高 | 中等 | 快 | 大多数情况,推荐使用 |
| 多项式插值 | 极高 | 高 | 中等 | 数据点极少时谨慎使用 |
我的建议:默认用三次样条,加上自然边界条件。如果发现过冲,就改用「分段线性+平滑」的混合方法——在数据密集区用线性,稀疏区用样条。
四、本章知识体系
下面这张图,把本章的核心逻辑串起来了。从原始数据到最终曲面,每一步都有坑,每一步都有对策。
这张图里,每一步都是环环相扣的。数据洗不干净,后面插值再漂亮也没用。筛选标准定得太松,曲面就会「失真」;定得太严,数据点不够,插值就会「发散」。说白了,这是个平衡的艺术。
好了,这一章的内容就到这儿。数据准备好了,下一章我们就要真正动手构建曲面了——到时候会用到今天讲的所有内容。