|
From: Amy C. <amy...@gm...> - 2024-03-27 15:56:25
|
Hi All,
I am struggling to calculate the implied volatility for a Euribor6m
Swaption.
I was given single period swap rates and swap rates for ESTR, as well as
Euribor 6M swap rates. I have created rate helpers from these and
constructed a curve for each.
Following this, I have inserted the values I was giving for a 10Y10Y
swaption. The expected volatility in normal terms is 75.2 Bps. However, I
get 98.22 as a result.
Is there anything special about the discount curve fed into the pricing
engine for swaptions?
Please see my code below.
Thank you,
Amy
*Output:*
NPV: -19.210268277252
Fixed Leg NPV: 1723.3766390105964 Floating Leg NPV: -1742.5869072878484 Fair
Rate: 0.02608758943943771 Maturity Date: March 7th, 2044 Start Date: March
6th, 2034 Quantlib Calc: 98.22 Bps Expected Val: 75.2 Bps
*Input:*
import QuantLib as ql
ql.Settings.instance().evaluationDate = ql.Date(29, 2, 2024)
estr_sps = [
{
"start_date": ql.Date(20, 3, 2024),
"end_date": ql.Date(20, 6, 2024),
"rate": 0.038,
},
{
"start_date": ql.Date(17, 7, 2024),
"end_date": ql.Date(17, 10, 2024),
"rate": 0.0346,
},
{
"start_date": ql.Date(20, 11, 2024),
"end_date": ql.Date(20, 2, 2025),
"rate": 0.03,
},
{
"start_date": ql.Date(19, 3, 2025),
"end_date": ql.Date(19, 6, 2025),
"rate": 0.026,
},
{
"start_date": ql.Date(16, 7, 2025),
"end_date": ql.Date(16, 10, 2025),
"rate": 0.0246,
},
{
"start_date": ql.Date(19, 11, 2025),
"end_date": ql.Date(19, 2, 2026),
"rate": 0.023,
},
{
"start_date": ql.Date(18, 3, 2026),
"end_date": ql.Date(18, 6, 2026),
"rate": 0.0229,
},
{
"start_date": ql.Date(15, 7, 2026),
"end_date": ql.Date(15, 10, 2026),
"rate": 0.0227,
},
{
"start_date": ql.Date(18, 11, 2026),
"end_date": ql.Date(18, 2, 2027),
"rate": 0.022,
},
{
"start_date": ql.Date(17, 3, 2027),
"end_date": ql.Date(17, 6, 2027),
"rate": 0.0223,
},
{
"start_date": ql.Date(21, 7, 2027),
"end_date": ql.Date(21, 10, 2027),
"rate": 0.0223,
},
]
estr_swaps = [
{"periods": "48M", "rate": 0.0264},
{"periods": "60M", "rate": 0.0258},
{"periods": "72M", "rate": 0.0255},
{"periods": "84M", "rate": 0.0253},
{"periods": "96M", "rate": 0.0253},
{"periods": "108M", "rate": 0.0254},
{"periods": "120M", "rate": 0.0256},
{"periods": "132M", "rate": 0.0256},
{"periods": "144M", "rate": 0.0259},
{"periods": "156M", "rate": 0.0268},
{"periods": "168M", "rate": 0.0262},
{"periods": "180M", "rate": 0.0263},
{"periods": "192M", "rate": 0.0263},
{"periods": "204M", "rate": 0.0263},
{"periods": "216M", "rate": 0.0262},
{"periods": "228M", "rate": 0.026},
{"periods": "240M", "rate": 0.026},
{"periods": "300M", "rate": 0.025},
{"periods": "360M", "rate": 0.0242},
{"periods": "420M", "rate": 0.0235},
{"periods": "480M", "rate": 0.0229},
{"periods": "540M", "rate": 0.0223},
{"periods": "600M", "rate": 0.0217},
{"periods": "720M", "rate": 0.021},
{"periods": "840M", "rate": 0.0203},
]
euribor6m_swaps = [
{"periods": "12M", "rate": 0.0374},
{"periods": "24M", "rate": 0.0324},
{"periods": "36M", "rate": 0.03},
{"periods": "48M", "rate": 0.0288},
{"periods": "60M", "rate": 0.0279},
{"periods": "72M", "rate": 0.0276},
{"periods": "84M", "rate": 0.0273},
{"periods": "96M", "rate": 0.0272},
{"periods": "108M", "rate": 0.0272},
{"periods": "120M", "rate": 0.0273},
{"periods": "132M", "rate": 0.0273},
{"periods": "144M", "rate": 0.0274},
{"periods": "156M", "rate": 0.0274},
{"periods": "168M", "rate": 0.0274},
{"periods": "180M", "rate": 0.0274},
{"periods": "192M", "rate": 0.0273},
{"periods": "204M", "rate": 0.0272},
{"periods": "216M", "rate": 0.027},
{"periods": "228M", "rate": 0.0268},
{"periods": "240M", "rate": 0.0266},
{"periods": "300M", "rate": 0.0254},
{"periods": "360M", "rate": 0.0243},
{"periods": "420M", "rate": 0.0235},
{"periods": "480M", "rate": 0.0226},
{"periods": "540M", "rate": 0.0218},
{"periods": "600M", "rate": 0.021},
{"periods": "720M", "rate": 0.0202},
{"periods": "840M", "rate": 0.0194},
]
estr_helpers = list()
for sps in estr_sps:
estr_helpers.append(
ql.DatedOISRateHelper(
sps["start_date"],
sps["end_date"],
ql.QuoteHandle(ql.SimpleQuote(sps["rate"])),
ql.Estr(),
)
)
for swap in estr_swaps:
estr_helpers.append(
ql.OISRateHelper(
2,
ql.Period(swap["periods"]),
ql.QuoteHandle(ql.SimpleQuote(swap["rate"])),
ql.Estr(),
)
)
estr_curve = ql.PiecewiseLogCubicDiscount(2, ql.TARGET(), estr_helpers, ql.
Actual360())
estr_curve.enableExtrapolation()
estr_curve_handle = ql.YieldTermStructureHandle(estr_curve)
euribor6m_swaps_helpers = list()
euribor6m = ql.Euribor6M()
for swap in euribor6m_swaps:
euribor6m_swaps_helpers.append(
ql.SwapRateHelper(
ql.QuoteHandle(ql.SimpleQuote(swap["rate"])),
ql.Period(swap["periods"]),
ql.TARGET(),
ql.Annual,
ql.Unadjusted,
ql.Actual360(),
euribor6m,
)
)
euribor6m_curve = ql.PiecewiseLogCubicDiscount(
2, ql.TARGET(), euribor6m_swaps_helpers, ql.Actual360()
)
euribor6m_curve.enableExtrapolation()
euribor6m_curve_handle = ql.YieldTermStructureHandle(euribor6m_curve)
exercise_date = ql.TARGET().advance(
ql.Settings.instance().evaluationDate, ql.Period(120, ql.Months)
)
swap = ql.MakeVanillaSwap(
ql.Period(120, ql.Months),
ql.Euribor6M(euribor6m_curve_handle),
0.0258,
ql.Period(120, ql.Months),
Nominal=10_000,
pricingEngine=ql.DiscountingSwapEngine(estr_curve_handle),
swapType=ql.Swap.Receiver,
)
print(f"NPV: {swap.NPV()}")
print(f"Fixed Leg NPV: {swap.fixedLegNPV()}")
print(f"Floating Leg NPV: {swap.floatingLegNPV()}")
print(f"Fair Rate: {swap.fairRate()}")
print(f"Maturity Date: {swap.maturityDate()}")
print(f"Start Date: {swap.startDate()}")
exercise = ql.EuropeanExercise(swap.startDate())
swaption = ql.Swaption(swap, exercise)
volatility = 0.2
vol_handle = ql.QuoteHandle(ql.SimpleQuote(volatility))
bachelier_model = ql.BachelierSwaptionEngine(estr_curve_handle, vol_handle)
swaption.setPricingEngine(bachelier_model)
normal_vol = (
swaption.impliedVolatility(
819,
estr_curve_handle,
guess=0.002,
type=ql.Normal,
)
* 10_000
)
print("\n")
print(f"Quantlib Calc: {round(normal_vol,2)} Bps")
print(f"Expected Val: 75.2 Bps")
|