跳到正文

隐含波动率求解器:从 Newton 到流形无关方法

Newton-Raphson、二分、Householder 和 Brenner-Subrahmanyam 种子。为什么收敛会失败,以及怎么处理。

第 8 篇 · 共 12 篇 约 9 分钟

如果你做衍生品,你的第一段不平凡代码会是隐含波动率求解器。它是那些看起来无聊的工具之一, 用 σ\sigma 作为未知数反演 Black-Scholes 公式, 但结果有半打失败模式会在周五下午 4 点券商 API 喂你垃圾行使价时咬你。

这篇文章走过标准方法、它们在哪里失败、以及当一切失败时该怎么做。

问题

你观察市场价格 VmV_m。你想找 σ\sigma 使得:

BS(S,K,r,T,σ)−Vm=0\text{BS}(S, K, r, T, \sigma) - V_m = 0

其中 BS\text{BS} 是 Black-Scholes 公式(或你手头资产类别的变种)。这是一个一维求根问题。σ\sigma 的函数平滑、对任何实值度关于 σ\sigma 单调(香草看涨和看跌的 vega 总是为正)、有界于 0 和远期价格之间。

一阶导数, 你需要快速收敛的东西, 正好是前一篇文章的 vegas。对看涨:

Vega(σ)=Sϕ(d1)T\text{Vega}(\sigma) = S \phi(d_1) \sqrt{T}

所以一次 Newton 迭代是:

σn+1=σn−BS(σn)−VmVega(σn)\sigma_{n+1} = \sigma_n - \frac{\text{BS}(\sigma_n) - V_m}{\text{Vega}(\sigma_n)}

Newton-Raphson

Newton-Raphson 在根附近二次收敛。从一个合理起点三次迭代会到机器精度(Press et al., 2007, 第 9.6 节)。

from math import erf, log, sqrt
from scipy.stats import norm
from functools import lru_cache

def bs_call(S, K, r, T, sigma):
    if sigma <= 0 or T <= 0:
        return max(S - K * math.exp(-r * T), 0.0)
    d1 = (log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * sqrt(T))
    d2 = d1 - sigma * sqrt(T)
    return S * norm.cdf(d1) - K * math.exp(-r * T) * norm.cdf(d2)

def bs_vega(S, K, r, T, sigma):
    d1 = (log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * sqrt(T))
    return S * norm.pdf(d1) * sqrt(T)

def implied_vol_newton(market_price, S, K, r, T, sigma0=0.2, tol=1e-8, max_iter=50):
    sigma = sigma0
    for i in range(max_iter):
        price = bs_call(S, K, r, T, sigma)
        diff = price - market_price
        if abs(diff) < tol:
            return sigma
        vega = bs_vega(S, K, r, T, sigma)
        if vega < 1e-12:           # vega 崩溃,无法继续
            return float('nan')
        sigma = sigma - diff / vega
        if sigma <= 0:             # 反射 Newton
            sigma = 0.05
    return sigma

三个观察:

  • Vega 崩溃。σ\sigma 很小时,vega 趋于零,Newton 步爆炸。反射 Newton(截断 σ>0\sigma > 0 然后重启)有帮助。
  • 错符号。如果 σ\sigma 跳成负数,下个迭代的 bs_call 没意义。截断。
  • 坏起点。种子差,Newton 会漫游到波动区。连续 5 次迭代高于 ATM vol 通常表示求解器在追错根或你在撞数值垃圾。

Brenner-Subrahmanyam 近似

迭代求解器需要起点。Brenner-Subrahmanyam (1988) 近似给闭式估计,对 ATM 和短期有效,大约 1% 相对误差:

σinitial≈2πST⋅Vm−intrinsic1+18π\sigma_{\text{initial}} \approx \frac{\sqrt{2\pi}}{S\sqrt{T}} \cdot \frac{V_m - \text{intrinsic}}{1 + \frac{1}{8}\pi}

对 ATM 期权,内在价值为零,所以:

σinitial≈VmS⋅0.3989T\sigma_{\text{initial}} \approx \frac{V_m}{S \cdot 0.3989 \sqrt{T}}

数值上:ATM S=100S = 100,Vm=5V_m = 5,T=0.5T = 0.5。σinitial=5/(100⋅0.3989⋅0.707)=0.177\sigma_{\text{initial}} = 5/(100 \cdot 0.3989 \cdot 0.707) = 0.177。真值依赖于 rr 和 K=100K = 100,但对 r=0.05r = 0.05,真 BS 给 σ≈0.143\sigma \approx 0.143。所以 Brenner-Subrahmanyam 这里高约 24%, 做种子够用。

对 ITM 或 OTM 期权,包括内在价值:

σinitial≈2πST⋅Vm−max⁡(S−Ke−rT,0)...\sigma_{\text{initial}} \approx \frac{\sqrt{2\pi}}{S\sqrt{T}} \cdot \frac{V_m - \max(S - K e^{-rT}, 0)}{...}

更精细的版本(Chung, Chang & Wang, 2011)跨微笑给约 0.5% 误差。用它作起点 Newton 通常 3 次迭代收敛。

二分,Newton 的可靠表亲

Newton 工作时快但不工作时不可预测。二分相反:慢但防弹。你把根框在 [σa,σb][\sigma_a, \sigma_b],检查符号,减半,重复。收敛线性(Press et al., 2007, 第 9.1 节)。

在生产中,正确方法是把它们组合:二分几步得到合理框,然后交给 Newton 收最后几位。

def implied_vol_bisect(market_price, S, K, r, T, lo=1e-4, hi=5.0, tol=1e-7, max_iter=100):
    pa, pb = bs_call(S, K, r, T, lo) - market_price, bs_call(S, K, r, T, hi) - market_price
    if pa * pb > 0:
        return float('nan')   # 没框
    for _ in range(max_iter):
        mid = 0.5 * (lo + hi)
        pm = bs_call(S, K, r, T, mid) - market_price
        if abs(pm) < tol or (hi - lo) < tol:
            return mid
        if pa * pm < 0:
            hi = mid; pb = pm
        else:
            lo = mid; pa = pm
    return 0.5 * (lo + hi)

大约 30 次迭代从任何初始框到 7 位精度。对一个自动流水线每分钟处理 50,000 期权,这是不小的成本。策略:二分钟几步,然后切换到 Newton。

Householder 和 Halley

Halley 方法是 Newton 的三阶细化。公式:

σn+1=σn−2ff′2(f′)2−ff′′\sigma_{n+1} = \sigma_n - \frac{2 f f'}{2(f')^2 - f f''}

其中 f=BS−Vmf = \text{BS} - V_m,撇号是关于 σ\sigma 的导数。对 Black-Scholes,所有导数从希腊字母可知:

f′=Vega,f′′=Vomma=Vega⋅d1d2σf' = \text{Vega}, \quad f'' = \text{Vomma} = \text{Vega} \cdot \frac{d_1 d_2}{\sigma}

Halley 通常 2 次迭代收敛而不是 3 次。代价是每步更多函数求值(你需要 f′′f'')。对预先算 vomma 值表的情形,值得交换(Press et al., 2007, 第 9.6 节)。

Householder 方法是推广。每加一阶加一个导数但改善收敛。三阶是实际的甜蜜点;更高阶没用,因为你的输入数据只有 4-5 位有意义。

收敛失败

给定合理起点,求解器应该收敛。四种情形打破这个。

1. 零附近 Vega 崩溃

当 σ→0\sigma \to 0,看涨价格接近内在价值,vega 崩溃。Newton 步变无穷。含义:如果市场价格在 Vm<ϵV_m < \epsilon 内于内在价值,IV “接近零”且无意义。别费心求解, 返回 σ\sigma 很小并标记。

2. 反问题

有时你反演的公式是错的。SPX 指数期权是欧式现金结算;用假设连续红利的 Black 公式给深度 ITM 行使价的 IV 错 1-2 vol。然后没有 σ\sigma 匹配市场因为模型本身偏。解法:在反演前切换到正确公式。没有花哨的数值修复。

3. 买卖价差套利

你有时能构造相互矛盾的”市场价格”:

V(K1)−2V(K2)+V(K3)<0V(K_1) - 2 V(K_2) + V(K_3) < 0

意味着中间价包含任何 IV 都不适合的日历套利。如果你求解得到无意义的东西,先查数据。

4. 价格数值噪声

如果市场价格 VmV_m 只有三位小数(零售链就这样),隐含波动率在第 4 位就不确定了。在噪声输入上迭代到 1e-10 容差是过度求解。知道你需要什么精度。

专用求解器

对非香草期权, 亚式、障碍、回望, 没有闭式存在,所以”隐含波动率”意思是解 σ\sigma 你会代入 Black-Scholes 以匹配等价 Black-Scholes 期权的价格。这些情形没有闭式可反演。用数值方法:二分 + Newton 混合稳健且毫秒级收敛。

对美式期权,你会在树或有限差分求解器里反演。每一步 IV 然后是一个函数求值,成本毫秒到秒。用 Brent 方法(Press et al., 2007, 第 10.2 节),它结合二分、割线和反二次插值。对你负担不起 Newton 发散的昂贵函数求值它表现良好。

def brent(f, a, b, tol=1e-7, maxiter=100):
    fa, fb = f(a), f(b)
    if fa * fb > 0: return None   # 需要框
    if abs(fa) < tol: return a
    if abs(fb) < tol: return b
    d = c = b
    for _ in range(maxiter):
        s = a if fa * (fb - fc) / (fc - fa) < 0 else b  # 反二次/割线
        # Brent:反二次、割线、二分的组合
        # ...(完整实现在 scipy.optimize.brent)

from scipy.optimize import brent 为你做这些。教训是 scipy.optimize 对几乎所有隐含波动率工作足够好;除非你需要否则别重新实现 Brent。

真实求解器栽跟头的边缘情形

“几乎内在价值”问题

当市场报价在内在价值几美分之内(即 Vm≈max⁡(S−Ke−rT,0)V_m \approx \max(S - K e^{-rT}, 0)),隐含波动率接近零。一些实现返回 σ=0\sigma = 0,其他返回很小值如 0.00010.0001。两派:那些把接近内在视为退化情形并截断的,那些返回 BS 给的任何小值。

约定:如果 Vm<S−Ke−rT+ϵV_m < S - K e^{-rT} + \epsilon 对很小 ϵ\epsilon,返回 σ=0.001\sigma = 0.001(本质上无 vol)。其他都是噪声。

“过于昂贵”问题

如果 VmV_m 比任何合理 BS 价格高很多,你不是有数据错误就是有非香草收益(数字期权、回望等)。BS 反演会失败或给出无意义值如 σ>5\sigma > 5(500%)。检查:VmV_m 对看涨应该上界于远期价格 F=SerTF = S e^{rT},下界于 0。否则,调查。

“卡住” Newton 问题

有时 Newton 收敛到非根, BS 函数的局部最小 ∂V/∂σ=0\partial V/\partial \sigma = 0 但 V≠VmV \neq V_m。修复:当 Newton 步停滞时,退回二分。许多生产实现包括回落检查:如果 ∣V(σn)−Vm∣>10−4|V(\sigma_n) - V_m| > 10^{-4} 连续 5 次迭代,切换方法。

“多根”问题

对奇异收益,多个 σ\sigma 可以匹配同一价格。支付 1ST>K\mathbb{1}_{S_T > K} 的数字看涨单调但在 S=KS = K 跳跃,根据数字的具体值给出歧义”隐含波动率”。对这些,没有唯一 IV。约定是不为这种工具算 IV,或者在带平滑价格-波动率映射的特定模型下算。

向量化求解器

在交易台,你需要同时反演上千 IV, 整个期权链、多标的、多到期。原生 Python 循环即使每次 30 微秒也慢。向量化:

def implied_vol_vectorized(market_prices, S, K_array, r, T_array, sigma0=0.2):
    sigma = np.full_like(K_array, sigma0, dtype=float)
    for it in range(50):
        d1 = (np.log(S / K_array) + (r + 0.5*sigma**2)*T_array) / (sigma*np.sqrt(T_array))
        d2 = d1 - sigma*np.sqrt(T_array)
        price = S * norm.cdf(d1) - K_array * np.exp(-r*T_array) * norm.cdf(d2)
        diff  = price - market_prices
        vega  = S * norm.pdf(d1) * np.sqrt(T_array)
        sigma = sigma - diff / np.maximum(vega, 1e-10)
        sigma = np.maximum(sigma, 1e-6)
        if np.max(np.abs(diff)) < 1e-8: break
    return sigma

向量化给你 10-100x 加速对比 Python 循环。对 SPX 链(8 个期限 2000 行使价),16,000 IV 大约 5 ms。

GPU:类似向量化通过 cupy 在 GPU 上不到 1ms。生产级期权链校准在大多数跑自动做市的公司是 GPU 加速的。

收敛诊断

求解器行为不端时,诊断是日志:

log = {
    'iterations': 0,
    'final_diff': diff,
    'final_sigma': sigma,
    'fallback_used': False,
    'reason': 'converged' if abs(diff) < tol else 'no_convergence'
}

如果 5% 期权需要退回二分,你的 Newton 种子不好。如果 1% 撞 σ\sigma 的下截断,你在处理一些奇怪报价。如果平均迭代次数是 8 而不是 3,你的起点远离答案,应该改进种子。

真实实现包括广泛的回落逻辑。典型模式:

  1. 用 Brenner-Subrahmanyam 作种子试 Newton。
  2. 如果 10 次迭代没收敛,退回二分。
  3. 如果二分有麻烦,记录并 NaN 出去。
  4. 定期审查 NaN 的行使价, 它们大概有糟糕的市场报价。

隐含波动率的方差

即使求解器收敛,隐含波动率也有来自输入数据的不确定性:

  • 市场价格买卖价差 1bps → 对 ATM 短期期权 vol 上 0.5bps,对深度 OTM 长期期权 5-10bps。
  • 报价陈旧 → vol 可能跟实时价差 2-3bps。
  • 供应商数据馈送延迟 → 100-500ms 陈旧。

换句话说,如果你的输入有 2bps 噪声,“精确” IV 到 6 位小数没意义。瞄准 3 位(3bps),用那个作为”官方” IV,别相信更多。

超出买卖价差,IV 计算有偏。Black-Scholes 不完美拟合市场,所以”IV”依赖于你平均哪些行使价、是用买价还是中间价、以及哪些行使价流动。每个台或定价服务可能为”同一个”期权报略不同的 IV。区别典型是 5-10bps, 烦但可管理。

关于偏态行使价 IV 拟合的一点

标准 Newton 假设 vega 非退化,对远离零和无穷的行使价没问题。接近两翼,vega 变小(小 ϕ(d1)\phi(d_1)),Newton 步爆炸。

修复:重新参数化。通过更稳定的不同形式解价格。例如,用对数价格 ln⁡(S/K)\ln(S/K) 而不是 KK;这自然因为你意识到 d1d_1 依赖于 ln⁡(S/K)\ln(S/K) 而非直接 KK。

另一个修复:当 vega 太小时,放弃 Newton 步切换二分。(Newton,二分)循环比坚持一个方法更快。

为什么有时二分宽度重要

二分需要框 [σa,σb][\sigma_a, \sigma_b] 其中 BS 函数变号。常见生产实现用 [σa,σb]=[0.001,5.0][\sigma_a, \sigma_b] = [0.001, 5.0]。对很低波动率期权(某些货币对交易 3−4%3-4\% IV),上界太高;对很高波动率期权(牛市中的加密、单只股票带 M&A 风险),5.0(500% IV)的上界只是勉强够。

实践中,设 [σa,σb]=[0.0005,10.0][\sigma_a, \sigma_b] = [0.0005, 10.0], 慷慨的界, 让二分处理极端情形。代价是更多迭代,但平均收敛仍然快。

方法比较

| 方法 | 收敛率 | 每次迭代成本 | 稳健性 | | | | -| | | Newton-Raphson | 二次 | 2 函数求值 | 中 | | Halley | 三次 | 3 函数求值 | 中高 | | 二分 | 线性 | 1 函数求值 | 完美 | | Brent | 超线性 (~1.6) | 1-2 函数求值 | 完美 | | 割线 | 超线性 (~1.6) | 1 函数求值 | 低 |

对闭式 BS 定价,Newton 是对的默认, 快、准、易向量化。对非闭式定价(美式、障碍、亚式),Brent 是安全选择。

生产代码实际长什么样

生产 IV 求解器有:

  1. 预计算种子(Brenner-Subrahmanyam 或来自先前校准的相关起点)。
  2. Newton 为主,Brent 回落。
  3. 向量化。
  4. 诊断日志。
  5. 边缘情形处理(负 vega、接近零 vol、市场怪事)。
  6. 容差和最大迭代控制每调用可调。
  7. 多模型变种(股票用 Black、期货用 Black-76、利率用 normal/Bachelier)。

参考实现存在于 vollib(Python)、QuantLib(C++ 和 Python),以及各种供应商库。如果从零开始,vollib 是体面参考;如果你有真账簿,你大概需要自己调过的版本。金融行业已经在 Newton 加二分开上做默认上达成一致。

生产代码实际长什么样

在真台账簿里,你有 GPU 上的向量化 IV 计算器同时处理整个链。输入是向量化的行使价 KK、SS、rr、TT、VmV_m。输出是向量化的 σ\sigma。整个流程是:

  1. 为每个行使价预计算 Brenner-Subrahmanyam。
  2. Newton 方法每个行使价 5 次迭代预算。
  3. 对发散行使价,退回 Brent。
  4. NaN 出去低于内在价值或有不可能价格的行使价。
  5. 收集,向风险仪表盘报告任何失败。

总时间:GPU 上 2000 行使价链不到 1 毫秒。CPU 上不到 1 秒。

教训:算法不是难的部分。难的部分是知道什么时候输入是坏的。


阅读清单。 Hull, Options, Futures, and Other Derivatives, 第 10 版,第 17 章(2018)关于市场报价和 IV。Press et al., Numerical Recipes, 第 3 版,第 9 章(2007)关于求根和 Brent。Brenner & Subrahmanyam, “A Simple Formula to Compute the Implied Volatility,” FAJ (1988)。Jäckel, Let me see…, 第 3 章(2015)关于现代最先进 ivi 稳健性材料。