Skip to content
All library documents

Calibrating the G2++ Rate Model to Cap Volatilities in QuantLib

Article Quant Q&A · Author: Marc157

Summary

The document describes an attempt to calibrate the two-factor G2++ interest-rate model to cap volatility quotes using QuantLib's Python interface. The example builds a yield curve, creates cap helpers from maturities and volatility inputs, sets an optimization method and stopping criteria, and calls the model calibration routine. The reported issue is that optimization appears to leave parameters at their initial values.

The accepted response points to a mismatch in pricing engines: the Black cap-floor engine prices caps using a closed-form Black framework, whereas calibration of G2 is suggested to use a model-based tree engine. Its example creates an index and cap helpers, assigns a tree cap-floor pricing engine, then calibrates and prints the resulting parameters. This is practical implementation advice, but the snippet is illustrative and uses different market inputs and curve setup from the original example. It does not show a diagnostic analysis of the original data, discuss parameter identifiability or quote conventions, or establish that the sample calibration is robust for other setups.

Key ideas

  • Cap helpers need a pricing engine consistent with the model being calibrated.
  • The response recommends a tree-based cap-floor engine for G2 calibration rather than a Black engine.
  • The example calibrates model parameters to cap volatility quotes using QuantLib helpers and an optimizer.
  • Its code uses a separate curve and sample inputs, so it does not fully diagnose the original setup.
  • Calibration outcomes also depend on market quotes, conventions, and model identifiability.

Tags

Full text
# Python Quantlib for the calibration of interest rate caps


# Python Quantlib for the calibration of interest rate caps












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}")
```
```

## Answer by user35980 (score 3, accepted)

https://quant.stackexchange.com/a/78168

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 ]
```

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.