20、波动率曲面校准:市场数据校准流程、校准目标函数、优化算法选择
波动率曲面这东西,看着漂亮,但真正要用它来做定价或风控,你得先把它「校准」到当前市场。说白了,模型参数不能拍脑袋,得让模型输出的价格,跟市场上真实交易的价格对上。这个过程,就是校准。
我刚开始做这块时,觉得校准不就是个拟合嘛,调调参数让误差最小就完了。后来发现,坑多着呢。今天咱们就把这个流程掰开揉碎,讲清楚。
20.1 市场数据校准流程
校准的第一步,不是跑代码,而是拿数据。你想想看,模型再牛,输入的数据是脏的,结果肯定废。
我个人习惯的流程是这样的:
- 获取市场报价:从数据源拿到期权链的隐含波动率数据。通常是不同行权价、不同到期日的 IV 矩阵。
- 数据清洗:剔除明显异常的点。比如深度虚值期权流动性差,报价经常跳来跳去,这些点我会直接扔掉。
- 筛选有效数据:只保留近月到远月几个关键期限,行权价覆盖平值附近 ±2 个标准差就够了。太远的尾部数据,对校准贡献不大,反而引入噪声。
- 构建目标矩阵:把清洗后的数据整理成 (期限, 行权价) 的二维表,这就是我们要拟合的目标。
- 执行校准:用优化算法去调模型参数,让模型输出的曲面尽量贴近目标矩阵。
- 验证与诊断:校准完了别急着用,先看看拟合误差分布,有没有系统性偏差。
我曾经在一个项目中,数据源给的波动率是百分数,我当成小数直接喂进去了,结果校准出来的参数完全离谱。嗯,这种低级错误,犯一次就长记性了。
核心原则:校准不是越精确越好。过度拟合市场噪声,会导致参数不稳定,明天开盘数据一变,参数就乱跳。我们追求的是「稳健拟合」。
20.2 校准目标函数
目标函数,就是衡量「模型输出」和「市场数据」之间差距的数学表达式。选什么样的目标函数,直接影响校准结果。
常见的几种目标函数:
| 目标函数类型 | 公式 | 特点 |
|---|---|---|
| 均方误差 (MSE) | ∑(σ_model - σ_market)² / N | 最常用,对大偏差惩罚重,容易受异常点影响 |
| 平均绝对误差 (MAE) | ∑|σ_model - σ_market| / N | 对异常点鲁棒性更好,但收敛速度慢 |
| 加权均方误差 (WMSE) | ∑ w_i · (σ_model - σ_market)² / N | 给不同区域赋权重,比如平值附近权重高,尾部权重低 |
| 价格误差 | ∑ (Price_model - Price_market)² | 直接对期权价格拟合,但价格对波动率敏感度不同 |
我个人更倾向于用 加权均方误差。为什么?因为平值附近的期权流动性最好,报价最可靠,理应给更高权重。深度虚值期权,报价经常是理论值,参考意义不大,权重给低点。
权重的设定,我一般用 vega 的倒数或者交易量的比例。你想想看,vega 大的地方,波动率稍微一变价格就剧烈波动,这些点更应该被精确拟合。
小技巧:校准目标函数里,我习惯加一个正则化项,比如 λ·||θ||²,惩罚参数过大。这样能防止参数跑到极端值,提高模型的稳定性。
20.3 优化算法选择
目标函数定好了,接下来就是怎么找到让目标函数最小的参数。优化算法选不对,可能跑半天都不收敛,或者收敛到局部最优。
常用的优化算法,我按经验排个序:
- Levenberg-Marquardt (LM):我的首选。它结合了梯度下降和高斯-牛顿法的优点,收敛快,适合中小规模参数优化。SVI、SABR 这类参数不多的模型,LM 基本够用。
- BFGS / L-BFGS:拟牛顿法,适合参数稍多的情况。不需要计算 Hessian 矩阵,内存占用小。我一般在参数超过 10 个时用 L-BFGS。
- Nelder-Mead (单纯形法):无导数方法,适合目标函数不光滑的情况。但收敛慢,我一般只用来做初值搜索。
- 全局优化 (差分进化、模拟退火):当目标函数有很多局部极小点时,先用全局优化找个好初值,再用 LM 或 BFGS 精调。我遇到过 SABR 模型的 β 参数,局部极小点特别多,不用全局优化根本找不到全局最优。
这里有个经验:初值很重要。我习惯先用历史数据跑一遍,把历史参数的平均值作为初值。或者用简单模型(比如平值波动率)先估算一下参数范围,再喂给优化器。
避坑指南:我曾经直接用默认初值去校准 SVI 模型,结果优化器报错说 Hessian 矩阵奇异。查了半天,发现是初值导致某个参数组合让模型退化。后来我改成先用网格搜索粗筛一遍初值,再跑 LM,问题就解决了。
20.4 校准流程的 SVG 框架图
下面这张图,把整个校准流程串起来了。你可以把它当作操作手册,每次校准前扫一眼,避免漏步骤。
20.5 代码示例:SVI 模型校准
光说不练假把式。下面我用 Python 演示一个 SVI 模型的校准过程。SVI 模型参数少(5个),校准起来比较快,适合做例子。
import numpy as np
from scipy.optimize import minimize
# SVI 模型:w(k) = a + b * (rho * (k - m) + sqrt((k - m)^2 + sigma^2))
def svi_vol(k, a, b, rho, m, sigma):
return a + b * (rho * (k - m) + np.sqrt((k - m)**2 + sigma**2))
# 目标函数:加权均方误差
def objective(params, strikes, target_vols, weights):
a, b, rho, m, sigma = params
# 参数约束:b > 0, sigma > 0, |rho| < 1
if b <= 0 or sigma <= 0 or abs(rho) >= 1:
return 1e10
model_vols = svi_vol(strikes, a, b, rho, m, sigma)
return np.sum(weights * (model_vols - target_vols)**2)
# 模拟市场数据(实际使用时替换为真实数据)
strikes = np.linspace(-0.3, 0.3, 11) # 对数行权价
target_vols = 0.15 + 0.05 * strikes**2 + 0.02 * np.random.randn(11)
weights = np.ones_like(strikes) # 简单等权
# 初值设定(经验值)
initial_guess = [0.1, 0.1, -0.3, 0.0, 0.1]
# 执行优化
result = minimize(objective, initial_guess,
args=(strikes, target_vols, weights),
method='L-BFGS-B',
bounds=[(0.01, 0.5), (0.01, 0.5), (-0.99, 0.99),
(-0.2, 0.2), (0.01, 0.3)])
print("校准参数:", result.x)
print("最终误差:", result.fun)
经验之谈:代码里我加了参数边界约束,这是必须的。SVI 模型的参数如果没有约束,优化器可能给出 b 为负值,那波动率曲面形状就完全反了。我刚开始写代码时没加约束,结果画出来的曲面像个倒扣的碗,闹了笑话。
20.6 校准后的检查清单
校准跑完了,别急着部署。我每次都会过一遍这个清单:
- 拟合误差是否在可接受范围? 一般均方根误差 (RMSE) 小于 0.5 个 vol 点算不错。
- 参数是否在合理区间? 比如 SVI 的 rho 应该在 -1 到 1 之间,如果接近边界,说明数据可能有问题。
- 曲面形状是否平滑? 用肉眼扫一眼曲面图,有没有不该出现的尖刺或凹陷。
- 参数稳定性如何? 用昨天的数据校准一次,今天的再校准一次,参数变化大不大?如果一天一变,说明模型过拟合了。
嗯,校准这件事,说白了就是「用模型去解释市场」。模型永远是对现实的简化,校准的目标不是让模型完美拟合每一个点,而是抓住市场的核心特征——偏斜、期限结构、微笑形态。抓住了这些,你的曲面就能用了。
公众号:蓝海资料掘金营,微信deep3321