Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
infjybuilder.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2020, 2022 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
39#include <ql/cashflows/yoyinflationcoupon.hpp>
40#include <ql/pricingengines/swap/discountingswapengine.hpp>
41#include <ql/time/daycounters/thirty360.hpp>
42#include <boost/range/join.hpp>
43
54using QuantLib::DiscountingSwapEngine;
55using QuantLib::Thirty360;
56using QuantLib::YoYInflationCoupon;
57using std::lower_bound;
58using std::set;
59using std::string;
60
61namespace {
62
63// Used as comparator in various sets below.
64struct CloseCmp {
65 bool operator() (QuantLib::Time s, QuantLib::Time t) const {
66 return s < t && !QuantLib::close(s, t);
67 }
68};
69
70}
71
72namespace ore {
73namespace data {
74
76
77InfJyBuilder::InfJyBuilder(const QuantLib::ext::shared_ptr<Market>& market, const QuantLib::ext::shared_ptr<InfJyData>& data,
78 const string& configuration, const string& referenceCalibrationGrid,
79 const bool dontCalibrate)
80 : market_(market), configuration_(configuration), data_(data), referenceCalibrationGrid_(referenceCalibrationGrid),
81 dontCalibrate_(dontCalibrate), marketObserver_(QuantLib::ext::make_shared<MarketObserver>()),
82 zeroInflationIndex_(*market_->zeroInflationIndex(data_->index(), configuration_)) {
83
84 LOG("InfJyBuilder: building model for inflation index " << data_->index());
85
86 // Get rate curve
87 rateCurve_ = market_->discountCurve(zeroInflationIndex_->currency().code(), configuration_);
88
89 // Register with market observables except volatilities
91 marketObserver_->registerWith(rateCurve_);
93
94 // Register the model builder with the market observer
95 registerWith(marketObserver_);
96
97 // Notify observers of all market data changes, not only when not calculated
98 alwaysForwardNotifications();
99
100 // Build the calibration instruments
102
103 // Create the JY parameterisation.
104 parameterization_ = QuantLib::ext::make_shared<QuantExt::InfJyParameterization>(
106}
107
109 return data_->index();
110}
111
112QuantLib::ext::shared_ptr<QuantExt::InfJyParameterization> InfJyBuilder::parameterization() const {
113 calculate();
114 return parameterization_;
115}
116
118 calculate();
119 return realRateBasket_;
120}
121
123 calculate();
124 return indexBasket_;
125}
126
128 return (data_->realRateVolatility().calibrate() || data_->realRateReversion().calibrate() ||
129 data_->indexVolatility().calibrate()) &&
130 (marketObserver_->hasUpdated(false) || forceCalibration_ || pricesChanged(false));
131}
132
134 if (requiresRecalibration()) {
136 }
137}
138
140 marketObserver_->hasUpdated(true);
141 pricesChanged(true);
142}
143
145 forceCalibration_ = true;
147 forceCalibration_ = false;
148}
149
151
152 // If calibration type is None, don't build any baskets.
153 if (data_->calibrationType() == CalibrationType::None) {
154 DLOG("InfJyBuilder: calibration type is None so no calibration baskets built.");
155 return;
156 }
157
158 const auto& cbs = data_->calibrationBaskets();
159
160 // If calibration type is BestFit, check that we have at least one calibration basket. Build up to a maximum of
161 // two calibration baskets. Log a warning if more than two are given. Arbitrarily assign the built baskets to
162 // the realRateBasket_ and indexBasket_ members. They will be combined again in any case for BestFit calibration.
163 if (data_->calibrationType() == CalibrationType::BestFit) {
164 QL_REQUIRE(cbs.size() > 0, "InfJyBuilder: calibration type is BestFit but no calibration baskets provided.");
165 rrInstActive_ = vector<bool>(cbs[0].instruments().size(), false);
167 if (cbs.size() > 1) {
168 indexInstActive_ = vector<bool>(cbs[1].instruments().size(), false);
170 }
171 if (cbs.size() > 2)
172 WLOG("InfJyBuilder: only 2 calibration baskets can be processed but " << cbs.size() <<
173 " were supplied. The extra baskets are ignored.");
174 return;
175 }
176
177 // Make sure that the calibration type is now Bootstrap.
178 QL_REQUIRE(data_->calibrationType() == CalibrationType::Bootstrap, "InfJyBuilder: expected the calibration " <<
179 "type to be one of None, BestFit or Bootstrap.");
180
181 const VolatilityParameter& idxVolatility = data_->indexVolatility();
182 const ReversionParameter& rrReversion = data_->realRateReversion();
183 const VolatilityParameter& rrVolatility = data_->realRateVolatility();
184
185 // Firstly, look at the inflation index portion i.e. are we calibrating it.
186 if (idxVolatility.calibrate()) {
187
188 DLOG("InfJyBuilder: building calibration basket for JY index bootstrap calibration.");
189
190 // If we are not calibrating the real rate portion, then we expect exactly one calibration basket. Otherwise
191 // we need to find a basket with the 'Index' parameter.
192 if (!rrReversion.calibrate() && !rrVolatility.calibrate()) {
193 QL_REQUIRE(cbs.size() == 1, "InfJyBuilder: calibrating only JY index volatility using Bootstrap so " <<
194 "expected exactly one basket but got " << cbs.size() << ".");
195 const auto& cb = cbs[0];
196 if (!cb.parameter().empty() && cb.parameter() != "Index") {
197 WLOG("InfJyBuilder: calibrating only JY index volatility using Bootstrap so expected the " <<
198 "calibration basket parameter to be 'Index' but got '" << cb.parameter() << "'.");
199 }
200 indexInstActive_ = vector<bool>(cb.instruments().size(), false);
202 } else {
203 DLOG("InfJyBuilder: need a calibration basket with parameter equal to 'Index'.");
204 const auto& cb = calibrationBasket("Index");
205 indexInstActive_ = vector<bool>(cb.instruments().size(), false);
207 }
208 }
209
210 // Secondly, look at the real rate portion i.e. are we calibrating it.
211 if (rrReversion.calibrate() || rrVolatility.calibrate()) {
212
213 DLOG("InfJyBuilder: building calibration basket for JY real rate bootstrap calibration.");
214 QL_REQUIRE(!(rrReversion.calibrate() && rrVolatility.calibrate()), "InfJyBuilder: calibrating both the " <<
215 "real rate reversion and real rate volatility using Bootstrap is not supported.");
216
217 // If we are not calibrating the index portion, then we expect exactly one calibration basket. Otherwise
218 // we need to find a basket with the 'RealRate' parameter.
219 if (!idxVolatility.calibrate()) {
220 QL_REQUIRE(cbs.size() == 1, "InfJyBuilder: calibrating only JY real rate using Bootstrap so " <<
221 "expected exactly one basket but got " << cbs.size() << ".");
222 const auto& cb = cbs[0];
223 if (!cb.parameter().empty() && cb.parameter() != "RealRate") {
224 WLOG("InfJyBuilder: calibrating only JY real rate using Bootstrap so expected the " <<
225 "calibration basket parameter to be 'RealRate' but got '" << cb.parameter() << "'.");
226 }
227 rrInstActive_ = vector<bool>(cb.instruments().size(), false);
229 } else {
230 DLOG("InfJyBuilder: need a calibration basket with parameter equal to 'RealRate'.");
231 const auto& cb = calibrationBasket("RealRate");
232 rrInstActive_ = vector<bool>(cb.instruments().size(), false);
234 }
235 }
236
237}
238
240 vector<bool>& active, Array& expiries, bool forRealRateReversion) const {
241
242 QL_REQUIRE(!cb.empty(), "InfJyBuilder: calibration basket should not be empty.");
243
244 const auto& ci = cb.instruments();
245 QL_REQUIRE(ci.size() == active.size(), "InfJyBuilder: expected the active instruments vector " <<
246 "size to equal the number of calibration instruments");
247 fill(active.begin(), active.end(), false);
248
249 if (cb.instrumentType() == "CpiCapFloor") {
250 return buildCpiCapFloorBasket(cb, active, expiries);
251 } else if (cb.instrumentType() == "YoYCapFloor") {
252 return buildYoYCapFloorBasket(cb, active, expiries);
253 } else if (cb.instrumentType() == "YoYSwap") {
254 return buildYoYSwapBasket(cb, active, expiries, forRealRateReversion);
255 } else {
256 QL_FAIL("InfJyBuilder: expected calibration instrument to be one of CpiCapFloor, YoYCapFloor or YoYSwap");
257 }
258}
259
261 vector<bool>& active, Array& expiries) const {
262
263 DLOG("InfJyBuilder: start building the CPI cap floor calibration basket.");
264
265 QL_REQUIRE(!cpiVolatility_.empty(), "InfJyBuilder: need a non-empty CPI cap floor volatility structure " <<
266 "to build a CPI cap floor calibration basket.");
267
268 // Procedure is to create a CPI cap floor as described by each instrument in the calibration basket. We then value
269 // each of the CPI cap floor instruments using market data and an engine and pass the NPV as the market premium to
270 // helper that we create.
271
272 Helpers helpers;
273
274 // Create the engine
275 auto zts = zeroInflationIndex_->zeroInflationTermStructure();
276
277 QuantLib::ext::shared_ptr<QuantExt::CPICapFloorEngine> engine;
278 bool isLogNormalVol = QuantExt::ZeroInflation::isCPIVolSurfaceLogNormal(cpiVolatility_.currentLink());
279 if (isLogNormalVol) {
280 engine = QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(rateCurve_, cpiVolatility_);
281 } else {
282 engine = QuantLib::ext::make_shared<QuantExt::CPIBachelierCapFloorEngine>(rateCurve_, cpiVolatility_);
283 }
284 // CPI cap floor calibration instrument details. Assumed to equal those from the index and market structures.
285 // Some of these should possibly come from conventions.
286 // Also some variables used in the loop below.
287 auto calendar = zeroInflationIndex_->fixingCalendar();
288 auto baseDate = zts->baseDate();
289 auto baseCpi = dontCalibrate_ ? 100.0 : zeroInflationIndex_->fixing(baseDate);
290 auto bdc = cpiVolatility_->businessDayConvention();
291 auto obsLag = cpiVolatility_->observationLag();
292
293 Handle<ZeroInflationIndex> inflationIndex(zeroInflationIndex_);
294 Date today = Settings::instance().evaluationDate();
295 Real nominal = 1.0;
296
297 // Avoid instruments with duplicate expiry times in the loop below
298 set<Time, CloseCmp> expiryTimes;
299
300 // Reference calibration dates if any. If they are given, we only include one calibration instrument from each
301 // period in the grid. Logic copied from other builders.
302 auto rcDates = referenceCalibrationDates();
303 auto prevRcDate = Date::minDate();
304
305 // Add calibration instruments to the helpers vector.
306 const auto& ci = cb.instruments();
307
308 auto observationInterpolation = cpiVolatility_->indexIsInterpolated() ? CPI::Linear : CPI::Flat;
309
310 for (Size i = 0; i < ci.size(); ++i) {
311
312 auto cpiCapFloor = QuantLib::ext::dynamic_pointer_cast<CpiCapFloor>(ci[i]);
313 QL_REQUIRE(cpiCapFloor, "InfJyBuilder: expected CpiCapFloor calibration instrument.");
314 auto maturity = optionMaturity(cpiCapFloor->maturity(), calendar);
315
316 // Deal with reference calibration date grid stuff.
317 auto rcDate = lower_bound(rcDates.begin(), rcDates.end(), maturity);
318 if (!(rcDate == rcDates.end() || *rcDate > prevRcDate)) {
319 active[i] = false;
320 continue;
321 }
322
323 if (rcDate != rcDates.end())
324 prevRcDate = *rcDate;
325
326 // Build the CPI calibration instrument in order to calculate its NPV.
327
328 /* FIXME - the maturity date is not adjusted on eval date changes even if given as a tenor
329 - if the strike is atm, the value will not be updated on eval date changes */
330 Real strikeValue =
331 cpiCapFloorStrikeValue(cpiCapFloor->strike(), *zeroInflationIndex_->zeroInflationTermStructure(), maturity);
332 Option::Type capfloor = cpiCapFloor->type() == CapFloor::Cap ? Option::Call : Option::Put;
333 auto inst = QuantLib::ext::make_shared<CPICapFloor>(capfloor, nominal, today, baseCpi, maturity, calendar, bdc, calendar, bdc,
334 strikeValue, zeroInflationIndex_, obsLag, observationInterpolation);
335 inst->setPricingEngine(engine);
336
337 auto fixingDate = inst->fixingDate();
338 auto t = inflationTime(fixingDate, *zts, false);
339
340 // Build the helper using the NPV as the premium.
341 Real premium;
343 premium = 0.01;
344 else if(t <= 0.0)
345 premium = 0.0;
346 else
347 premium = inst->NPV();
348
349 auto helper = QuantLib::ext::make_shared<CpiCapFloorHelper>(capfloor, baseCpi, maturity, calendar, bdc, calendar, bdc,
350 strikeValue, inflationIndex, obsLag, premium,
351 observationInterpolation);
352
353 // if time is not positive or market prem is zero deactivate helper
354 if (t < 0.0 || QuantLib::close_enough(t, 0.0) || QuantLib::close_enough(premium, 0.0)) {
355 active[i] = false;
356 continue;
357 }
358
359 auto p = expiryTimes.insert(t);
360 QL_REQUIRE(data_->ignoreDuplicateCalibrationExpiryTimes() || p.second,
361 "InfJyBuilder: a CPI cap floor calibration "
362 << "instrument with the expiry time, " << t << ", was already added.");
363
364 if(p.second)
365 helpers.push_back(helper);
366
367 TLOG("InfJyBuilder: " << (p.second ?
368 "added CPICapFloor helper" :
369 "skipped CPICapFloor helper due to duplicate expiry time (" + std::to_string(t) + ")") <<
370 ": index = " << data_->index() <<
371 ", type = " << cpiCapFloor->type() <<
372 ", expiry = " << io::iso_date(maturity) <<
373 ", base CPI = " << baseCpi <<
374 ", strike = " << strikeValue <<
375 ", obs lag = " << obsLag <<
376 ", market premium = " << premium);
377 }
378
379 // Populate the expiry times array with the unique sorted expiry times.
380 expiries = Array(expiryTimes.begin(), expiryTimes.end());
381
382 DLOG("InfJyBuilder: finished building the CPI cap floor calibration basket.");
383
384 return helpers;
385}
386
387Helpers InfJyBuilder::buildYoYCapFloorBasket(const CalibrationBasket& cb, vector<bool>& active, Array& expiries) const {
388
389 DLOG("InfJyBuilder: start building the YoY cap floor calibration basket.");
390
391 // Initial checks.
392 QL_REQUIRE(yoyInflationIndex_, "InfJyBuilder: need a valid year on year inflation index "
393 << "to build a year on year cap floor calibration basket.");
394 auto yoyTs = yoyInflationIndex_->yoyInflationTermStructure();
395 QL_REQUIRE(!yoyTs.empty(), "InfJyBuilder: need a valid year on year term structure "
396 << "to build a year on year cap floor calibration basket.");
397 QL_REQUIRE(!yoyVolatility_.empty(), "InfJyBuilder: need a valid year on year volatility "
398 << "structure to build a year on year cap floor calibration basket.");
399
400 // Procedure is to create a YoY cap floor as described by each instrument in the calibration basket. We then value
401 // each of the YoY cap floor instruments using market data and an engine and pass the NPV as the market premium to
402 // helper that we create.
403
404 Helpers helpers;
405
406 // Create the engine which depends on the type of the YoY volatility and the shift.
407 QuantLib::ext::shared_ptr<PricingEngine> engine;
408 auto ovsType = yoyVolatility_->volatilityType();
409 if (ovsType == Normal)
410 engine = QuantLib::ext::make_shared<YoYInflationBachelierCapFloorEngine>(yoyInflationIndex_, yoyVolatility_, rateCurve_);
411 else if (ovsType == ShiftedLognormal && close(yoyVolatility_->displacement(), 0.0))
412 engine = QuantLib::ext::make_shared<YoYInflationBlackCapFloorEngine>(yoyInflationIndex_, yoyVolatility_, rateCurve_);
413 else if (ovsType == ShiftedLognormal)
414 engine =
415 QuantLib::ext::make_shared<YoYInflationUnitDisplacedBlackCapFloorEngine>(yoyInflationIndex_, yoyVolatility_, rateCurve_);
416 else
417 QL_FAIL("InfJyBuilder: can't create engine with yoy volatility type, " << ovsType << ".");
418
419 // YoY cap floor calibration instrument details. Assumed to equal those from the index and market structures.
420 // Some of these should possibly come from conventions. Also some variables used in the loop below.
421 Natural settlementDays = 2;
422 auto calendar = yoyInflationIndex_->fixingCalendar();
423 DayCounter dc = Thirty360(Thirty360::BondBasis);
424 auto bdc = Following;
425 auto obsLag = yoyVolatility_->observationLag();
426
427 // Avoid instruments with duplicate expiry times in the loop below
428 set<Time, CloseCmp> expiryTimes;
429
430 // Reference calibration dates if any. If they are given, we only include one calibration instrument from each
431 // period in the grid. Logic copied from other builders.
432 auto rcDates = referenceCalibrationDates();
433 auto prevRcDate = Date::minDate();
434
435 // Add calibration instruments to the helpers vector.
436 const auto& ci = cb.instruments();
437 for (Size i = 0; i < ci.size(); ++i) {
438
439 auto yoyCapFloor = QuantLib::ext::dynamic_pointer_cast<YoYCapFloor>(ci[i]);
440 QL_REQUIRE(yoyCapFloor, "InfJyBuilder: expected YoYCapFloor calibration instrument.");
441
442 /*! Get the configured strike.
443 FIXME If the strike is atm, the value will not be updated on evaluation date changes */
444 Date today = Settings::instance().evaluationDate();
445 Date maturityDate = calendar.advance(calendar.advance(today, settlementDays * Days), yoyCapFloor->tenor(), bdc);
446 Real strikeValue = yoyCapFloorStrikeValue(yoyCapFloor->strike(), *yoyTs, maturityDate);
447
448 // Build the YoY cap floor helper.
449 auto quote = QuantLib::ext::make_shared<SimpleQuote>(0.01);
450 auto helper = QuantLib::ext::make_shared<YoYCapFloorHelper>(Handle<Quote>(quote), yoyCapFloor->type(), strikeValue,
451 settlementDays, yoyCapFloor->tenor(), yoyInflationIndex_, obsLag, calendar, bdc, dc, calendar, bdc);
452
453 // Deal with reference calibration date grid stuff based on maturity of helper instrument.
454 auto helperInst = helper->yoyCapFloor();
455 auto maturity = helperInst->maturityDate();
456 auto rcDate = lower_bound(rcDates.begin(), rcDates.end(), maturity);
457 if (!(rcDate == rcDates.end() || *rcDate > prevRcDate)) {
458 active[i] = false;
459 continue;
460 }
461
462 if (rcDate != rcDates.end())
463 prevRcDate = *rcDate;
464
465 // Price the underlying helper instrument to get its fair premium.
466 helperInst->setPricingEngine(engine);
467
468 // Update the helper's market quote with the fair rate.
469 quote->setValue(dontCalibrate_ ? 0.1 : helperInst->NPV());
470
471 // Add the helper's time to expiry.
472 auto fixingDate = helperInst->lastYoYInflationCoupon()->fixingDate();
473 auto t = inflationTime(fixingDate, *yoyTs, yoyInflationIndex_->interpolated());
474
475 // if time is not positive deactivate helper
476 if (t < 0.0 || QuantLib::close_enough(t, 0.0)) {
477 active[i] = false;
478 continue;
479 }
480
481 auto p = expiryTimes.insert(t);
482 QL_REQUIRE(data_->ignoreDuplicateCalibrationExpiryTimes() || p.second,
483 "InfJyBuilder: a YoY cap floor calibration "
484 << "instrument with the expiry time, " << t << ", was already added.");
485
486 // Add the helper to the calibration helpers.
487 if(p.second)
488 helpers.push_back(helper);
489
490 TLOG("InfJyBuilder: " << (p.second ?
491 "added YoYCapFloor helper" :
492 "skipped YoYCapFloor helper due to duplicate expiry time (" + std::to_string(t) + ")") <<
493 ": index = " << data_->index() <<
494 ", type = " << yoyCapFloor->type() <<
495 ", expiry = " << io::iso_date(maturity) <<
496 ", strike = " << strikeValue <<
497 ", obs lag = " << obsLag <<
498 ", market premium = " << quote->value());
499 }
500
501 // Populate the expiry times array with the unique sorted expiry times.
502 expiries = Array(expiryTimes.begin(), expiryTimes.end());
503
504 DLOG("InfJyBuilder: finished building the YoY cap floor calibration basket.");
505
506 return helpers;
507}
508
510 vector<bool>& active, Array& expiries, bool forRealRateReversion) const {
511
512 DLOG("InfJyBuilder: start building the YoY swap calibration basket.");
513
514 // Initial checks.
515 QL_REQUIRE(yoyInflationIndex_, "InfJyBuilder: need a valid year on year inflation index " <<
516 "to build a year on year swap calibration basket.");
517 auto yoyTs = yoyInflationIndex_->yoyInflationTermStructure();
518 QL_REQUIRE(!yoyTs.empty(), "InfJyBuilder: need a valid year on year term structure " <<
519 "to build a year on year swap calibration basket.");
520
521 // Procedure is to create a YoY cap floor as described by each instrument in the calibration basket. We then value
522 // each of the YoY cap floor instruments using market data and an engine and pass the NPV as the market premium to
523 // helper that we create.
524
525 Helpers helpers;
526
527 // Create the engine
528 auto engine = QuantLib::ext::make_shared<DiscountingSwapEngine>(rateCurve_);
529
530 // YoY swap calibration instrument details. Assumed to equal those from the index and market structures.
531 // Some of these should possibly come from conventions. Hardcoded some common values here.
532 // Also some variables used in the loop below.
533 Natural settlementDays = 2;
534 auto calendar = yoyInflationIndex_->fixingCalendar();
535 auto dc = Thirty360(Thirty360::BondBasis);
536 auto bdc = Following;
537 auto obsLag = yoyTs->observationLag();
538
539 // Avoid instruments with duplicate expiry times in the loop below
540 set<Time, CloseCmp> expiryTimes;
541
542 // Reference calibration dates if any. If they are given, we only include one calibration instrument from each
543 // period in the grid. Logic copied from other builders.
544 auto rcDates = referenceCalibrationDates();
545 auto prevRcDate = Date::minDate();
546
547 // Add calibration instruments to the helpers vector.
548 const auto& ci = cb.instruments();
549 for (Size i = 0; i < ci.size(); ++i) {
550
551 auto yoySwap = QuantLib::ext::dynamic_pointer_cast<YoYSwap>(ci[i]);
552 QL_REQUIRE(yoySwap, "InfJyBuilder: expected YoYSwap calibration instrument.");
553
554 // Build the YoY helper.
555 auto quote = QuantLib::ext::make_shared<SimpleQuote>(0.01);
556 auto helper = QuantLib::ext::make_shared<YoYSwapHelper>(Handle<Quote>(quote), settlementDays, yoySwap->tenor(),
557 yoyInflationIndex_, rateCurve_, obsLag, calendar, bdc, dc,
558 calendar, bdc, dc, calendar, bdc);
559
560 // Deal with reference calibration date grid stuff based on maturity of helper instrument.
561 auto helperInst = helper->yoySwap();
562 auto maturity = helperInst->maturityDate();
563 auto rcDate = lower_bound(rcDates.begin(), rcDates.end(), maturity);
564 if (!(rcDate == rcDates.end() || *rcDate > prevRcDate)) {
565 active[i] = false;
566 continue;
567 }
568
569 if (rcDate != rcDates.end())
570 prevRcDate = *rcDate;
571
572 // Price the underlying helper instrument to get its fair rate.
573 helperInst->setPricingEngine(engine);
574
575 // Update the helper's market quote with the fair rate.
576 quote->setValue(helperInst->fairRate());
577
578 // For JY calibration to YoY swaps, the parameter's time depends on whether you are calibrating the real rate
579 // reversion or the real rate volatility (probably don't want to calibrate the inflation index vol to YoY
580 // swaps as it only shows up via the drift). If you are calibrating to real rate reversion, you want the time
581 // to the numerator index fixing date on the last YoY swaplet on the YoY leg. If you are calibrating to real
582 // rate volatility, you want the time to the denominator index fixing date on the last YoY swaplet on the YoY
583 // leg. We use numerator fixing date - 1 * Years here for this. You can see this from the parameter
584 // dependencies in the YoY swaplet formula in Section 13 of the book (i.e. T vs. S).
585 // If t is not positive, we log a message and skip this helper.
586 Time t = 0.0;
587 QL_REQUIRE(!helperInst->yoyLeg().empty(), "InfJyBuilder: expected YoYSwap to have non-empty YoY leg.");
588 auto finalYoYCoupon = QuantLib::ext::dynamic_pointer_cast<YoYInflationCoupon>(helperInst->yoyLeg().back());
589 Date numFixingDate = finalYoYCoupon->fixingDate();
590 if (forRealRateReversion) {
591 t = inflationTime(numFixingDate, *yoyTs, yoyInflationIndex_->interpolated());
592 } else {
593 auto denFixingDate = numFixingDate - 1 * Years;
594 t = inflationTime(denFixingDate, *yoyTs, yoyInflationIndex_->interpolated());
595 }
596
597 if (t < 0 || close_enough(t, 0.0)) {
598 DLOG("The year on year swap with maturity tenor, " << yoySwap->tenor() << ", and date, " << maturity <<
599 ", has a non-positive parameter time, " << t << ", so skipping this as a calibration instrument.");
600 continue;
601 }
602
603 // Add the helper to the calibration helpers.
604 helpers.push_back(helper);
605
606 auto p = expiryTimes.insert(t);
607 QL_REQUIRE(p.second, "InfJyBuilder: a YoY swap calibration instrument with the expiry " <<
608 "time, " << t << ", was already added.");
609
610 TLOG("InfJyBuilder: added year on year swap helper" <<
611 ": index = " << data_->index() <<
612 ", maturity = " << io::iso_date(maturity) <<
613 ", obs lag = " << obsLag <<
614 ", market rate = " << quote->value());
615 }
616
617 // Populate the expiry times array with the unique sorted expiry times.
618 expiries = Array(expiryTimes.begin(), expiryTimes.end());
619
620 DLOG("InfJyBuilder: finished building the YoY swap calibration basket.");
621
622 return helpers;
623}
624
625const CalibrationBasket& InfJyBuilder::calibrationBasket(const string& parameter) const {
626
627 for (const auto& cb : data_->calibrationBaskets()) {
628 if (cb.parameter() == parameter) {
629 return cb;
630 }
631 }
632
633 QL_FAIL("InfJyBuilder: unable to find calibration basket with parameter value equal to '" << parameter << "'.");
634}
635
636QuantLib::ext::shared_ptr<Lgm1fParametrization<ZeroInflationTermStructure>> InfJyBuilder::createRealRateParam() const {
637
638 DLOG("InfJyBuilder: start creating the real rate parameterisation.");
639
640 // Initial parameter setup as provided by the data_.
641 const ReversionParameter& rrReversion = data_->realRateReversion();
642 const VolatilityParameter& rrVolatility = data_->realRateVolatility();
643 Array rrVolatilityTimes(rrVolatility.times().begin(), rrVolatility.times().end());
644 Array rrVolatilityValues(rrVolatility.values().begin(), rrVolatility.values().end());
645 Array rrReversionTimes(rrReversion.times().begin(), rrReversion.times().end());
646 Array rrReversionValues(rrReversion.values().begin(), rrReversion.values().end());
647
648 // Perform checks and in the event of bootstrap calibration, may need to restructure the parameters.
649 setupParams(rrReversion, rrReversionTimes, rrReversionValues, rrInstExpiries_, "RealRate reversion");
650 setupParams(rrVolatility, rrVolatilityTimes, rrVolatilityValues, rrInstExpiries_, "RealRate volatility");
651
652 // Create the JY parameterization.
653 using RT = LgmData::ReversionType;
654 using VT = LgmData::VolatilityType;
655
656 // Real rate parameter constraints
657 const auto& cc = data_->calibrationConfiguration();
658 auto rrVolConstraint = cc.constraint("RealRateVolatility");
659 auto rrRevConstraint = cc.constraint("RealRateReversion");
660
661 // Create the real rate portion of the parameterization
662 using QuantLib::ZeroInflationTermStructure;
663 QuantLib::ext::shared_ptr<QuantExt::Lgm1fParametrization<ZeroInflationTermStructure>> realRateParam;
664 if (rrReversion.reversionType() == RT::HullWhite && rrVolatility.volatilityType() == VT::HullWhite) {
666 DLOG("InfJyBuilder: real rate parameterization is Lgm1fPiecewiseConstantHullWhiteAdaptor");
667 realRateParam = QuantLib::ext::make_shared<Lgm1fPiecewiseConstantHullWhiteAdaptor<ZeroInflationTermStructure>>(
668 zeroInflationIndex_->currency(), zeroInflationIndex_->zeroInflationTermStructure(), rrVolatilityTimes,
669 rrVolatilityValues, rrReversionTimes, rrReversionValues, data_->index(), rrVolConstraint, rrRevConstraint);
670 } else if (rrReversion.reversionType() == RT::HullWhite && rrVolatility.volatilityType() == VT::Hagan) {
672 DLOG("InfJyBuilder: real rate parameterization is Lgm1fPiecewiseConstantParametrization");
673 realRateParam = QuantLib::ext::make_shared<Lgm1fPiecewiseConstantParametrization<ZeroInflationTermStructure>>(
674 zeroInflationIndex_->currency(), zeroInflationIndex_->zeroInflationTermStructure(), rrVolatilityTimes,
675 rrVolatilityValues, rrReversionTimes, rrReversionValues, data_->index(), rrVolConstraint, rrRevConstraint);
676 } else if (rrReversion.reversionType() == RT::Hagan && rrVolatility.volatilityType() == VT::Hagan) {
678 DLOG("InfJyBuilder: real rate parameterization is Lgm1fPiecewiseLinearParametrization");
679 realRateParam = QuantLib::ext::make_shared<Lgm1fPiecewiseLinearParametrization<ZeroInflationTermStructure>>(
680 zeroInflationIndex_->currency(), zeroInflationIndex_->zeroInflationTermStructure(), rrVolatilityTimes,
681 rrVolatilityValues, rrReversionTimes, rrReversionValues, data_->index(), rrVolConstraint, rrRevConstraint);
682 } else {
683 QL_FAIL("InfJyBuilder: reversion type Hagan and volatility type HullWhite not supported.");
684 }
685
686 Time horizon = data_->reversionTransformation().horizon();
687 if (horizon >= 0.0) {
688 DLOG("InfJyBuilder: apply shift horizon " << horizon << " to the JY real rate parameterisation for index " <<
689 data_->index() << ".");
690 realRateParam->shift() = horizon;
691 } else {
692 WLOG("InfJyBuilder: ignoring negative horizon, " << horizon <<
693 ", passed to the JY real rate parameterisation for index " << data_->index() << ".");
694 }
695
696 Real scaling = data_->reversionTransformation().scaling();
697 if (scaling > 0.0) {
698 DLOG("InfJyBuilder: apply scaling " << scaling << " to the JY real rate parameterisation for index " <<
699 data_->index() << ".");
700 realRateParam->scaling() = scaling;
701 } else {
702 WLOG("Ignoring non-positive scaling, " << scaling <<
703 ", passed to the JY real rate parameterisation for index " << data_->index() << ".");
704 }
705
706 DLOG("InfJyBuilder: finished creating the real rate parameterisation.");
707
708 return realRateParam;
709}
710
711QuantLib::ext::shared_ptr<FxBsParametrization> InfJyBuilder::createIndexParam() const {
712
713 DLOG("InfJyBuilder: start creating the index parameterisation.");
714
715 // Initial parameter setup as provided by the data_.
716 const VolatilityParameter& idxVolatility = data_->indexVolatility();
717 Array idxVolatilityTimes(idxVolatility.times().begin(), idxVolatility.times().end());
718 Array idxVolatilityValues(idxVolatility.values().begin(), idxVolatility.values().end());
719
720 // Perform checks and in the event of bootstrap calibration, may need to restructure the parameters.
721 setupParams(idxVolatility, idxVolatilityTimes, idxVolatilityValues, indexInstExpiries_, "Index volatility");
722
723 // Create the index portion of the parameterization
724 QuantLib::ext::shared_ptr<QuantExt::FxBsParametrization> indexParam;
725
726 Handle<Quote> baseCpiQuote(QuantLib::ext::make_shared<SimpleQuote>(
727 dontCalibrate_ ? 100
728 : zeroInflationIndex_->fixing(zeroInflationIndex_->zeroInflationTermStructure()->baseDate())));
729
730 // Index volatility parameter constraints
731 const auto& cc = data_->calibrationConfiguration();
732 auto idxVolConstraint = cc.constraint("IndexVolatility");
733
734 if (idxVolatility.type() == ParamType::Piecewise) {
736 DLOG("InfJyBuilder: index volatility parameterization is FxBsPiecewiseConstantParametrization");
737 indexParam = QuantLib::ext::make_shared<FxBsPiecewiseConstantParametrization>(
738 zeroInflationIndex_->currency(), baseCpiQuote, idxVolatilityTimes, idxVolatilityValues, idxVolConstraint);
739 } else if (idxVolatility.type() == ParamType::Constant) {
741 DLOG("InfJyBuilder: index volatility parameterization is FxBsConstantParametrization");
742 indexParam = QuantLib::ext::make_shared<FxBsConstantParametrization>(
743 zeroInflationIndex_->currency(), baseCpiQuote, idxVolatilityValues[0]);
744 } else {
745 QL_FAIL("InfJyBuilder: index volatility parameterization needs to be Piecewise or Constant.");
746 }
747
748 DLOG("InfJyBuilder: finished creating the index parameterisation.");
749
750 return indexParam;
751}
752
753void InfJyBuilder::setupParams(const ModelParameter& param, Array& times, Array& values,
754 const Array& expiries, const string& paramName) const {
755
756 DLOG("InfJyBuilder: start setting up parameters for " << paramName);
757
758 if (param.type() == ParamType::Constant) {
759 QL_REQUIRE(param.times().size() == 0, "InfJyBuilder: parameter is constant so empty times expected");
760 QL_REQUIRE(param.values().size() == 1, "InfJyBuilder: parameter is constant so initial value array " <<
761 "should have 1 element.");
762 } else if (param.type() == ParamType::Piecewise) {
763
764 if (param.calibrate() && data_->calibrationType() == CalibrationType::Bootstrap) {
765 QL_REQUIRE(!expiries.empty(), "InfJyBuilder: calibration instrument expiries are empty.");
766 QL_REQUIRE(!values.empty(), "InfJyBuilder: expected at least one initial value.");
767 DLOG("InfJyBuilder: overriding initial times " << times << " with option calibration instrument " <<
768 "expiries " << expiries << ".");
769 times = Array(expiries.begin(), expiries.end() - 1);
770 values = Array(times.size() + 1, values[0]);
771 } else {
772 QL_REQUIRE(values.size() == times.size() + 1, "InfJyBuilder: size of values grid, " << values.size() <<
773 ", should be 1 greater than the size of the times grid, " << times.size() << ".");
774 }
775
776 } else {
777 QL_FAIL("Expected " << paramName << " parameter to be Constant or Piecewise.");
778 }
779
780 DLOG("InfJyBuilder: finished setting up parameters for " << paramName);
781}
782
784
785 TLOG("InfJyBuilder: start building reference date grid '" << referenceCalibrationGrid_ << "'.");
786
787 vector<Date> res;
788 if (!referenceCalibrationGrid_.empty())
790
791 TLOG("InfJyBuilder: finished building reference date grid.");
792
793 return res;
794}
795
797
798 TLOG("InfJyBuilder: start initialising market data members.");
799
800 // Try catches are not nice but Market does not have a method for checking if a structure exists so it is
801 // unfortunately necessary.
802 try {
803 cpiVolatility_ = market_->cpiInflationCapFloorVolatilitySurface(data_->index(), configuration_);
804 } catch (...) {
805 DLOG("InfJyBuilder: the market does not have a CPI cap floor volatility surface.");
806 }
807
808 try {
809 yoyInflationIndex_ = *market_->yoyInflationIndex(data_->index(), configuration_);
810 marketObserver_->registerWith(yoyInflationIndex_);
811 } catch (...) {
812 DLOG("InfJyBuilder: the market does not have a YoY inflation index.");
813 }
814
815 try {
816 yoyVolatility_ = market_->yoyCapFloorVol(data_->index(), configuration_);
817 } catch (...) {
818 DLOG("InfJyBuilder: the market does not have a YoY cap floor volatility surface.");
819 }
820
821 TLOG("InfJyBuilder: finished initialising market data members.");
822}
823
824bool InfJyBuilder::pricesChanged(bool updateCache) const {
826 return false;
827
828 // Build the calibration instruments again before checking the market price below.
829 // Don't need to do this if updateCache is true, because only called above after buildCalibrationBaskets().
830 if (!updateCache)
832
833 // Resize the cache to match the number of calibration instruments
834 auto numInsts = realRateBasket_.size() + indexBasket_.size();
835 if (priceCache_.size() != numInsts)
836 priceCache_ = vector<Real>(numInsts, Null<Real>());
837
838 // Check if any market prices have changed. Return true if they have and false if they have not.
839 // If asked to update the cached prices, via updateCache being true, update the prices, if necessary.
840 bool result = false;
841 Size ctr = 0;
842 for (const auto& ci : boost::range::join(realRateBasket_, indexBasket_)) {
843 auto mp = marketPrice(ci);
844 if (!close_enough(priceCache_[ctr], mp)) {
845 if (updateCache)
846 priceCache_[ctr] = mp;
847 result = true;
848 }
849 ctr++;
850 }
851
852 return result;
853}
854
855Real InfJyBuilder::marketPrice(const QuantLib::ext::shared_ptr<CalibrationHelper>& helper) const {
856
857 if (auto h = QuantLib::ext::dynamic_pointer_cast<CpiCapFloorHelper>(helper)) {
858 return h->marketValue();
859 }
860
861 if (auto h = QuantLib::ext::dynamic_pointer_cast<YoYCapFloorHelper>(helper)) {
862 return h->marketValue();
863 }
864
865 if (QuantLib::ext::shared_ptr<YoYSwapHelper> h = QuantLib::ext::dynamic_pointer_cast<YoYSwapHelper>(helper)) {
866 return h->marketRate();
867 }
868
869 QL_FAIL("InfJyBuilder: unrecognised calibration instrument for JY calibration.");
870}
871
872}
873}
virtual void forceRecalculate()
bool empty() const
Returns true if the calibration basket is empty.
const std::string & instrumentType() const
const std::vector< QuantLib::ext::shared_ptr< CalibrationInstrument > > & instruments() const
Simulation Date Grid.
Definition: dategrid.hpp:38
const std::vector< QuantLib::Date > & dates() const
Definition: dategrid.hpp:81
Helpers buildCalibrationBasket(const CalibrationBasket &cb, std::vector< bool > &active, QuantLib::Array &expiries, bool forRealRateReversion=false) const
Build the calibration basket.
QuantLib::ext::shared_ptr< QuantLib::ZeroInflationIndex > zeroInflationIndex_
std::vector< QuantLib::ext::shared_ptr< QuantLib::CalibrationHelper > > Helpers
std::string referenceCalibrationGrid_
void forceRecalculate() override
QuantLib::ext::shared_ptr< Market > market_
void performCalculations() const override
Helpers indexBasket() const
Helpers buildYoYSwapBasket(const CalibrationBasket &cb, std::vector< bool > &active, QuantLib::Array &expiries, bool forRealRateReversion=false) const
Build a YoY swap calibration basket.
QuantLib::ext::shared_ptr< QuantExt::MarketObserver > marketObserver_
QuantLib::Handle< QuantLib::YoYOptionletVolatilitySurface > yoyVolatility_
const CalibrationBasket & calibrationBasket(const std::string &parameter) const
Find calibration basket with parameter value equal to parameter.
void setCalibrationDone() const
Helpers buildYoYCapFloorBasket(const CalibrationBasket &cb, std::vector< bool > &active, QuantLib::Array &expiries) const
Build a YoY cap floor calibration basket.
void initialiseMarket()
Attempt to initialise market data members that may be needed for building calibration instruments.
QuantLib::ext::shared_ptr< QuantLib::YoYInflationIndex > yoyInflationIndex_
Helpers buildCpiCapFloorBasket(const CalibrationBasket &cb, std::vector< bool > &active, QuantLib::Array &expiries) const
Build a CPI cap floor calibration basket.
QuantLib::ext::shared_ptr< InfJyData > data_
QuantLib::ext::shared_ptr< QuantExt::InfJyParameterization > parameterization() const
bool requiresRecalibration() const override
QuantLib::ext::shared_ptr< QuantExt::Lgm1fParametrization< ZeroInflationTermStructure > > createRealRateParam() const
Create the real rate parameterisation.
Handle< YieldTermStructure > rateCurve_
void buildCalibrationBaskets() const
Build any calibration baskets requested by the configuration i.e. via the data_ member.
bool pricesChanged(bool updateCache) const
InfJyBuilder(const QuantLib::ext::shared_ptr< Market > &market, const QuantLib::ext::shared_ptr< InfJyData > &data, const std::string &configuration=Market::defaultConfiguration, const std::string &referenceCalibrationGrid="", const bool donCalibrate=false)
void setupParams(const ModelParameter &param, QuantLib::Array &times, QuantLib::Array &values, const QuantLib::Array &expiries, const std::string &paramName) const
QuantLib::Array indexInstExpiries_
std::vector< bool > rrInstActive_
QuantLib::ext::shared_ptr< QuantExt::FxBsParametrization > createIndexParam() const
Create the inflation index parameterisation.
std::vector< QuantLib::Date > referenceCalibrationDates() const
Create the reference calibration dates.
std::string inflationIndex() const
QuantLib::ext::shared_ptr< QuantExt::InfJyParameterization > parameterization_
QuantLib::Array rrInstExpiries_
std::vector< QuantLib::Real > priceCache_
Cache the prices of all of the active calibration helper instruments.
Helpers realRateBasket() const
QuantLib::Real marketPrice(const QuantLib::ext::shared_ptr< QuantLib::CalibrationHelper > &helper) const
Return the market value of the given calibration helper.
std::vector< bool > indexInstActive_
QuantLib::Handle< QuantLib::CPIVolatilitySurface > cpiVolatility_
ReversionType
Supported mean reversion types.
Definition: lgmdata.hpp:56
VolatilityType
Supported volatility types.
Definition: lgmdata.hpp:67
const std::vector< QuantLib::Time > & times() const
const std::vector< QuantLib::Real > & values() const
LgmData::ReversionType reversionType() const
const boost::optional< LgmData::VolatilityType > & volatilityType() const
The date grid class.
Builder for a Jarrow Yildrim inflation model component.
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define LOG(text)
Logging Macro (Level = Notice)
Definition: log.hpp:552
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define WLOG(text)
Logging Macro (Level = Warning)
Definition: log.hpp:550
#define TLOG(text)
Logging Macro (Level = Data)
Definition: log.hpp:556
class for holding details of a zero coupon CPI cap floor calibration instrument.
class for holding details of a year on year inflation cap floor calibration instrument.
Time maturity
Definition: utilities.cpp:66
Calendar calendar
Definition: utilities.cpp:441
Shared utilities for model building and calibration.
QuantLib::Date fixingDate(const QuantLib::Date &d, const QuantLib::Period obsLag, const QuantLib::Frequency freq, bool interpolated)
bool isCPIVolSurfaceLogNormal(const boost::shared_ptr< QuantLib::CPIVolatilitySurface > &surface)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Time inflationTime(const Date &date, const boost::shared_ptr< InflationTermStructure > &inflationTs, bool indexIsInterpolated, const DayCounter &dayCounter)
Real yoyCapFloorStrikeValue(const QuantLib::ext::shared_ptr< BaseStrike > &strike, const QuantLib::ext::shared_ptr< YoYInflationTermStructure > &curve, const QuantLib::Date &optionMaturityDate)
Return a yoy cap/floor strike value, the input strike can be of type absolute or atm forward.
Definition: utilities.cpp:468
InfJyBuilder::Helpers Helpers
Size size(const ValueType &v)
Definition: value.cpp:145
Date optionMaturity(const boost::variant< Date, Period > &maturity, const QuantLib::Calendar &calendar, const QuantLib::Date &referenceDate)
Definition: utilities.cpp:447
Real cpiCapFloorStrikeValue(const QuantLib::ext::shared_ptr< BaseStrike > &strike, const QuantLib::ext::shared_ptr< ZeroInflationTermStructure > &curve, const QuantLib::Date &optionMaturityDate)
Return a cpi cap/floor strike value, the input strike can be of type absolute or atm forward.
Definition: utilities.cpp:453
Serializable Credit Default Swap.
Definition: namespaces.docs:23
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
class for holding details of a year on year inflation swap calibration instrument.