Cap Implied Volatility Inversion: Price Units and Black Formula Check
Summary
The document troubleshoots finding Black implied volatility from a cap price using a numerical root solver. It identifies two issues: the supplied cap prices use percentage-style values while the pricing formula expects decimal values, and the implementation of the Black d2 term is incorrect because it omits a one-half factor in the variance contribution.
The accepted answer explains why the solver cannot find a root for the original inputs: as volatility grows, the caplet price approaches its forward rate, so the stated price is above the model’s attainable range. Rescaling the cap prices to decimal units makes the inversion feasible for the intended data. The example highlights that a solver’s failure can indicate inconsistent units or an impossible target price, not a need to switch algorithms. It is a specific implementation diagnosis rather than a general treatment of cap pricing or calibration.
Key ideas
- Black cap price inputs must use the units expected by the pricing formula.
- A caplet price approaches its forward rate as volatility increases, which limits attainable prices.
- A target above that limit has no volatility solution, so a root solver cannot bracket a root.
- The d2 expression in the example should include one-half of the variance term.
Tags
Full text
# Inverting the Black formula for Cap price to find Black implied volatility
# Inverting the Black formula for Cap price to find Black implied volatility
I'm solving the following problem as a part of Interest Rate Models class on Coursera
I'm having a hard time using nonlinear root solver to invert the Black formula for Cap price in order to obtain a Black implied volatility that will be used further for calibration purposes. My Python code is attached below.
```
import numpy as np
import math
import scipy
from scipy import optimize
from scipy import stats
Time = [0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4]
ForwardRates = [0.06, 0.08, 0.09, 0.10, 0.10, 0.10, 0.09, 0.09]
CapPrices = [0.20, 0.80, 1.20, 1.60]
SimpleRates = []
DiscountFactors = []
SimpleRates.append(ForwardRates[0])
DiscountFactors.append(1/(1+Time[0]*SimpleRates[0]))
for i in range(1, len(Time)):
SimpleRates.append(((1+0.5*ForwardRates[i])*(1+Time[i-1]*SimpleRates[i-1])-1)/Time[i])
DiscountFactors.append(1/(1+Time[i]*SimpleRates[i]))
SwapRates = []
for k in range(1, len(Time)):
s = sum(DiscountFactors[i] for i in range(1, k + 1))
SwapRates.append((DiscountFactors[0] - DiscountFactors[k])/(0.5*s))
def d1(i, sigma):
return (np.log(ForwardRates[i]/SwapRates[i])+0.5*math.pow(sigma, 2)*Time[i-1])/(sigma*math.sqrt(Time[i-1]))
def d2(i, sigma):
return (np.log(ForwardRates[i]/SwapRates[i])+math.pow(sigma, 2)*Time[i-1])/(sigma*math.sqrt(Time[i-1])) - sigma*math.sqrt(Time[i-1])
def caplet(begin, end, sigma):
return 0.5*DiscountFactors[end]*(ForwardRates[end]*scipy.stats.norm.cdf(d1(end, sigma))-SwapRates[end]*scipy.stats.norm.cdf(d2(end, sigma)))
def cap(end, sigma):
price = 0
for j in range(1, end + 1):
price += caplet(j - 1, j, sigma)
return price
s = scipy.optimize.bisect(lambda sigma: cap(1, sigma) - CapPrices[0], 0.0005, 0.5)
```
I'm getting an error that f(a) and f(b) must have different signs regardless of my interval choice. What particular root solving method should I use in Python to get around that? Is there anything else that I'm doing wrong?
## Answer by emot (score 1, accepted)
https://quant.stackexchange.com/a/66366
Notice that cap prices are given as %, therefore your vector of cap prices should be
```
CapPrices = [0.002, 0.008, 0.012, 0.016]
```
while you have
```
CapPrices = [0.20, 0.80, 1.20, 1.60]
```
The code should find solution without problem for these cap prices.
Notice that for your incorrectly given CapPrices there is no solution. Notice:
${Caplet(F,\sigma,t) \to F} $ as $\sigma \to \infty$. Therefore the highest caplet value is $F$, but you are trying to find solution for roughly $2.5F$ i.e. (0.20 / 0.08 = 2.5), therefore the solver fails.
I have also noticed that you have error in your formula in d2, it should be equal to:
```
return (np.log(ForwardRates[i]/SwapRates[i])+0.5*math.pow(sigma, 2)*Time[i-1])/(sigma*math.sqrt(Time[i-1])) - sigma*math.sqrt(Time[i-1])
```
while you have (missing multiplication by 0.5)
```
return (np.log(ForwardRates[i]/SwapRates[i])+math.pow(sigma, 2)*Time[i-1])/(sigma*math.sqrt(Time[i-1])) - sigma*math.sqrt(Time[i-1])
```Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.