2
$\begingroup$

I am trying to calibrate the G2++ model to interest rate caps using the Quantlib library in Python. I have the problem that my optimization always stops with the starting values. So probably either my setup for the cap helpers or the optimization itself is wrong. Unfortunately, I can't get any further here and would be delighted to have some help.

I hope that someone has experience with Quantlib and can help. Attached is my code with some sample data.

Thank you very much!

import QuantLib as ql

# Initial settings
valuation_date = ql.Date(29, 1, 2024)
ql.Settings.instance().evaluationDate = valuation_date

# Setting up yield curve
swap_maturities = [ql.Period(6, ql.Months), ql.Period(1, ql.Years), ql.Period(2, ql.Years),
ql.Period(3, ql.Years), ql.Period(4, ql.Years), ql.Period(5, ql.Years),
ql.Period(6, ql.Years), ql.Period(7, ql.Years), ql.Period(8, ql.Years),
ql.Period(9, ql.Years), ql.Period(10, ql.Years), ql.Period(12, ql.Years),
ql.Period(15, ql.Years), ql.Period(20, ql.Years), ql.Period(25, ql.Years),
ql.Period(30, ql.Years)]
dates = [valuation_date + maturity for maturity in swap_maturities]

yields = [0.03873, 0.03524, 0.02955, 0.02745, 0.02662, 0.02631, 0.02625, 0.02631,
0.02644, 0.02661, 0.02680, 0.02720, 0.02749, 0.02696, 0.02598, 0.02499]

day_count = ql.Actual360()
calendar = ql.Germany()
interpolation = ql.Linear()
compounding = ql.Compounded
compounding_frequency = ql.Semiannual

term_structure = ql.ZeroCurve(dates, yields, day_count, calendar,
interpolation, compounding, compounding_frequency)
ts_handle = ql.YieldTermStructureHandle(term_structure)

# Cap Vols
market_vols = {
1: 0.9081,
2: 1.0488,
3: 1.0533,
4: 1.0391,
5: 1.0232,
6: 1.008,
7: 0.9926,
8: 0.978,
9: 0.9633,
10: 0.9498,
12: 0.9246,
15: 0.8901,
20: 0.8439,
25: 0.8091,
}


# Set up Model
model = ql.G2(ts_handle)

# Create Cap-Objects
#euribor_index = ql.Euribor6M(ts_handle)
cap_helpers = []
start_date = valuation_date + ql.Period(6,ql.Months)

for maturity, vol in market_vols.items():
period = ql.Period(maturity, ql.Years)
end_date = calendar.advance(valuation_date, period)
schedule = ql.Schedule(start_date, end_date, ql.Period(ql.Annual), calendar,
ql.Unadjusted, ql.Unadjusted, ql.DateGeneration.Forward, False)

# Calculation of Strike Rate
fwd_rate = term_structure.forwardRate(start_date, end_date, day_count, compounding, compounding_frequency).rate()
strike_quote = ql.SimpleQuote(fwd_rate)


# Vola quote
vol_quote = ql.QuoteHandle(ql.SimpleQuote(vol))

# Setup of Cap Helper
helper = ql.CapHelper(period, vol_quote, strike_quote, ql.Annual, day_count, False, ts_handle)
helper.setPricingEngine(ql.BlackCapFloorEngine(ts_handle, vol_quote))
cap_helpers.append(helper)

# Setup calibration method
optimization_method = ql.LevenbergMarquardt(1e-8, 1e-8, 1e-8)

# Definition of ending criteria
end_criteria = ql.EndCriteria(1000, 500, 1e-8, 1e-8, 1e-8)

# Calibration
model.calibrate(cap_helpers, optimization_method, end_criteria)

# Results
a, sigma, b, eta, rho = model.params()
print(f"G2++ Model parameters: a = {a}, sigma = {sigma}, b = {b}, eta = {eta}, rho = {rho}")
```
$\endgroup$

1 Answer 1

3
$\begingroup$

I think you should use something like TreeCapFloorEngine for cap/floor calibration with G2. BlackCapFloorEngine is for closed form Black pricing. For example,

model=ql.G2(usd3mcurve)
optimization_method = ql.LevenbergMarquardt(1e-8, 1e-8, 1e-8)
end_criteria = ql.EndCriteria(1000, 500, 1e-8, 1e-8, 1e-8)
periods = [ql.Period('1y'),ql.Period('2y'),ql.Period('5y'),ql.Period('7y'),ql.Period('10Y'),ql.Period('15Y')]
quotes = [ql.QuoteHandle(ql.SimpleQuote(0.45)),ql.QuoteHandle(ql.SimpleQuote(0.55)),ql.QuoteHandle(ql.SimpleQuote(0.65)),
          ql.QuoteHandle(ql.SimpleQuote(0.55)),ql.QuoteHandle(ql.SimpleQuote(0.35)),ql.QuoteHandle(ql.SimpleQuote(0.25))]
yts = usd3mcurve
index = ql.USDLibor(ql.Period('3m'),usd3mcurve)

helpers = [ql.CapHelper(i, j, index, ql.Quarterly, ql.Actual360(), False, yts) for i,j in zip(periods,quotes)]
for h in helpers:
    h.setPricingEngine(ql.TreeCapFloorEngine(model,20))

model.calibrate(helpers,optimization_method,end_criteria)
print(model.params())

returns:

[ 0.0514912; 0.00746613; 0.0497814; 0.011121; -0.39568 ]
$\endgroup$
2
  • $\begingroup$ thanks a lot for this hint! Now it works perfectly. Maybe another question regarding this: What strikes is this using now? Are this ATM Caps then? $\endgroup$
    – Marc157
    Commented Feb 1 at 8:43
  • $\begingroup$ yes, helpers calibrate to atm $\endgroup$
    – user35980
    Commented Feb 1 at 9:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.