3、波动率曲面构建方法:插值法(线性、样条)、参数化模型(SVI、SSVI)、市场数据清洗与处理
说实话,波动率曲面这东西,看着挺唬人。但说白了,它就是一张三维地图——横轴是行权价,纵轴是到期时间,高度是隐含波动率。我们做套利,就是在这张地图上找“坑”和“包”。
但问题来了:市场给我们的数据是离散的、有噪声的。你不可能在每个行权价、每个到期日上都有活跃的期权报价。所以,我们需要把这张地图“补全”。这就是构建曲面的核心任务。
我个人习惯把构建过程分成三步:数据清洗 → 插值/拟合 → 曲面校验。每一步踩过的坑,我都记得清清楚楚。
3.1 市场数据清洗与处理——别让垃圾数据毁了你的曲面
我刚开始做这个的时候,犯过一个低级错误:直接把交易所的原始报价扔进模型。结果曲面长成了“心电图”,根本没法用。后来我学乖了,清洗这一步,再怎么强调都不过分。
我的清洗流程一般包含这几步:
- 剔除价差过大的合约:买卖价差超过中间价一定比例(比如10%),直接扔掉。这种数据没有参考价值。
- 过滤深度虚值/实值期权:Delta绝对值小于0.05或大于0.95的,我一般会谨慎处理。这些合约流动性差,报价容易失真。
- 处理临近到期合约:到期日小于3天的期权,波动率容易“抽风”。我建议要么剔除,要么单独处理。
- 异常值检测:用3-sigma原则或者IQR(四分位距)方法,把明显偏离的波动率点揪出来。
嗯,这里要注意:清洗不是越狠越好。你想想看,如果把所有“看起来不对劲”的数据都删了,曲面可能会过度平滑,反而丢失了真实的市场信息。我一般会保留那些虽然偏离但仍有交易量的点。
3.2 插值法——最简单也最直接的曲面构建方式
插值法,说白了就是“连线”。你有了几个离散的点,想得到中间的值,那就用数学方法把它们连起来。最常用的两种:线性插值和样条插值。
3.2.1 线性插值
线性插值是最朴素的方法。在两个已知点之间画一条直线,中间的值按比例算出来。
# 一维线性插值示例
import numpy as np
from scipy.interpolate import interp1d
# 假设我们有三个行权价对应的波动率
strikes = np.array([100, 105, 110])
vols = np.array([0.25, 0.28, 0.32])
# 创建线性插值函数
f_linear = interp1d(strikes, vols, kind='linear')
# 在行权价102.5处插值
vol_at_102_5 = f_linear(102.5)
print(f"行权价102.5的波动率: {vol_at_102_5:.4f}")
线性插值的优点是简单、快速、不会出现“过冲”。但缺点也很明显——它不够平滑。在真实市场中,波动率曲面通常是光滑的,线性插值会在数据点处留下“尖角”。
3.2.2 样条插值
样条插值就优雅多了。它用分段低次多项式来拟合数据,保证在数据点处不仅连续,而且一阶、二阶导数也连续。说白了,就是画出一条“丝滑”的曲线。
# 三次样条插值示例
from scipy.interpolate import CubicSpline
# 同样的数据
strikes = np.array([100, 105, 110])
vols = np.array([0.25, 0.28, 0.32])
# 创建三次样条插值函数
f_spline = CubicSpline(strikes, vols, bc_type='natural')
# 在行权价102.5处插值
vol_at_102_5_spline = f_spline(102.5)
print(f"样条插值结果: {vol_at_102_5_spline:.4f}")
样条插值的问题在于:它可能会“过度拟合”。尤其是在数据点稀疏的区域,样条曲线可能会产生不合理的波动。我曾经在构建远月合约曲面时,因为数据点太少,样条插值直接画出了一个“驼峰”,完全不符合市场逻辑。
3.3 参数化模型——SVI与SSVI
插值法虽然简单,但它有个致命缺陷:它只是数学上的拟合,没有考虑波动率曲面的金融学结构。参数化模型就不一样了,它用特定的函数形式来描述波动率曲面的形状。
我个人最喜欢的参数化模型是SVI(Stochastic Volatility Inspired)及其变体SSVI。为什么?因为它们参数少、解释性强、而且能很好地捕捉波动率曲面的“微笑”特征。
3.3.1 SVI模型
SVI模型由Gatheral提出,核心思想是用一个有理函数来描述波动率与行权价的关系。它的公式长这样:
# SVI模型参数化
# w(k) = a + b * (rho * (k - m) + sqrt((k - m)^2 + sigma^2))
# 其中:
# k = log(K/F) —— 对数行权价
# a, b, rho, m, sigma —— 五个待估参数
# w(k) = sigma_imp^2 * T —— 总方差
import numpy as np
def svi_vol(k, a, b, rho, m, sigma):
"""计算SVI模型下的波动率"""
# 先计算总方差
w = a + b * (rho * (k - m) + np.sqrt((k - m)**2 + sigma**2))
# 确保方差非负
w = np.maximum(w, 0.0)
return w
这五个参数各有含义:a控制整体水平,b控制倾斜程度,rho控制不对称性,m控制微笑中心,sigma控制微笑曲率。
我在项目中遇到过一个问题:SVI的参数估计对初始值非常敏感。如果初始值给得不好,优化算法很容易陷入局部最优。我的建议是:先用简单的线性回归估算a和b的初值,再逐步优化其他参数。
3.3.2 SSVI模型——SVI的进化版
SSVI(Surface SVI)是SVI的扩展,专门用来处理整个曲面(包含不同到期日)。它解决了SVI的一个痛点:不同到期日的参数之间缺乏一致性约束。
SSVI的核心假设是:波动率曲面的形状随到期时间变化,但这种变化是有规律的。它引入了一个“倾斜函数”phi(theta),来描述不同到期日下微笑形状的变化。
# SSVI模型的核心思想
# 对于每个到期日T,总方差theta = sigma_imp^2 * T
# 微笑参数rho和sigma与theta之间存在函数关系
def ssvi_vol(k, theta, rho_inf, eta, gamma):
"""
SSVI模型简化版
theta: 总方差(与到期日相关)
rho_inf, eta, gamma: 全局参数
"""
# 倾斜函数
phi = 1.0 / (1.0 + np.exp(-eta * theta**gamma))
# 微笑曲率
sigma = theta * (1.0 - np.abs(rho_inf * phi))
# 计算波动率
w = theta / 2.0 * (1.0 + rho_inf * phi * k + np.sqrt((k + rho_inf * phi)**2 + sigma**2))
return np.sqrt(np.maximum(w, 0.0) / theta)
3.4 三种方法的对比与选择
说了这么多,到底该用哪种方法?我整理了一个对比表,方便你快速决策:
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 线性插值 | 简单、快速、无过冲 | 不平滑、导数不连续 | 快速原型、高频场景 |
| 样条插值 | 平滑、灵活 | 可能过拟合、边界不稳定 | 数据密集、需要平滑曲面 |
| SVI | 参数少、解释性强、捕捉微笑 | 参数估计敏感、跨期不一致 | 单到期日微笑拟合 |
| SSVI | 全局一致、跨期稳定、参数更少 | 模型假设较强、实现复杂 | 完整曲面构建、套利策略 |
我个人建议:如果你刚开始做波动率曲面,先从样条插值入手,感受一下数据的特点。等你对市场有了感觉,再切换到SVI或SSVI。我曾经在实盘策略中同时维护两套曲面——一套用样条插值做实时监控,一套用SSVI做策略信号生成。
3.5 构建流程与避坑指南
最后,我总结一下完整的构建流程,以及我踩过的坑:
- 数据获取与清洗:剔除价差过大、流动性差的合约。注意不同交易所的数据格式差异。
- 选择构建方法:根据你的策略类型(高频/低频、套利/对冲)选择合适的方法。
- 参数估计:对于参数化模型,使用非线性最小二乘法(如Levenberg-Marquardt)进行拟合。注意初始值的选择。
- 曲面校验:检查是否存在套利机会(如蝶式套利、日历套利)。如果曲面存在套利,说明构建有问题。
- 动态更新:市场在变,曲面也要跟着变。我一般每5-10分钟重新拟合一次。
嗯,构建波动率曲面,说白了就是“在噪声中寻找信号”。方法没有绝对的好坏,关键看你的策略需要什么样的曲面。插值法快但粗糙,参数化模型优雅但复杂。我的建议是:先跑通一个简单的版本,再逐步优化。
你想想看,如果连曲面都构建不好,后面的波动率套利就是空中楼阁。所以,这一步值得你花时间打磨。