Calibrating the G2++ Model with Negative Rates and Cap Volatility
Summary
The document describes a QuantLib calibration problem for the G2++ interest-rate model in a negative-rate environment. The reported error arises because the cap helper uses shifted lognormal volatility with zero displacement, which requires the strike plus displacement to be nonnegative. The response says to use normal volatility or supply a suitable shift parameter.
An example shows cap helpers configured for normal volatility, followed by model calibration and calculation of model-implied volatilities. It reports fitted parameters and volatilities for a sample curve and cap-volatility set. The example demonstrates a practical configuration choice, but it is not a general calibration study: results depend on the specific market inputs, model setup, and QuantLib conventions used.
Key ideas
- The reported calibration error comes from a nonnegative-strike requirement under shifted lognormal volatility with zero displacement.
- Normal cap volatilities are suggested for handling negative rates in the example.
- A shift parameter is offered as an alternative to normal volatility.
- The example calibrates G2++ using cap helpers and a tree pricing engine, then computes model-implied volatilities.
- The displayed calibration outputs apply only to the sample inputs and setup.
Tags
Full text
# Python Quantlib G2 calibration with negative interest
# Python Quantlib G2 calibration with negative interest
I am currently calibrating the G2++ in Python with Quantlib in negative interest rate environments with cap volatilities. Unfortunately, this does not work as intended and I get error messages:RuntimeError: strike + displacement (-0.00425602 + 0) must be non-negative.
According to Brigo & Mercurio (2006), however, negative interest rates are generally possible in G2++. Is there a trick here or does anyone have experience with this adjustment?
Thank you very much! Please find attached the code. I define yield_curves as list of quantlib yield_curves for serveral points of time and cap_vols_list as quantlib QuoteHandle elements.
final_results = []
for i in range(len(yield_curves)-183, len(yield_curves)-182): curve_date, ts_handle = yield_curves[i] cap_date, quotes = cap_vols_list[i]
```
# Check for same dates
if curve_date == cap_date:
ql.Settings.instance().evaluationDate = curve_date
# Setup Model and Optimization
model = ql.G2(ts_handle)
optimization_method = ql.LevenbergMarquardt(1e-8, 1e-8, 1e-8)
end_criteria = ql.EndCriteria(1000, 500, 1e-8, 1e-8, 1e-8)
yts = ts_handle
index = ql.Euribor6M(ts_handle)
# Setup Cap Helpers and Pricing Engine
helpers = [ql.CapHelper(i, j, index, ql.Annual, ql.Actual360(), False, yts) for i, j in zip(periods, quotes)] # Hier kann auch ql.Semiannual verwendet wreden
for h, i in zip(helpers, quotes):
h.setPricingEngine(ql.TreeCapFloorEngine(model, 20))
# Model Calibration
model.calibrate(helpers, optimization_method, end_criteria)
# Empty List for results
results = []
# Calculate Model Vola
for i, helper in enumerate(helpers):
maturity = periods[i]
market_vol = quotes[i].value()
model_value = helper.modelValue()
model_vol = helper.impliedVolatility(model_value, 1e-6, 300, 0.0, 2.0)
# Calculate Error
percent_diff = (model_vol - market_vol) / market_vol * 100
results.append([curve_date, maturity, market_vol, model_vol, percent_diff])
# Append Results
final_results.extend(results)
```
## Answer by user35980 (score 1)
https://quant.stackexchange.com/a/78234
Using normal vols should work (default is shiftedLN with 0 shift in Quantlib). Alternatively use a shift parameter. For example:
```
usd3mcurve=ql.YieldTermStructureHandle(ql.FlatForward(2,ql.UnitedStates(0),-0.05,ql.Actual360()))
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.0045)),ql.QuoteHandle(ql.SimpleQuote(0.0055)),ql.QuoteHandle(ql.SimpleQuote(0.0065)),
ql.QuoteHandle(ql.SimpleQuote(0.0055)),ql.QuoteHandle(ql.SimpleQuote(0.0035)),ql.QuoteHandle(ql.SimpleQuote(0.0025))]
index = ql.USDLibor(ql.Period('3m'),usd3mcurve)
helpers = [ql.CapHelper(i, j, index, ql.Quarterly, ql.Actual360(), False, usd3mcurve,0,ql.Normal) 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())
model_vols=[]
for h in helpers:
model_vols.append(h.impliedVolatility(h.modelValue(),1e-5,50,0,4))
print(model_vols)
```
gives parameters
```
[ 0.241215; 0.00715896; 0.243035; 0.00408839; -0.508568 ]
```
and vols:
```
[0.005542885387877183, 0.005189063505819368, 0.0044364313964000875, 0.004039502609889546, 0.003556074388616398, 0.002984907583591529]
```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.