I have the following code:
import QuantLib as ql
import numpy as np
reference_date = ql.Date(25,11,2019)
ql.Settings.instance().evaluationDate = reference_date
dates_3M = [ql.Date(25,11,2019), ql.Date(27,2,2020), ql.Date(27,3,2020), ql.Date(27,4,2020)]
rates_3M = [0.003, 0.0032, 0.0031, 0.0029]
dates_ois = [ql.Date(25,11,2019), ql.Date(26,11,2019), ql.Date(4,12,2019), ql.Date(11,12,2019),
ql.Date(18,12,2019), ql.Date(27,12,2019), ql.Date(27,1,2020), ql.Date(27,2,2020),
ql.Date(27,3,2020), ql.Date(27,4,2020)]
rates_ois = [0.0034, 0.0033, 0.0032, 0.0032, 0.0031, 0.003, 0.0029, 0.0031, 0.003, 0.0031]
calendar = ql.TARGET()
curve_ois = ql.ZeroCurve(dates_ois, rates_ois, ql.Actual360(), calendar, ql.Linear(), ql.Compounded, ql.Annual)
curve_ois_handle = ql.YieldTermStructureHandle(curve_ois)
curve_3M = ql.ZeroCurve(dates_3M, rates_3M, ql.Actual360(), calendar, ql.Linear(), ql.Compounded, ql.Annual)
curve_3M_handle = ql.YieldTermStructureHandle(curve_3M)
startDate = reference_date + 2
endDate = calendar.advance(startDate, ql.Period('5M')
schedule = ql.Schedule(startDate, endDate, ql.Period(ql.Quarterly), calendar, ql.ModifiedFollowing, ql.ModifiedFollowing, ql.DateGeneration.Forward, False)
nom = 1000000
capRate = 0.01
cap = ql.Cap(ql.IborLeg([nominal], schedule, ql.Euribor3M(curve_3M_handle)), [capRate])
vola = ql.QuoteHandle(ql.SimpleQuote(0.3064))
engine = ql.BachelierCapFloorEngine(curve_ois_handle, vola)
cap.setPricingEngine(engine)
cap_npv = cap.NPV()
This gives me the output 9596.7536... Now I am trying
cap.impliedVolatility(9596.7536, curve_ois_handle, 0.2)
hoping I would get the original vola 0.3064. However, now I don't get the error as before, but the error "root not bracketed f[1e-07,4] -> [-9,596735e+03, -9.4199483+03]"
Now let's say instead I try the following:
tenors_vola = [ql.Period('2M'), ql.Period('4M'), ql.Period('6M'), ql.Period('8M')]
rates_vola = [0.16, 0.2, 0.22, 0.25]
strikes = [0,1]
rates_vola_new = [[item, item] for item in rates_vola]
vola_surf = ql.CapFloorTermVolSurface(2, calendar, ql.ModifiedFollowing, tenors_vola,
strikes, rates_vola_new)
tmp1 = ql.OptionletStripper1(vola_surf, ql.Euribor3M(curve_3M_handle), type=ql.Normal)
tmp2 = ql.StrippedOptionletAdapter(tmp1)
vola_handle = ql.OptionletVolatilityStructureHandle(tmp2)
engine = ql.BachelierCapFloorEngine(curve_ois_handle, vola_handle)
cap.setPricingEngine(engine)
print(cap.NPV())
I have vola date for different tenors but not for different strikes, so I assume my vola surface to be constant in the strike dimension. Now in this specific example I don't get this code to run because I get the error "not enough points to interpolate: at least 2 required, 1 provided." However in my actual code it works and there the only difference is, that my ois, euribor and vola data is much longer for many more years. But I cannot copy that data here. Could someone please adjust my code such that it is running, including in the end the
cap.impliedVolatility(price, curve_ois_handle, vola_guess)
And how would I interpret the resulting implied volatility in this case? Because the NPV is computed depending on all the volas in the curve. Would the result of cap.impliedVolatility(...) be some kind of average?
I have the following code:
import QuantLib as ql
import numpy as np
reference_date = ql.Date(25,11,2019)
ql.Settings.instance().evaluationDate = reference_date
dates_3M = [ql.Date(25,11,2019), ql.Date(27,2,2020), ql.Date(27,3,2020), ql.Date(27,4,2020)]
rates_3M = [0.003, 0.0032, 0.0031, 0.0029]
dates_ois = [ql.Date(25,11,2019), ql.Date(26,11,2019), ql.Date(4,12,2019), ql.Date(11,12,2019),
ql.Date(18,12,2019), ql.Date(27,12,2019), ql.Date(27,1,2020), ql.Date(27,2,2020),
ql.Date(27,3,2020), ql.Date(27,4,2020)]
rates_ois = [0.0034, 0.0033, 0.0032, 0.0032, 0.0031, 0.003, 0.0029, 0.0031, 0.003, 0.0031]
calendar = ql.TARGET()
curve_ois = ql.ZeroCurve(dates_ois, rates_ois, ql.Actual360(), calendar, ql.Linear(), ql.Compounded, ql.Annual)
curve_ois_handle = ql.YieldTermStructureHandle(curve_ois)
curve_3M = ql.ZeroCurve(dates_3M, rates_3M, ql.Actual360(), calendar, ql.Linear(), ql.Compounded, ql.Annual)
curve_3M_handle = ql.YieldTermStructureHandle(curve_3M)
startDate = reference_date + 2
endDate = calendar.advance(startDate, ql.Period('5M')
schedule = ql.Schedule(startDate, endDate, ql.Period(ql.Quarterly), calendar, ql.ModifiedFollowing, ql.ModifiedFollowing, ql.DateGeneration.Forward, False)
nom = 1000000
capRate = 0.01
cap = ql.Cap(ql.IborLeg([nominal], schedule, ql.Euribor3M(curve_3M_handle)), [capRate])
vola = ql.QuoteHandle(ql.SimpleQuote(0.3064))
engine = ql.BachelierCapFloorEngine(curve_ois_handle, vola)
cap.setPricingEngine(engine)
cap_npv = cap.NPV()
This gives me the output 9596.7536... Now I am trying
cap.impliedVolatility(9596.7536, curve_ois_handle, 0.2)
hoping I would get the original vola 0.3064. However, now I don't get the error as before, but the error "root not bracketed f[1e-07,4] -> [-9,596735e+03, -9.4199483+03]"
Now let's say instead I try the following:
tenors_vola = [ql.Period('2M'), ql.Period('4M'), ql.Period('6M'), ql.Period('8M')]
rates_vola = [0.16, 0.2, 0.22, 0.25]
strikes = [0,1]
rates_vola_new = [[item, item] for item in rates_vola]
vola_surf = ql.CapFloorTermVolSurface(2, calendar, ql.ModifiedFollowing, tenors_vola,
strikes, rates_vola_new)
tmp1 = ql.OptionletStripper1(vola_surf, ql.Euribor3M(curve_3M_handle), type=ql.Normal)
tmp2 = ql.StrippedOptionletAdapter(tmp1)
vola_handle = ql.OptionletVolatilityStructureHandle(tmp2)
engine = ql.BachelierCapFloorEngine(curve_ois_handle, vola_handle)
cap.setPricingEngine(engine)
print(cap.NPV())
I have vola date for different tenors but not for different strikes, so I assume my vola surface to be constant in the strike dimension. Now in this specific example I don't get this code to run because I get the error "not enough points to interpolate: at least 2 required, 1 provided." However in my actual code it works and there the only difference is, that my ois, euribor and vola data is much longer for many more years. But I cannot copy that data here. Could someone please adjust my code such that it is running, including in the end the
cap.impliedVolatility(price, curve_ois_handle, vola_guess)
And how would I interpret the resulting implied volatility in this case? Because the NPV is computed depending on all the volas in the curve. Would the result of cap.impliedVolatility(...) be some kind of average?
Share Improve this question edited Mar 12 at 14:57 Maria Reinhardt asked Mar 11 at 8:50 Maria ReinhardtMaria Reinhardt 591 silver badge7 bronze badges 4- Volatility is an alias for a real. – Luigi Ballabio Commented Mar 11 at 14:30
- I tried with just the real number, but got the same error. – Maria Reinhardt Commented Mar 11 at 17:03
- Please post runnable code so we can check. The one above misses data and has syntax errors. – Luigi Ballabio Commented Mar 11 at 18:24
- I just edited my entire post, including example lists now with which the code runs for a constant vola. I do not get it to run with the vola curve, although it was running in my actual code where I use the real data, which I cannot provide here. In none of the 2 cases I manage to compute the implied Volatility, however. – Maria Reinhardt Commented Mar 12 at 14:59
1 Answer
Reset to default 1In your first example you're using the Bachelier engine, which means you're using normal volatility. When you're calling impliedVolatility
, though, it defaults to using lognormal (Black) volatility (you can see its declaration and its default parameters here). The inconsistency causes the error. If you specify that you want a normal implied volatility, as in
cap.impliedVolatility(cap_npv, curve_ois_handle, guess=0.2, type=ql.Normal)
you'll get back your input volatility.
In your second example, I'm not sure what should be adjusted since you say that it works with your data. In any case, the call to impliedVolatility
will give you the volatility that would give you the same price when used as a constant vol for all the caplets in the cap.