隐含波动率求解器:从 Newton 到流形无关方法
Newton-Raphson、二分、Householder 和 Brenner-Subrahmanyam 种子。为什么收敛会失败,以及怎么处理。
如果你做衍生品,你的第一段不平凡代码会是隐含波动率求解器。它是那些看起来无聊的工具之一, 用 作为未知数反演 Black-Scholes 公式, 但结果有半打失败模式会在周五下午 4 点券商 API 喂你垃圾行使价时咬你。
这篇文章走过标准方法、它们在哪里失败、以及当一切失败时该怎么做。
问题
你观察市场价格 。你想找 使得:
其中 是 Black-Scholes 公式(或你手头资产类别的变种)。这是一个一维求根问题。 的函数平滑、对任何实值度关于 单调(香草看涨和看跌的 vega 总是为正)、有界于 0 和远期价格之间。
一阶导数, 你需要快速收敛的东西, 正好是前一篇文章的 vegas。对看涨:
所以一次 Newton 迭代是:
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 崩溃。 很小时,vega 趋于零,Newton 步爆炸。反射 Newton(截断 然后重启)有帮助。
- 错符号。如果 跳成负数,下个迭代的
bs_call没意义。截断。 - 坏起点。种子差,Newton 会漫游到波动区。连续 5 次迭代高于 ATM vol 通常表示求解器在追错根或你在撞数值垃圾。
Brenner-Subrahmanyam 近似
迭代求解器需要起点。Brenner-Subrahmanyam (1988) 近似给闭式估计,对 ATM 和短期有效,大约 1% 相对误差:
对 ATM 期权,内在价值为零,所以:
数值上:ATM ,,。。真值依赖于 和 ,但对 ,真 BS 给 。所以 Brenner-Subrahmanyam 这里高约 24%, 做种子够用。
对 ITM 或 OTM 期权,包括内在价值:
更精细的版本(Chung, Chang & Wang, 2011)跨微笑给约 0.5% 误差。用它作起点 Newton 通常 3 次迭代收敛。
二分,Newton 的可靠表亲
Newton 工作时快但不工作时不可预测。二分相反:慢但防弹。你把根框在 ,检查符号,减半,重复。收敛线性(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 的三阶细化。公式:
其中 ,撇号是关于 的导数。对 Black-Scholes,所有导数从希腊字母可知:
Halley 通常 2 次迭代收敛而不是 3 次。代价是每步更多函数求值(你需要 )。对预先算 vomma 值表的情形,值得交换(Press et al., 2007, 第 9.6 节)。
Householder 方法是推广。每加一阶加一个导数但改善收敛。三阶是实际的甜蜜点;更高阶没用,因为你的输入数据只有 4-5 位有意义。
收敛失败
给定合理起点,求解器应该收敛。四种情形打破这个。
1. 零附近 Vega 崩溃
当 ,看涨价格接近内在价值,vega 崩溃。Newton 步变无穷。含义:如果市场价格在 内于内在价值,IV “接近零”且无意义。别费心求解, 返回 很小并标记。
2. 反问题
有时你反演的公式是错的。SPX 指数期权是欧式现金结算;用假设连续红利的 Black 公式给深度 ITM 行使价的 IV 错 1-2 vol。然后没有 匹配市场因为模型本身偏。解法:在反演前切换到正确公式。没有花哨的数值修复。
3. 买卖价差套利
你有时能构造相互矛盾的”市场价格”:
意味着中间价包含任何 IV 都不适合的日历套利。如果你求解得到无意义的东西,先查数据。
4. 价格数值噪声
如果市场价格 只有三位小数(零售链就这样),隐含波动率在第 4 位就不确定了。在噪声输入上迭代到 1e-10 容差是过度求解。知道你需要什么精度。
专用求解器
对非香草期权, 亚式、障碍、回望, 没有闭式存在,所以”隐含波动率”意思是解 你会代入 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。
真实求解器栽跟头的边缘情形
“几乎内在价值”问题
当市场报价在内在价值几美分之内(即 ),隐含波动率接近零。一些实现返回 ,其他返回很小值如 。两派:那些把接近内在视为退化情形并截断的,那些返回 BS 给的任何小值。
约定:如果 对很小 ,返回 (本质上无 vol)。其他都是噪声。
“过于昂贵”问题
如果 比任何合理 BS 价格高很多,你不是有数据错误就是有非香草收益(数字期权、回望等)。BS 反演会失败或给出无意义值如 (500%)。检查: 对看涨应该上界于远期价格 ,下界于 0。否则,调查。
“卡住” Newton 问题
有时 Newton 收敛到非根, BS 函数的局部最小 但 。修复:当 Newton 步停滞时,退回二分。许多生产实现包括回落检查:如果 连续 5 次迭代,切换方法。
“多根”问题
对奇异收益,多个 可以匹配同一价格。支付 的数字看涨单调但在 跳跃,根据数字的具体值给出歧义”隐含波动率”。对这些,没有唯一 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% 撞 的下截断,你在处理一些奇怪报价。如果平均迭代次数是 8 而不是 3,你的起点远离答案,应该改进种子。
真实实现包括广泛的回落逻辑。典型模式:
- 用 Brenner-Subrahmanyam 作种子试 Newton。
- 如果 10 次迭代没收敛,退回二分。
- 如果二分有麻烦,记录并 NaN 出去。
- 定期审查 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 变小(小 ),Newton 步爆炸。
修复:重新参数化。通过更稳定的不同形式解价格。例如,用对数价格 而不是 ;这自然因为你意识到 依赖于 而非直接 。
另一个修复:当 vega 太小时,放弃 Newton 步切换二分。(Newton,二分)循环比坚持一个方法更快。
为什么有时二分宽度重要
二分需要框 其中 BS 函数变号。常见生产实现用 。对很低波动率期权(某些货币对交易 IV),上界太高;对很高波动率期权(牛市中的加密、单只股票带 M&A 风险),5.0(500% IV)的上界只是勉强够。
实践中,设 , 慷慨的界, 让二分处理极端情形。代价是更多迭代,但平均收敛仍然快。
方法比较
| 方法 | 收敛率 | 每次迭代成本 | 稳健性 | | | | -| | | Newton-Raphson | 二次 | 2 函数求值 | 中 | | Halley | 三次 | 3 函数求值 | 中高 | | 二分 | 线性 | 1 函数求值 | 完美 | | Brent | 超线性 (~1.6) | 1-2 函数求值 | 完美 | | 割线 | 超线性 (~1.6) | 1 函数求值 | 低 |
对闭式 BS 定价,Newton 是对的默认, 快、准、易向量化。对非闭式定价(美式、障碍、亚式),Brent 是安全选择。
生产代码实际长什么样
生产 IV 求解器有:
- 预计算种子(Brenner-Subrahmanyam 或来自先前校准的相关起点)。
- Newton 为主,Brent 回落。
- 向量化。
- 诊断日志。
- 边缘情形处理(负 vega、接近零 vol、市场怪事)。
- 容差和最大迭代控制每调用可调。
- 多模型变种(股票用 Black、期货用 Black-76、利率用 normal/Bachelier)。
参考实现存在于 vollib(Python)、QuantLib(C++ 和 Python),以及各种供应商库。如果从零开始,vollib 是体面参考;如果你有真账簿,你大概需要自己调过的版本。金融行业已经在 Newton 加二分开上做默认上达成一致。
生产代码实际长什么样
在真台账簿里,你有 GPU 上的向量化 IV 计算器同时处理整个链。输入是向量化的行使价 、、、、。输出是向量化的 。整个流程是:
- 为每个行使价预计算 Brenner-Subrahmanyam。
- Newton 方法每个行使价 5 次迭代预算。
- 对发散行使价,退回 Brent。
- NaN 出去低于内在价值或有不可能价格的行使价。
- 收集,向风险仪表盘报告任何失败。
总时间: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 稳健性材料。