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 从期权价格反推局部波动率的步骤

实际操作中,我一般按以下步骤来:

  1. 数据准备:收集不同行权价和到期日的期权价格,构建隐含波动率曲面
  2. 曲面平滑:用插值方法(如样条插值)对隐含波动率曲面做平滑处理
  3. 转回价格:用 BS 公式将平滑后的隐含波动率转回期权价格
  4. 计算偏导数:用有限差分法计算 ∂C/∂T、∂C/∂K 和 ∂²C/∂K²
  5. 代入公式:用 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 图展示了典型的局部波动率曲面结构:

局部波动率曲面示意图 行权价 K 波动率 σ 到期时间 最长 微笑效应明显 趋于平坦

从这张图能看出几个特点:

  • 短期期权:波动率微笑很明显,价外期权波动率显著高于平值期权
  • 长期期权:波动率曲面趋于平坦,微笑效应减弱
  • 整体趋势:随着到期时间增加,波动率水平整体下降(波动率期限结构)

9.6 实际应用中的注意事项

用 Dupire 公式做局部波动率模型,有几个坑我得提醒你:

坑一:数据稀疏性。市场上不是所有行权价和到期日都有活跃交易的期权。我遇到过只有 5 个行权价、3 个到期日的情况,插值出来的曲面根本不可靠。

我的做法:对于数据稀疏的情况,我会先用参数化模型(如 SVI 模型)拟合隐含波动率曲面,再用拟合后的曲面计算局部波动率。这样虽然引入了一些模型假设,但比直接插值稳定得多。

坑二:数值稳定性。Dupire 公式的分母是 ∂²C/∂K²,这个值在平值附近最大,在深度价内/价外时趋近于零。分母接近零时,局部波动率会变得极不稳定。

我记得有一次做回测,局部波动率模型在深度价外期权上表现特别差。后来发现是分母太小,导致局部波动率被放大了几十倍。解决办法是设置一个阈值,当分母小于某个值时,用相邻点的插值代替。

9.7 局部波动率 vs 随机波动率

最后,我想聊聊这两个模型的取舍。很多初学者会问:到底该用哪个?

我的看法是这样的:

对比维度 局部波动率模型 随机波动率模型
拟合能力 能完美拟合市场微笑 只能近似拟合
参数数量 很多(每个网格点一个参数) 少(通常 3-5 个参数)
动态行为 静态(波动率曲面固定) 动态(波动率会随机变化)
对冲效果 短期对冲效果好 长期风险管理更合理
计算速度 快(有限差分法) 慢(需要蒙特卡洛模拟)

说白了,局部波动率模型适合做定价和对冲,随机波动率模型适合做风险管理。我在做期权做市时,每天用局部波动率模型给几百个期权定价;但在做投资组合的风险分析时,会用随机波动率模型来模拟未来的波动率变化。

嗯,这就是局部波动率模型的核心内容。Dupire 公式看起来复杂,但理解了它的思想——从市场价格反推波动率结构——其实并不难。关键是要处理好数值计算中的各种细节,这需要在实际项目中慢慢积累经验。

公众号:蓝海资料掘金营,微信deep3321