9. 局部波动率模型:Dupire公式、从期权价格反推局部波动率
聊完了随机波动率,咱们来聊聊另一个经典模型——局部波动率模型。说实话,我第一次接触这个模型时,觉得它有点「反直觉」。你想想看,波动率本身是个随机过程,怎么能是价格和时间的确定性函数呢?
但后来我在实际项目中用了几次,才真正体会到它的妙处。局部波动率模型的核心思想很简单:波动率不是常数,也不是随机变量,而是标的资产价格和时间的函数。用数学语言说就是:
dS = μS dt + σ(S, t) S dW
这里的 σ(S, t) 就是局部波动率函数。它告诉我们:在某个时刻、某个价格水平上,波动率到底是多少。
9.1 为什么需要局部波动率?
你可能要问:Black-Scholes 假设波动率是常数,随机波动率模型假设波动率是随机过程,为什么还要搞个局部波动率?
嗯,这里有个很实际的问题。我在做期权做市的时候,每天都要面对几百个不同行权价、不同到期日的期权。用 BS 模型算出来的隐含波动率,你会发现一个明显的「波动率微笑」或「波动率偏斜」。
随机波动率模型虽然能解释这个现象,但参数校准起来太麻烦了。而且,它很难完美拟合市场上所有期权的价格。
局部波动率模型就不一样了。它有个非常漂亮的性质:只要给定足够多的期权价格,就能唯一确定局部波动率函数。这意味着,我们可以完美复制市场上观察到的波动率微笑。
核心思想:局部波动率模型是「从市场价格反推波动率结构」的桥梁。它不假设波动率如何变化,而是让数据说话。
9.2 Dupire公式:从期权价格到局部波动率
1994年,Dupire 发表了一篇论文,给出了一个惊人的结果:如果知道所有行权价和到期日的欧式期权价格,就能直接计算出局部波动率。
公式长这样:
σ²(K, T) = [∂C/∂T + (r - q)K ∂C/∂K + qC] / [½ K² ∂²C/∂K²]
其中:
- C(K, T) 是行权价为 K、到期日为 T 的欧式看涨期权价格
- r 是无风险利率
- q 是股息率
我第一次看到这个公式时,第一反应是:这玩意儿能用吗?分母是二阶导数,期权价格稍微有点噪音,结果就全乱套了。
后来在实际项目中,我确实踩过这个坑。有一次用市场数据直接套公式,算出来的局部波动率全是负的——明显不对。后来才发现,是期权价格数据太粗糙,二阶导数根本算不准。
我的经验:用 Dupire 公式前,一定要对期权价格做平滑处理。我习惯用三次样条插值,先对隐含波动率曲面做平滑,再转回期权价格,最后算局部波动率。这样能大大减少数值噪音。
9.3 从期权价格反推局部波动率的步骤
实际操作中,我一般按以下步骤来:
- 数据准备:收集不同行权价和到期日的期权价格,构建隐含波动率曲面
- 曲面平滑:用插值方法(如样条插值)对隐含波动率曲面做平滑处理
- 转回价格:用 BS 公式将平滑后的隐含波动率转回期权价格
- 计算偏导数:用有限差分法计算 ∂C/∂T、∂C/∂K 和 ∂²C/∂K²
- 代入公式:用 Dupire 公式计算每个 (K, T) 点的局部波动率
这里有个关键点:∂C/∂T 是时间偏导,∂C/∂K 是价格一阶偏导,∂²C/∂K² 是价格二阶偏导。二阶偏导最难算准,因为它对噪音最敏感。
9.4 代码实现:Dupire公式的数值计算
下面是我在实际项目中用过的代码片段。它实现了从期权价格矩阵反推局部波动率的核心逻辑:
import numpy as np
from scipy.interpolate import RectBivariateSpline
def compute_local_vol(prices, strikes, maturities, r, q):
"""
从期权价格矩阵计算局部波动率
参数:
prices: 期权价格矩阵 (len(maturities) x len(strikes))
strikes: 行权价数组
maturities: 到期时间数组
r: 无风险利率
q: 股息率
返回:
local_vol: 局部波动率矩阵
"""
# 第一步:对价格曲面做平滑插值
# 我习惯用双三次样条,效果比较稳定
interp = RectBivariateSpline(maturities, strikes, prices)
# 第二步:计算偏导数
# 时间偏导 ∂C/∂T
dC_dT = interp(maturities, strikes, dx=1, dy=0, grid=True)
# 价格一阶偏导 ∂C/∂K
dC_dK = interp(maturities, strikes, dx=0, dy=1, grid=True)
# 价格二阶偏导 ∂²C/∂K²
d2C_dK2 = interp(maturities, strikes, dx=0, dy=2, grid=True)
# 第三步:用 Dupire 公式计算局部波动率
# 注意:要处理分母为零的情况
K_grid, T_grid = np.meshgrid(strikes, maturities)
numerator = dC_dT + (r - q) * K_grid * dC_dK + q * prices
denominator = 0.5 * K_grid**2 * d2C_dK2
# 避免除以零或负数
mask = denominator > 1e-10
local_vol_sq = np.where(mask, numerator / denominator, np.nan)
# 取平方根得到波动率
local_vol = np.sqrt(np.maximum(local_vol_sq, 0))
return local_vol
注意:这段代码假设输入的价格矩阵已经过平滑处理。如果直接用原始市场数据,结果会很糟糕。我曾经犯过这个错误,算出来的局部波动率曲面全是毛刺,根本没法用。
9.5 局部波动率曲面的可视化
算出来局部波动率后,我通常会画个三维曲面图看看。下面这个 SVG 图展示了典型的局部波动率曲面结构:
从这张图能看出几个特点:
- 短期期权:波动率微笑很明显,价外期权波动率显著高于平值期权
- 长期期权:波动率曲面趋于平坦,微笑效应减弱
- 整体趋势:随着到期时间增加,波动率水平整体下降(波动率期限结构)
9.6 实际应用中的注意事项
用 Dupire 公式做局部波动率模型,有几个坑我得提醒你:
坑一:数据稀疏性。市场上不是所有行权价和到期日都有活跃交易的期权。我遇到过只有 5 个行权价、3 个到期日的情况,插值出来的曲面根本不可靠。
我的做法:对于数据稀疏的情况,我会先用参数化模型(如 SVI 模型)拟合隐含波动率曲面,再用拟合后的曲面计算局部波动率。这样虽然引入了一些模型假设,但比直接插值稳定得多。
坑二:数值稳定性。Dupire 公式的分母是 ∂²C/∂K²,这个值在平值附近最大,在深度价内/价外时趋近于零。分母接近零时,局部波动率会变得极不稳定。
我记得有一次做回测,局部波动率模型在深度价外期权上表现特别差。后来发现是分母太小,导致局部波动率被放大了几十倍。解决办法是设置一个阈值,当分母小于某个值时,用相邻点的插值代替。
9.7 局部波动率 vs 随机波动率
最后,我想聊聊这两个模型的取舍。很多初学者会问:到底该用哪个?
我的看法是这样的:
| 对比维度 | 局部波动率模型 | 随机波动率模型 |
|---|---|---|
| 拟合能力 | 能完美拟合市场微笑 | 只能近似拟合 |
| 参数数量 | 很多(每个网格点一个参数) | 少(通常 3-5 个参数) |
| 动态行为 | 静态(波动率曲面固定) | 动态(波动率会随机变化) |
| 对冲效果 | 短期对冲效果好 | 长期风险管理更合理 |
| 计算速度 | 快(有限差分法) | 慢(需要蒙特卡洛模拟) |
说白了,局部波动率模型适合做定价和对冲,随机波动率模型适合做风险管理。我在做期权做市时,每天用局部波动率模型给几百个期权定价;但在做投资组合的风险分析时,会用随机波动率模型来模拟未来的波动率变化。
嗯,这就是局部波动率模型的核心内容。Dupire 公式看起来复杂,但理解了它的思想——从市场价格反推波动率结构——其实并不难。关键是要处理好数值计算中的各种细节,这需要在实际项目中慢慢积累经验。