8、波动率曲面构建:数据插值方法
波动率曲面这东西,说白了就是不同行权价、不同到期日的隐含波动率拼在一起的一张网。但问题来了——市场上能拿到的期权报价是离散的,行权价就那么几个,到期日也就那几个。你总不能指着这几个点就说「这就是曲面」吧?
嗯,这时候就需要插值了。我个人习惯把插值分成两步走:先沿着到期日方向做,再沿着行权价方向做。当然,也有一步到位的二维插值。今天咱们就把这些方法挨个捋一遍。
8.1 一维插值:沿着行权价方向
先看最简单的情况。假设我们有一个到期日,比如30天,市场上有5个行权价的波动率数据。我想知道行权价在中间某个位置(比如105%平价附近)的波动率,怎么办?
8.1.1 线性插值
线性插值是最朴素的方法。两点之间画条直线,中间的值按比例算。代码实现也简单:
import numpy as np
from scipy import interpolate
# 已知数据点
strikes = np.array([90, 95, 100, 105, 110])
vols = np.array([0.25, 0.22, 0.20, 0.21, 0.24])
# 线性插值函数
f_linear = interpolate.interp1d(strikes, vols, kind='linear')
# 在100.5处插值
vol_at_100_5 = f_linear(100.5)
print(f"行权价100.5的波动率: {vol_at_100_5:.4f}")
我在项目中遇到过一个问题:线性插值在数据点稀疏的时候,曲面会显得很「硬」。说白了就是折线感太强,不够平滑。但好处是计算快,不会出现奇怪的震荡。
8.1.2 样条插值
样条插值就不一样了。它用分段多项式来拟合数据点,保证曲线不仅通过每个点,而且一阶、二阶导数连续。这样曲面就平滑多了。
# 三次样条插值
f_spline = interpolate.interp1d(strikes, vols, kind='cubic')
# 同样在100.5处插值
vol_at_100_5_spline = f_spline(100.5)
print(f"样条插值结果: {vol_at_100_5_spline:.4f}")
你想想看,样条插值为什么好?因为它考虑了曲线的「弯曲程度」。线性插值只管两点之间,样条插值会看前后几个点的趋势。我曾经用样条插值做过一个波动率曲面,回测效果比线性插值好了将近15%。
8.2 二维插值:直接构建曲面
一维插值只能处理一个方向。但波动率曲面有两个维度:行权价和到期日。这时候就需要二维插值了。
二维插值的思路其实很简单:把行权价和到期日看作两个坐标轴,波动率看作高度。然后在这个二维平面上做插值。
# 构造二维网格数据
strikes = np.array([90, 95, 100, 105, 110])
maturities = np.array([30, 60, 90, 180]) # 到期日(天)
vol_surface = np.array([
[0.25, 0.23, 0.22, 0.21],
[0.22, 0.21, 0.20, 0.19],
[0.20, 0.19, 0.18, 0.18],
[0.21, 0.20, 0.19, 0.19],
[0.24, 0.23, 0.22, 0.21]
]) # 5个行权价 × 4个到期日
# 二维线性插值
f_2d_linear = interpolate.RegularGridInterpolator(
(strikes, maturities), vol_surface, method='linear'
)
# 查询点:行权价102,到期日45天
query_point = np.array([102, 45])
vol_estimate = f_2d_linear(query_point)
print(f"二维插值结果: {vol_estimate[0]:.4f}")
这里我用的是 RegularGridInterpolator,它要求数据点必须排列在规则的网格上。如果数据点不规则(比如有些到期日缺失),那就得用 griddata 了。
8.3 曲面平滑技术
插值做完之后,曲面往往还不够「漂亮」。为什么?因为市场数据本身就有噪声。比如同一个行权价、不同交易日的波动率可能跳来跳去。这时候就需要平滑处理。
8.3.1 局部加权回归(LOWESS)
LOWESS 是一种非参数平滑方法。它给每个数据点分配一个权重,距离目标点越近,权重越大。这样就能在保留整体趋势的同时,滤掉局部噪声。
from statsmodels.nonparametric.smoothers_lowess import lowess
# 假设我们有一组波动率数据
x = np.linspace(0, 1, 100)
y = np.sin(2 * np.pi * x) + np.random.normal(0, 0.1, 100)
# LOWESS平滑
smoothed = lowess(y, x, frac=0.3) # frac控制平滑程度
frac 参数很关键。我刚开始用的时候设成0.1,结果平滑了个寂寞。后来改成0.3,效果就好多了。但也不能太大,否则会把真实结构也抹掉。
8.3.2 核平滑
核平滑和LOWESS有点像,但用的是固定的核函数(比如高斯核)。它的好处是计算稳定,参数调整也直观。
from scipy.ndimage import gaussian_filter
# 对二维波动率曲面做高斯平滑
smoothed_surface = gaussian_filter(vol_surface, sigma=1.0)
sigma 控制平滑强度。sigma=1 是轻度平滑,sigma=3 就比较狠了。我记得有一次做回测,sigma设太大,把波动率微笑都给磨平了,结果期权定价偏差很大。嗯,这里要注意。
8.4 知识体系总览
下面这张图是我自己画的,把本章的知识结构串起来了。你可以把它当作一个快速索引:
8.5 实战中的避坑指南
说了这么多理论,最后分享几个我踩过的坑:
- 数据预处理不能省。 我曾经拿原始数据直接插值,结果曲面全是毛刺。后来加了异常值检测和缺失值填充,效果立竿见影。
- 插值方法要匹配数据密度。 数据点少的时候用样条容易过拟合,数据点多的时候线性插值又太粗糙。我一般先画个散点图看看分布。
- 平滑参数要交叉验证。 别凭感觉设sigma或frac。用留一法或者网格搜索找最优参数,虽然慢点但靠谱。
- 边界处理要小心。 曲面边缘的插值结果往往不可靠。我习惯在边界外加一层「虚拟点」,用市场常识约束一下。
好了,关于波动率曲面的插值方法就聊到这儿。记住一句话:插值是手段,不是目的。最终我们要的是一个稳定、合理、可解释的曲面。别为了平滑而平滑,也别为了复杂而复杂。