Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
lgmbuilder.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 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
19#include <ql/math/optimization/levenbergmarquardt.hpp>
20#include <ql/models/shortrate/calibrationhelpers/swaptionhelper.hpp>
21#include <ql/pricingengines/swaption/blackswaptionengine.hpp>
22#include <ql/quotes/simplequote.hpp>
23
30
39
40using namespace QuantLib;
41using namespace QuantExt;
42using namespace std;
43
44namespace {
45
46// Return swaption data
47SwaptionData swaptionData(const QuantLib::ext::shared_ptr<Swaption> swaption, const Handle<YieldTermStructure>& yts,
48 const Handle<SwaptionVolatilityStructure>& svts) {
49
50 QuantLib::ext::shared_ptr<PricingEngine> engine;
51 switch (svts->volatilityType()) {
52 case ShiftedLognormal:
53 engine = QuantLib::ext::make_shared<BlackSwaptionEngine>(yts, svts);
54 break;
55 case Normal:
56 engine = QuantLib::ext::make_shared<BachelierSwaptionEngine>(yts, svts);
57 break;
58 default:
59 QL_FAIL("Could not construct swaption engine for volatility type: " << svts->volatilityType());
60 break;
61 }
62
63 swaption->setPricingEngine(engine);
64
65 SwaptionData sd;
66 sd.timeToExpiry = yts->timeFromReference(swaption->exercise()->dates().back());
67 sd.swapLength = swaption->result<Real>("swapLength");
68 sd.strike = swaption->result<Real>("strike");
69 sd.atmForward = swaption->result<Real>("atmForward");
70 sd.annuity = swaption->result<Real>("annuity");
71 sd.vega = swaption->result<Real>("vega");
72 sd.stdDev = swaption->result<Real>("stdDev");
73
74 return sd;
75}
76
77// Utility function to create swaption helper. Returns helper and (possibly updated) strike
78template <typename E, typename T>
79std::pair<QuantLib::ext::shared_ptr<SwaptionHelper>, double>
80createSwaptionHelper(const E& expiry, const T& term, const Handle<SwaptionVolatilityStructure>& svts,
81 const Handle<Quote>& vol, const QuantLib::ext::shared_ptr<IborIndex>& iborIndex,
82 const Period& fixedLegTenor, const DayCounter& fixedDayCounter, const DayCounter& floatDayCounter,
83 const Handle<YieldTermStructure>& yts, BlackCalibrationHelper::CalibrationErrorType errorType,
84 Real strike, Real shift, const Size settlementDays, const RateAveraging::Type averagingMethod) {
85
86 DLOG("LgmBuilder::createSwaptionHelper(" << expiry << ", " << term << ")");
87
88 // hardcoded parameters to ensure a robust cailbration:
89
90 // 1 If the helper's strike is too far away from the ATM level in terms of the relevant std dev, we move the
91 // calibration strike closer to the ATM level
92 static constexpr Real maxAtmStdDev = 3.0;
93
94 // 2 If the helper value is lower than mmv, replace it with a "more reasonable" helper. Here, we replace
95 // the helper with a helper that has the ATM strike. There are other options here.
96 static constexpr Real mmv = 1.0E-20;
97
98 // 3 Switch to PriceError if helper's market value is below smv
99 static constexpr Real smv = 1.0E-8;
100
101 // Notice: the vol that is passed in to this method is in general a dummy value, which is good enough though to
102 // check 2 and 3 above. To check 1, the vol is not needed at all.
103
104 auto vt = svts->volatilityType();
105 auto helper = QuantLib::ext::make_shared<SwaptionHelper>(expiry, term, vol, iborIndex, fixedLegTenor, fixedDayCounter,
106 floatDayCounter, yts, errorType, strike, 1.0, vt, shift,
107 settlementDays, averagingMethod);
108 auto sd = swaptionData(helper->swaption(), yts, svts);
109
110 // ensure point 1 from above
111
112 Real atmStdDev = svts->volatility(sd.timeToExpiry, sd.swapLength, sd.atmForward) * std::sqrt(sd.timeToExpiry);
113 if (vt == ShiftedLognormal) {
114 atmStdDev *= sd.atmForward + shift;
115 }
116 if (strike != Null<Real>() && std::abs(strike - sd.atmForward) > maxAtmStdDev * atmStdDev) {
117 DLOG("Helper with expiry " << expiry << " and term " << term << " has a strike (" << strike
118 << ") that is too far out of the money (atm = " << sd.atmForward << ", atmStdDev = "
119 << atmStdDev << "). Adjusting the strike using maxAtmStdDev " << maxAtmStdDev);
120 if (strike > sd.atmForward)
121 strike = sd.atmForward + maxAtmStdDev * atmStdDev;
122 else
123 strike = sd.atmForward - maxAtmStdDev * atmStdDev;
124 helper = QuantLib::ext::make_shared<SwaptionHelper>(expiry, term, vol, iborIndex, fixedLegTenor, fixedDayCounter,
125 floatDayCounter, yts, errorType, strike, 1.0, vt, shift,
126 settlementDays, averagingMethod);
127 }
128
129 // ensure point 2 from above
130
131 auto mv = std::abs(helper->marketValue());
132 if (mv < mmv) {
133 DLOG("Helper with expiry " << expiry << " and term " << term << " has an absolute market value of "
134 << std::scientific << mv << " which is lower than minimum market value " << mmv
135 << " so switching to helper with atm rate " << sd.atmForward);
136 strike = sd.atmForward;
137 helper = QuantLib::ext::make_shared<SwaptionHelper>(expiry, term, vol, iborIndex, fixedLegTenor, fixedDayCounter,
138 floatDayCounter, yts, errorType, strike, 1.0, vt, shift,
139 settlementDays, averagingMethod);
140 }
141
142 // ensure point 3 from above
143
144 mv = std::abs(helper->marketValue());
145 if (errorType != BlackCalibrationHelper::PriceError && mv < smv) {
146 errorType = BlackCalibrationHelper::PriceError;
147 TLOG("Helper with expiry " << expiry << " and term " << term << " has an absolute market value of "
148 << std::scientific << mv << " which is lower than " << smv
149 << " so switching to a price error helper.");
150 helper = QuantLib::ext::make_shared<SwaptionHelper>(expiry, term, vol, iborIndex, fixedLegTenor, fixedDayCounter,
151 floatDayCounter, yts, errorType, strike, 1.0, vt, shift,
152 settlementDays, averagingMethod);
153 }
154
155 DLOG("Created swaption helper with expiry " << expiry << " and term " << term << ": vol=" << vol->value()
156 << ", index=" << iborIndex->name() << ", strike=" << strike
157 << ", shift=" << shift);
158
159 return std::make_pair(helper, strike);
160}
161
162} // namespace
163
164namespace ore {
165namespace data {
166
167LgmBuilder::LgmBuilder(const QuantLib::ext::shared_ptr<ore::data::Market>& market, const QuantLib::ext::shared_ptr<IrLgmData>& data,
168 const std::string& configuration, const Real bootstrapTolerance, const bool continueOnError,
169 const std::string& referenceCalibrationGrid, const bool setCalibrationInfo,
170 const std::string& id)
171 : market_(market), configuration_(configuration), data_(data), bootstrapTolerance_(bootstrapTolerance),
172 continueOnError_(continueOnError), referenceCalibrationGrid_(referenceCalibrationGrid),
173 setCalibrationInfo_(setCalibrationInfo), id_(id),
174 optimizationMethod_(QuantLib::ext::shared_ptr<OptimizationMethod>(new LevenbergMarquardt(1E-8, 1E-8, 1E-8))),
175 endCriteria_(EndCriteria(1000, 500, 1E-8, 1E-8, 1E-8)),
176 calibrationErrorType_(BlackCalibrationHelper::RelativePriceError) {
177
178 marketObserver_ = QuantLib::ext::make_shared<MarketObserver>();
179 string qualifier = data_->qualifier();
181 QuantLib::ext::shared_ptr<IborIndex> index;
182 if (tryParseIborIndex(qualifier, index)) {
183 currency_ = index->currency().code();
184 }
185 LOG("LgmCalibration for qualifier " << qualifier << " (ccy=" << currency_ << "), configuration is "
186 << configuration_);
187 Currency ccy = parseCurrency(currency_);
188
190 (data_->calibrateA() || data_->calibrateH()) && data_->calibrationType() != CalibrationType::None;
191
192 try {
194 market_->swapIndex(market_->shortSwapIndexBase(data_->qualifier(), configuration_), configuration_);
195 swapIndex_ = market_->swapIndex(market_->swapIndexBase(data_->qualifier(), configuration_), configuration_);
196 svts_ = market_->swaptionVol(data_->qualifier(), configuration_);
197 // see the comment for dinscountCurve() in the interface
198 modelDiscountCurve_ = RelinkableHandle<YieldTermStructure>(*swapIndex_->discountingTermStructure());
199 calibrationDiscountCurve_ = Handle<YieldTermStructure>(*swapIndex_->discountingTermStructure());
200 } catch (const std::exception& e) {
202 "Error when retrieving swap index base for qualifier '" + data_->qualifier() +
203 "'. Use market discount curve instead of swap index discount curve as a fallback.",
204 e.what(), id_)
205 .log();
206 modelDiscountCurve_ = RelinkableHandle<YieldTermStructure>(*market_->discountCurve(currency_, configuration_));
207 calibrationDiscountCurve_ = Handle<YieldTermStructure>(*market_->discountCurve(currency_, configuration_));
208 }
209
211 registerWith(svts_);
212 marketObserver_->addObservable(swapIndex_->forwardingTermStructure());
213 marketObserver_->addObservable(shortSwapIndex_->forwardingTermStructure());
214 marketObserver_->addObservable(shortSwapIndex_->discountingTermStructure());
215 }
216 // we do not register with modelDiscountCurve_, since this curve does not affect the calibration
218 registerWith(marketObserver_);
219 // notify observers of all market data changes, not only when not calculated
220 alwaysForwardNotifications();
221
222 swaptionActive_ = std::vector<bool>(data_->optionExpiries().size(), false);
223
226 }
227
228 Array aTimes(data_->aTimes().begin(), data_->aTimes().end());
229 Array hTimes(data_->hTimes().begin(), data_->hTimes().end());
230 Array alpha(data_->aValues().begin(), data_->aValues().end());
231 Array h(data_->hValues().begin(), data_->hValues().end());
232
233 if (data_->aParamType() == ParamType::Constant) {
234 QL_REQUIRE(data_->aTimes().size() == 0,
235 "LgmBuilder: empty volatility time grid expected for constant parameter type");
236 QL_REQUIRE(data_->aValues().size() == 1,
237 "LgmBuilder: initial volatility values should have size 1 for constant parameter type");
238 } else if (data_->aParamType() == ParamType::Piecewise) {
239 if (data_->calibrateA() && data_->calibrationType() == CalibrationType::Bootstrap) {
240 if (data_->aTimes().size() > 0) {
241 DLOG("overriding alpha time grid with swaption expiries, set all initial values to first given value");
242 }
243 QL_REQUIRE(swaptionExpiries_.size() > 0, "empty swaptionExpiries");
244 aTimes = Array(swaptionExpiries_.begin(), swaptionExpiries_.end() - 1);
245 alpha = Array(aTimes.size() + 1, data_->aValues()[0]);
246 } else {
247 QL_REQUIRE(alpha.size() == aTimes.size() + 1,
248 "LgmBuilder: LGM volatility time and initial value array sizes do not match");
249 }
250 } else
251 QL_FAIL("LgmBuilder: volatility parameter type not covered");
252
253 if (data_->hParamType() == ParamType::Constant) {
254 QL_REQUIRE(data_->hTimes().size() == 0,
255 "LgmBuilder: empty reversion time grid expected for constant parameter type");
256 QL_REQUIRE(data_->hValues().size() == 1,
257 "LgmBuidler: initial reversion values should have size 1 for constant parameter type");
258 } else if (data_->hParamType() == ParamType::Piecewise) {
259 if (data_->calibrateH() && data_->calibrationType() == CalibrationType::Bootstrap) {
260 if (data_->hTimes().size() > 0) {
261 DLOG("overriding h time grid with swaption underlying maturities, set all initial values to first "
262 "given value");
263 }
264 hTimes = swaptionMaturities_;
265 h = Array(hTimes.size() + 1, data_->hValues()[0]);
266 } else { // use input time grid and input h array otherwise
267 QL_REQUIRE(h.size() == hTimes.size() + 1, "H grids do not match");
268 }
269 } else
270 QL_FAIL("LgmBuilder: reversion parameter type case not covered");
271
272 DLOG("before calibration: alpha times = " << aTimes << " values = " << alpha);
273 DLOG("before calibration: h times = " << hTimes << " values = " << h);
274
275 if (data_->reversionType() == LgmData::ReversionType::HullWhite &&
276 data_->volatilityType() == LgmData::VolatilityType::HullWhite) {
277 DLOG("IR parametrization for " << qualifier << ": IrLgm1fPiecewiseConstantHullWhiteAdaptor");
278 parametrization_ = QuantLib::ext::make_shared<QuantExt::IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
279 ccy, modelDiscountCurve_, aTimes, alpha, hTimes, h);
280 } else if (data_->reversionType() == LgmData::ReversionType::HullWhite &&
281 data_->volatilityType() == LgmData::VolatilityType::Hagan) {
282 DLOG("IR parametrization for " << qualifier << ": IrLgm1fPiecewiseConstant");
283 parametrization_ = QuantLib::ext::make_shared<QuantExt::IrLgm1fPiecewiseConstantParametrization>(
284 ccy, modelDiscountCurve_, aTimes, alpha, hTimes, h);
285 } else if (data_->reversionType() == LgmData::ReversionType::Hagan &&
286 data_->volatilityType() == LgmData::VolatilityType::Hagan) {
287 parametrization_ = QuantLib::ext::make_shared<QuantExt::IrLgm1fPiecewiseLinearParametrization>(
288 ccy, modelDiscountCurve_, aTimes, alpha, hTimes, h);
289 DLOG("IR parametrization for " << qualifier << ": IrLgm1fPiecewiseLinear");
290 } else {
291 QL_FAIL("LgmBuilder: Reversion type Hagan and volatility type HullWhite not covered");
292 }
293 DLOG("alpha times size: " << aTimes.size());
294 DLOG("lambda times size: " << hTimes.size());
295
296 model_ = QuantLib::ext::make_shared<QuantExt::LGM>(parametrization_);
297 params_ = model_->params();
298}
299
300Real LgmBuilder::error() const {
301 calculate();
302 return error_;
303}
304
305QuantLib::ext::shared_ptr<QuantExt::LGM> LgmBuilder::model() const {
306 calculate();
307 return model_;
308}
309
310QuantLib::ext::shared_ptr<QuantExt::IrLgm1fParametrization> LgmBuilder::parametrization() const {
311 calculate();
312 return parametrization_;
313}
314
315std::vector<QuantLib::ext::shared_ptr<BlackCalibrationHelper>> LgmBuilder::swaptionBasket() const {
316 calculate();
317 return swaptionBasket_;
318}
319
321 return requiresCalibration_ &&
322 (volSurfaceChanged(false) || marketObserver_->hasUpdated(false) || forceCalibration_);
323}
324
326
327 DLOG("Recalibrate LGM model for qualifier " << data_->qualifier() << " currency " << currency_);
328
329 if (!requiresRecalibration()) {
330 DLOG("Skipping calibration as nothing has changed");
331 return;
332 }
333
334 // reset lgm observer's updated flag
335 marketObserver_->hasUpdated(true);
336
337 if (swaptionBasketRefDate_ != calibrationDiscountCurve_->referenceDate()) {
338 // build swaption basket if required, i.e. if reference date has changed since last build
340 volSurfaceChanged(true);
342 } else {
343 // otherwise just update vols
344 volSurfaceChanged(true);
346 }
347
348 for (Size j = 0; j < swaptionBasket_.size(); j++) {
349 auto engine = QuantLib::ext::make_shared<QuantExt::AnalyticLgmSwaptionEngine>(model_, calibrationDiscountCurve_,
350 data_->floatSpreadMapping());
351 engine->enableCache(!data_->calibrateH(), !data_->calibrateA());
352 swaptionBasket_[j]->setPricingEngine(engine);
353 // necessary if notifications are disabled (observation mode = Disable)
354 swaptionBasket_[j]->update();
355 }
356
357 // reset model parameters to ensure identical results on identical market data input
358 model_->setParams(params_);
359 parametrization_->shift() = 0.0;
360 parametrization_->scaling() = 1.0;
361
362 LgmCalibrationInfo calibrationInfo;
363 error_ = QL_MAX_REAL;
364 std::string errorTemplate =
365 std::string("Failed to calibrate LGM Model. ") +
366 (continueOnError_ ? std::string("Calculation will proceed anyway - using the calibration as is!")
367 : std::string("Calculation will aborted."));
368 try {
369 if (data_->calibrateA() && !data_->calibrateH() && data_->calibrationType() == CalibrationType::Bootstrap) {
370 DLOG("call calibrateVolatilitiesIterative for volatility calibration (bootstrap)");
371 model_->calibrateVolatilitiesIterative(swaptionBasket_, *optimizationMethod_, endCriteria_);
372 } else if (data_->calibrateH() && !data_->calibrateA() &&
373 data_->calibrationType() == CalibrationType::Bootstrap) {
374 DLOG("call calibrateReversionsIterative for reversion calibration (bootstrap)");
375 model_->calibrateVolatilitiesIterative(swaptionBasket_, *optimizationMethod_, endCriteria_);
376 } else {
377 QL_REQUIRE(data_->calibrationType() != CalibrationType::Bootstrap,
378 "LgmBuidler: Calibration type Bootstrap can be used with volatilities and reversions calibrated "
379 "simultaneously. Either choose BestFit oder fix one of these parameters.");
380 if (data_->calibrateA() && !data_->calibrateH()) {
381 DLOG("call calibrateVolatilities for (global) volatility calibration")
382 model_->calibrateVolatilities(swaptionBasket_, *optimizationMethod_, endCriteria_);
383 } else if (data_->calibrateH() && !data_->calibrateA()) {
384 DLOG("call calibrateReversions for (global) reversion calibration")
386 } else {
387 DLOG("call calibrate for global volatility and reversion calibration");
389 }
390 }
391 TLOG("LGM " << data_->qualifier() << " calibration errors:");
393 } catch (const std::exception& e) {
394 // just log a warning, we check below if we meet the bootstrap tolerance and handle the result there
395 StructuredModelErrorMessage(errorTemplate, e.what(), id_).log();
396 }
397 calibrationInfo.rmse = error_;
398 if (fabs(error_) < bootstrapTolerance_ ||
399 (data_->calibrationType() == CalibrationType::BestFit && error_ != QL_MAX_REAL)) {
400 // we check the log level here to avoid unnecessary computations
401 if (Log::instance().filter(ORE_DATA) || setCalibrationInfo_) {
402 TLOGGERSTREAM("Basket details:");
403 try {
404 auto d = getBasketDetails(calibrationInfo);
405 TLOGGERSTREAM(d);
406 } catch (const std::exception& e) {
407 WLOG("An error occurred: " << e.what());
408 }
409 TLOGGERSTREAM("Calibration details (with time grid = calibration swaption expiries):");
410 try {
411 auto d = getCalibrationDetails(calibrationInfo, swaptionBasket_, parametrization_);
412 TLOGGERSTREAM(d);
413 } catch (const std::exception& e) {
414 WLOG("An error occurred: " << e.what());
415 }
416 TLOGGERSTREAM("Parameter details (with parameter time grid)");
418 TLOGGERSTREAM("rmse = " << error_);
419 calibrationInfo.valid = true;
420 }
421 } else {
422 std::string exceptionMessage = "LGM (" + data_->qualifier() + ") calibration error " + std::to_string(error_) +
423 " exceeds tolerance " + std::to_string(bootstrapTolerance_);
424 StructuredModelErrorMessage(errorTemplate, exceptionMessage, id_).log();
425 WLOGGERSTREAM("Basket details:");
426 try {
427 auto d = getBasketDetails(calibrationInfo);
428 WLOGGERSTREAM(d);
429 } catch (const std::exception& e) {
430 WLOG("An error occurred: " << e.what());
431 }
432 WLOGGERSTREAM("Calibration details (with time grid = calibration swaption expiries):");
433 try {
434 auto d = getCalibrationDetails(calibrationInfo, swaptionBasket_, parametrization_);
435 WLOGGERSTREAM(d);
436 } catch (const std::exception& e) {
437 WLOG("An error occurred: " << e.what());
438 }
439 WLOGGERSTREAM("Parameter details (with parameter time grid)");
441 WLOGGERSTREAM("rmse = " << error_);
442 calibrationInfo.valid = true;
443 if (!continueOnError_) {
444 QL_FAIL(exceptionMessage);
445 }
446 }
447 model_->setCalibrationInfo(calibrationInfo);
448
449 DLOG("Apply shift horizon and scale (if not 0.0 and 1.0 respectively)");
450
451 QL_REQUIRE(data_->shiftHorizon() >= 0.0, "shift horizon must be non negative");
452 QL_REQUIRE(data_->scaling() > 0.0, "scaling must be positive");
453
454 if (data_->shiftHorizon() > 0.0) {
455 Real value = -parametrization_->H(data_->shiftHorizon());
456 DLOG("Apply shift horizon " << data_->shiftHorizon() << " (C=" << value << ") to the " << data_->qualifier()
457 << " LGM model");
458 parametrization_->shift() = value;
459 }
460
461 if (data_->scaling() != 1.0) {
462 DLOG("Apply scaling " << data_->scaling() << " to the " << data_->qualifier() << " LGM model");
463 parametrization_->scaling() = data_->scaling();
464 }
465} // performCalculations()
466
467void LgmBuilder::getExpiryAndTerm(const Size j, Period& expiryPb, Period& termPb, Date& expiryDb, Date& termDb,
468 Real& termT, bool& expiryDateBased, bool& termDateBased) const {
469 std::string expiryString = data_->optionExpiries()[j];
470 std::string termString = data_->optionTerms()[j];
471 parseDateOrPeriod(expiryString, expiryDb, expiryPb, expiryDateBased);
472 parseDateOrPeriod(termString, termDb, termPb, termDateBased);
473 if (termDateBased) {
474 Date tmpExpiry = expiryDateBased ? expiryDb : svts_->optionDateFromTenor(expiryPb);
475 Date tmpStart = swapIndex_->iborIndex()->valueDate(swapIndex_->iborIndex()->fixingCalendar().adjust(tmpExpiry));
476 // ensure that we have a term >= 1 Month, otherwise QL might throw "non-positive swap length (0) given" from
477 // the black swaption engine during calibration helper pricing; also notice that we use the swap length
478 // calculated in the svts (i.e. a length rounded to whole months) to read the volatility from the cube, which is
479 // consistent with what is done in BlackSwaptionEngine (although one might ask whether an interpolated
480 // volatility would be more appropriate)
481 termDb = std::max(termDb, tmpStart + 1 * Months);
482 termT = svts_->swapLength(tmpStart, termDb);
483 } else {
484 termT = svts_->swapLength(termPb);
485 // same as above, make sure the underlying term is at least >= 1 Month, but since Period::operator<
486 // throws in certain circumstances, we do the comparison based on termT here:
487 if (termT < 1.0 / 12.0) {
488 termT = 1.0 / 12.0;
489 termPb = 1 * Months;
490 }
491 }
492}
493
494Real LgmBuilder::getStrike(const Size j) const {
495 DLOG("LgmBuilder::getStrike(" << j << "): '" << data_->optionStrikes()[j] << "'");
496 Strike strike = parseStrike(data_->optionStrikes()[j]);
497 Real strikeValue;
498 // TODO: Extend strike type coverage
499 if (strike.type == Strike::Type::ATM)
500 strikeValue = Null<Real>();
501 else if (strike.type == Strike::Type::Absolute)
502 strikeValue = strike.value;
503 else
504 QL_FAIL("strike type ATM or Absolute expected");
505 return strikeValue;
506}
507
508bool LgmBuilder::volSurfaceChanged(const bool updateCache) const {
509 bool hasUpdated = false;
510
511 // create cache if not equal to required size
512 if (swaptionVolCache_.size() != swaptionBasket_.size())
513 swaptionVolCache_ = vector<Real>(swaptionBasket_.size(), Null<Real>());
514
515 Size swaptionCounter = 0;
516 for (Size j = 0; j < data_->optionExpiries().size(); j++) {
517 if (!swaptionActive_[j])
518 continue;
519
520 Real volCache = swaptionVolCache_.at(swaptionCounter);
521
522 bool expiryDateBased, termDateBased;
523 Period expiryPb, termPb;
524 Date expiryDb, termDb;
525 Real termT;
526
527 getExpiryAndTerm(j, expiryPb, termPb, expiryDb, termDb, termT, expiryDateBased, termDateBased);
528 Real strikeValue = swaptionStrike_.at(swaptionCounter);
529
530 Real vol;
531 if (expiryDateBased && termDateBased) {
532 vol = svts_->volatility(expiryDb, termT, strikeValue);
533 } else if (expiryDateBased && !termDateBased) {
534 vol = svts_->volatility(expiryDb, termPb, strikeValue);
535 } else if (!expiryDateBased && termDateBased) {
536 vol = svts_->volatility(expiryPb, termT, strikeValue);
537 } else {
538 // !expiryDateBased && !termDateBased
539 vol = svts_->volatility(expiryPb, termPb, strikeValue);
540 }
541
542 if (!close_enough(volCache, vol)) {
543 if (updateCache)
544 swaptionVolCache_[swaptionCounter] = vol;
545 hasUpdated = true;
546 }
547 swaptionCounter++;
548 }
549 return hasUpdated;
550}
551
553 for (Size j = 0; j < swaptionBasketVols_.size(); ++j)
554 swaptionBasketVols_.at(j)->setValue(swaptionVolCache_.at(j));
555}
556
558
559 DLOG("build swaption basket");
560
561 QL_REQUIRE(data_->optionExpiries().size() == data_->optionTerms().size(), "swaption vector size mismatch");
562 QL_REQUIRE(data_->optionExpiries().size() == data_->optionStrikes().size(), "swaption vector size mismatch");
563
564 std::ostringstream log;
565
566 std::vector<Time> expiryTimes;
567 std::vector<Time> maturityTimes;
568 swaptionBasket_.clear();
569 swaptionBasketVols_.clear();
570 swaptionVolCache_.clear();
571 swaptionStrike_.clear();
572
573 DLOG("build reference date grid '" << referenceCalibrationGrid_ << "'");
574 Date lastRefCalDate = Date::minDate();
575 std::vector<Date> referenceCalibrationDates;
576 if (!referenceCalibrationGrid_.empty())
577 referenceCalibrationDates = DateGrid(referenceCalibrationGrid_).dates();
578
579 for (Size j = 0; j < data_->optionExpiries().size(); j++) {
580 bool expiryDateBased, termDateBased;
581 Period expiryPb, termPb;
582 Date expiryDb, termDb;
583 Real termT = Null<Real>();
584
585 getExpiryAndTerm(j, expiryPb, termPb, expiryDb, termDb, termT, expiryDateBased, termDateBased);
586 Real strikeValue = getStrike(j);
587
588 // rounded to whole years, only used to distinguish between short and long
589 // swap tenors, which in practice always are multiples of whole years
590 Period termTmp = static_cast<Size>(termT + 0.5) * Years;
591 auto iborIndex = termTmp > shortSwapIndex_->tenor() ? swapIndex_->iborIndex() : shortSwapIndex_->iborIndex();
592 auto fixedLegTenor =
593 termTmp > shortSwapIndex_->tenor() ? swapIndex_->fixedLegTenor() : shortSwapIndex_->fixedLegTenor();
594 auto fixedDayCounter =
595 termTmp > shortSwapIndex_->tenor() ? swapIndex_->dayCounter() : shortSwapIndex_->dayCounter();
596 auto floatDayCounter = termTmp > shortSwapIndex_->tenor() ? swapIndex_->iborIndex()->dayCounter()
597 : shortSwapIndex_->iborIndex()->dayCounter();
598 Size settlementDays = Null<Size>();
599 RateAveraging::Type averagingMethod = RateAveraging::Compound;
600 if (auto on = dynamic_pointer_cast<OvernightIndexedSwapIndex>(*swapIndex_)) {
601 settlementDays = on->fixingDays();
602 averagingMethod = on->averagingMethod();
603 }
604
605 Real dummyQuote = svts_->volatilityType() == Normal ? 0.0020 : 0.10;
606 auto volQuote = QuantLib::ext::make_shared<SimpleQuote>(dummyQuote);
607 Handle<Quote> vol = Handle<Quote>(volQuote);
608 QuantLib::ext::shared_ptr<SwaptionHelper> helper;
609 Real updatedStrike;
610
611 if (expiryDateBased && termDateBased) {
612 Real shift = svts_->volatilityType() == ShiftedLognormal ? svts_->shift(expiryDb, termT) : 0.0;
613 std::tie(helper, updatedStrike) = createSwaptionHelper(
614 expiryDb, termDb, svts_, vol, iborIndex, fixedLegTenor, fixedDayCounter, floatDayCounter,
615 calibrationDiscountCurve_, calibrationErrorType_, strikeValue, shift, settlementDays, averagingMethod);
616 }
617 if (expiryDateBased && !termDateBased) {
618 Real shift = svts_->volatilityType() == ShiftedLognormal ? svts_->shift(expiryDb, termPb) : 0.0;
619 std::tie(helper, updatedStrike) = createSwaptionHelper(
620 expiryDb, termPb, svts_, vol, iborIndex, fixedLegTenor, fixedDayCounter, floatDayCounter,
621 calibrationDiscountCurve_, calibrationErrorType_, strikeValue, shift, settlementDays, averagingMethod);
622 }
623 if (!expiryDateBased && termDateBased) {
624 Date expiry = svts_->optionDateFromTenor(expiryPb);
625 Real shift = svts_->volatilityType() == ShiftedLognormal ? svts_->shift(expiryPb, termT) : 0.0;
626 std::tie(helper, updatedStrike) = createSwaptionHelper(
627 expiry, termDb, svts_, vol, iborIndex, fixedLegTenor, fixedDayCounter, floatDayCounter,
628 calibrationDiscountCurve_, calibrationErrorType_, strikeValue, shift, settlementDays, averagingMethod);
629 }
630 if (!expiryDateBased && !termDateBased) {
631 Real shift = svts_->volatilityType() == ShiftedLognormal ? svts_->shift(expiryPb, termPb) : 0.0;
632 std::tie(helper, updatedStrike) = createSwaptionHelper(
633 expiryPb, termPb, svts_, vol, iborIndex, fixedLegTenor, fixedDayCounter, floatDayCounter,
634 calibrationDiscountCurve_, calibrationErrorType_, strikeValue, shift, settlementDays, averagingMethod);
635 }
636
637 // check if we want to keep the helper when a reference calibration grid is given
638 Date expiryDate = helper->swaption()->exercise()->date(0);
639 auto refCalDate =
640 std::lower_bound(referenceCalibrationDates.begin(), referenceCalibrationDates.end(), expiryDate);
641 if (refCalDate == referenceCalibrationDates.end() || *refCalDate > lastRefCalDate) {
642 swaptionActive_[j] = true;
643 swaptionBasketVols_.push_back(volQuote);
644 swaptionBasket_.push_back(helper);
645 swaptionStrike_.push_back(updatedStrike);
646 expiryTimes.push_back(calibrationDiscountCurve_->timeFromReference(expiryDate));
647 Date matDate = helper->underlyingSwap() ? helper->underlyingSwap()->maturityDate()
648 : helper->underlyingOvernightIndexedSwap()->maturityDate();
649 maturityTimes.push_back(calibrationDiscountCurve_->timeFromReference(matDate));
650 if (refCalDate != referenceCalibrationDates.end())
651 lastRefCalDate = *refCalDate;
652 }
653 }
654
655 std::sort(expiryTimes.begin(), expiryTimes.end());
656 auto itExpiryTime = unique(expiryTimes.begin(), expiryTimes.end());
657 expiryTimes.resize(distance(expiryTimes.begin(), itExpiryTime));
658
659 swaptionExpiries_ = Array(expiryTimes.size());
660 for (Size j = 0; j < expiryTimes.size(); j++)
661 swaptionExpiries_[j] = expiryTimes[j];
662
663 std::sort(maturityTimes.begin(), maturityTimes.end());
664 auto itMaturityTime = unique(maturityTimes.begin(), maturityTimes.end());
665 maturityTimes.resize(distance(maturityTimes.begin(), itMaturityTime));
666
667 swaptionMaturities_ = Array(maturityTimes.size());
668 for (Size j = 0; j < maturityTimes.size(); j++)
669 swaptionMaturities_[j] = maturityTimes[j];
670
672}
673
675 std::ostringstream log;
676 log << std::right << std::setw(3) << "#" << std::setw(16) << "expiry" << std::setw(16) << "swapLength"
677 << std::setw(16) << "strike" << std::setw(16) << "atmForward" << std::setw(16) << "annuity" << std::setw(16)
678 << "vega" << std::setw(16) << "vol\n";
679 info.swaptionData.clear();
680 for (Size j = 0; j < swaptionBasket_.size(); ++j) {
681 auto swp = QuantLib::ext::static_pointer_cast<SwaptionHelper>(swaptionBasket_[j])->swaption();
682 auto sd = swaptionData(swp, calibrationDiscountCurve_, svts_);
683 log << std::right << std::setw(3) << j << std::setw(16) << sd.timeToExpiry << std::setw(16) << sd.swapLength
684 << std::setw(16) << sd.strike << std::setw(16) << sd.atmForward << std::setw(16) << sd.annuity
685 << std::setw(16) << sd.vega << std::setw(16) << std::setw(16) << sd.stdDev / std::sqrt(sd.timeToExpiry)
686 << "\n";
687 info.swaptionData.push_back(sd);
688 }
689 return log.str();
690}
691
693 forceCalibration_ = true;
695 forceCalibration_ = false;
696}
697
698} // namespace data
699} // namespace ore
virtual void forceRecalculate()
Simulation Date Grid.
Definition: dategrid.hpp:38
const std::vector< QuantLib::Date > & dates() const
Definition: dategrid.hpp:81
void log() const
generate Boost log record to pass to corresponding sinks
Definition: log.cpp:491
std::vector< bool > swaptionActive_
Definition: lgmbuilder.hpp:110
Handle< SwapIndex > swapIndex_
Definition: lgmbuilder.hpp:119
const std::string configuration_
Definition: lgmbuilder.hpp:94
void forceRecalculate() override
Definition: lgmbuilder.cpp:692
BlackCalibrationHelper::CalibrationErrorType calibrationErrorType_
Definition: lgmbuilder.hpp:126
void performCalculations() const override
Definition: lgmbuilder.cpp:325
bool volSurfaceChanged(const bool updateCache) const
Definition: lgmbuilder.cpp:508
LgmBuilder(const QuantLib::ext::shared_ptr< ore::data::Market > &market, const QuantLib::ext::shared_ptr< IrLgmData > &data, const std::string &configuration=Market::defaultConfiguration, Real bootstrapTolerance=0.001, const bool continueOnError=false, const std::string &referenceCalibrationGrid="", const bool setCalibrationInfo=false, const std::string &id="unknwon")
Definition: lgmbuilder.cpp:167
std::string getBasketDetails(QuantExt::LgmCalibrationInfo &info) const
Definition: lgmbuilder.cpp:674
QuantLib::ext::shared_ptr< QuantExt::IrLgm1fParametrization > parametrization() const
Definition: lgmbuilder.cpp:310
QuantLib::ext::shared_ptr< QuantExt::MarketObserver > marketObserver_
Definition: lgmbuilder.hpp:134
void updateSwaptionBasketVols() const
Definition: lgmbuilder.cpp:552
const bool continueOnError_
Definition: lgmbuilder.hpp:97
const Real bootstrapTolerance_
Definition: lgmbuilder.hpp:96
std::vector< QuantLib::ext::shared_ptr< SimpleQuote > > swaptionBasketVols_
Definition: lgmbuilder.hpp:113
const std::string id_
Definition: lgmbuilder.hpp:100
QuantLib::ext::shared_ptr< QuantExt::IrLgm1fParametrization > parametrization_
Definition: lgmbuilder.hpp:107
bool requiresRecalibration() const override
Definition: lgmbuilder.cpp:320
std::vector< QuantLib::ext::shared_ptr< BlackCalibrationHelper > > swaptionBasket() const
Definition: lgmbuilder.cpp:315
Handle< YieldTermStructure > calibrationDiscountCurve_
Definition: lgmbuilder.hpp:121
std::vector< QuantLib::Real > swaptionVolCache_
Definition: lgmbuilder.hpp:129
EndCriteria endCriteria_
Definition: lgmbuilder.hpp:125
std::vector< Real > swaptionStrike_
Definition: lgmbuilder.hpp:112
QuantLib::ext::shared_ptr< OptimizationMethod > optimizationMethod_
Definition: lgmbuilder.hpp:124
QuantLib::ext::shared_ptr< ore::data::Market > market_
Definition: lgmbuilder.hpp:93
Real getStrike(const Size j) const
Definition: lgmbuilder.cpp:494
std::vector< QuantLib::ext::shared_ptr< BlackCalibrationHelper > > swaptionBasket_
Definition: lgmbuilder.hpp:111
void buildSwaptionBasket() const
Definition: lgmbuilder.cpp:557
Handle< SwapIndex > shortSwapIndex_
Definition: lgmbuilder.hpp:119
const std::string referenceCalibrationGrid_
Definition: lgmbuilder.hpp:98
std::string ccy()
Definition: lgmbuilder.hpp:64
std::string qualifier()
Definition: lgmbuilder.hpp:63
Real error() const
Return calibration error.
Definition: lgmbuilder.cpp:300
QuantLib::ext::shared_ptr< IrLgmData > data_
Definition: lgmbuilder.hpp:95
void getExpiryAndTerm(const Size j, Period &expiryPb, Period &termPb, Date &expiryDb, Date &termDb, Real &termT, bool &expiryDateBased, bool &termDateBased) const
Definition: lgmbuilder.cpp:467
QuantLib::ext::shared_ptr< QuantExt::LGM > model() const
Definition: lgmbuilder.cpp:305
QuantLib::ext::shared_ptr< QuantExt::LGM > model_
Definition: lgmbuilder.hpp:105
const bool setCalibrationInfo_
Definition: lgmbuilder.hpp:99
Handle< QuantLib::SwaptionVolatilityStructure > svts_
Definition: lgmbuilder.hpp:118
RelinkableHandle< YieldTermStructure > modelDiscountCurve_
Definition: lgmbuilder.hpp:120
@ Hagan
Parametrize LGM H(t) as H(t) = int_0^t h(s) ds with constant or piecewise h(s)
@ HullWhite
Parametrize volatility as HullWhite sigma(t)
@ Hagan
Parametrize volatility as Hagan alpha(t)
Utility class for Structured Model errors.
SafeStack< ValueType > value
SafeStack< Filter > filter
The date grid class.
Strike parseStrike(const std::string &s)
Convert text to Strike.
Definition: strike.cpp:30
bool tryParseIborIndex(const string &s, QuantLib::ext::shared_ptr< IborIndex > &index)
Try to convert std::string to QuantLib::IborIndex.
boost::variant< QuantLib::Date, QuantLib::Period > parseDateOrPeriod(const string &s)
Convert text to QuantLib::Period or QuantLib::Date.
Definition: parsers.cpp:493
Currency parseCurrency(const string &s)
Convert text to QuantLib::Currency.
Definition: parsers.cpp:290
Map text representations to QuantLib/QuantExt types.
Build an lgm model.
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define WLOGGERSTREAM(text)
Definition: log.hpp:630
#define LOG(text)
Logging Macro (Level = Notice)
Definition: log.hpp:552
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define TLOGGERSTREAM(text)
Definition: log.hpp:633
#define ORE_DATA
Definition: log.hpp:33
#define WLOG(text)
Logging Macro (Level = Warning)
Definition: log.hpp:550
#define TLOG(text)
Logging Macro (Level = Data)
Definition: log.hpp:556
Shared utilities for model building and calibration.
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
RandomVariable log(RandomVariable x)
Real getCalibrationError(const std::vector< QuantLib::ext::shared_ptr< Helper > > &basket)
Definition: utilities.hpp:47
Size size(const ValueType &v)
Definition: value.cpp:145
std::string getCalibrationDetails(LgmCalibrationInfo &info, const std::vector< QuantLib::ext::shared_ptr< BlackCalibrationHelper > > &basket, const QuantLib::ext::shared_ptr< IrLgm1fParametrization > &parametrization)
Definition: utilities.cpp:125
Serializable Credit Default Swap.
Definition: namespaces.docs:23
Map text representations to QuantLib/QuantExt types.
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
std::vector< SwaptionData > swaptionData
QuantLib::Real value
Definition: strike.hpp:47
Error for model calibration / building.
strike description