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>
47SwaptionData swaptionData(
const QuantLib::ext::shared_ptr<Swaption> swaption,
const Handle<YieldTermStructure>& yts,
48 const Handle<SwaptionVolatilityStructure>& svts) {
50 QuantLib::ext::shared_ptr<PricingEngine> engine;
51 switch (svts->volatilityType()) {
52 case ShiftedLognormal:
53 engine = QuantLib::ext::make_shared<BlackSwaptionEngine>(yts, svts);
56 engine = QuantLib::ext::make_shared<BachelierSwaptionEngine>(yts, svts);
59 QL_FAIL(
"Could not construct swaption engine for volatility type: " << svts->volatilityType());
63 swaption->setPricingEngine(engine);
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");
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) {
86 DLOG(
"LgmBuilder::createSwaptionHelper(" << expiry <<
", " << term <<
")");
92 static constexpr Real maxAtmStdDev = 3.0;
96 static constexpr Real mmv = 1.0E-20;
99 static constexpr Real smv = 1.0E-8;
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);
113 if (vt == ShiftedLognormal) {
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);
121 strike = sd.
atmForward + maxAtmStdDev * atmStdDev;
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);
131 auto mv = std::abs(
helper->marketValue());
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);
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);
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);
155 DLOG(
"Created swaption helper with expiry " << expiry <<
" and term " << term <<
": vol=" << vol->value()
156 <<
", index=" << iborIndex->name() <<
", strike=" << strike
157 <<
", shift=" << shift);
159 return std::make_pair(
helper, strike);
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),
175 endCriteria_(EndCriteria(1000, 500, 1E-8, 1E-8, 1E-8)),
181 QuantLib::ext::shared_ptr<IborIndex> index;
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.",
220 alwaysForwardNotifications();
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());
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");
240 if (
data_->aTimes().size() > 0) {
241 DLOG(
"overriding alpha time grid with swaption expiries, set all initial values to first given value");
245 alpha = Array(aTimes.size() + 1,
data_->aValues()[0]);
247 QL_REQUIRE(alpha.size() == aTimes.size() + 1,
248 "LgmBuilder: LGM volatility time and initial value array sizes do not match");
251 QL_FAIL(
"LgmBuilder: volatility parameter type not covered");
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");
260 if (
data_->hTimes().size() > 0) {
261 DLOG(
"overriding h time grid with swaption underlying maturities, set all initial values to first "
265 h = Array(hTimes.size() + 1,
data_->hValues()[0]);
267 QL_REQUIRE(h.size() == hTimes.size() + 1,
"H grids do not match");
270 QL_FAIL(
"LgmBuilder: reversion parameter type case not covered");
272 DLOG(
"before calibration: alpha times = " << aTimes <<
" values = " << alpha);
273 DLOG(
"before calibration: h times = " << hTimes <<
" values = " << h);
277 DLOG(
"IR parametrization for " <<
qualifier <<
": IrLgm1fPiecewiseConstantHullWhiteAdaptor");
278 parametrization_ = QuantLib::ext::make_shared<QuantExt::IrLgm1fPiecewiseConstantHullWhiteAdaptor>(
282 DLOG(
"IR parametrization for " <<
qualifier <<
": IrLgm1fPiecewiseConstant");
283 parametrization_ = QuantLib::ext::make_shared<QuantExt::IrLgm1fPiecewiseConstantParametrization>(
287 parametrization_ = QuantLib::ext::make_shared<QuantExt::IrLgm1fPiecewiseLinearParametrization>(
289 DLOG(
"IR parametrization for " <<
qualifier <<
": IrLgm1fPiecewiseLinear");
291 QL_FAIL(
"LgmBuilder: Reversion type Hagan and volatility type HullWhite not covered");
293 DLOG(
"alpha times size: " << aTimes.size());
294 DLOG(
"lambda times size: " << hTimes.size());
327 DLOG(
"Recalibrate LGM model for qualifier " <<
data_->qualifier() <<
" currency " <<
currency_);
330 DLOG(
"Skipping calibration as nothing has changed");
350 data_->floatSpreadMapping());
351 engine->enableCache(!
data_->calibrateH(), !
data_->calibrateA());
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."));
370 DLOG(
"call calibrateVolatilitiesIterative for volatility calibration (bootstrap)");
372 }
else if (
data_->calibrateH() && !
data_->calibrateA() &&
374 DLOG(
"call calibrateReversionsIterative for reversion calibration (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")
383 }
else if (
data_->calibrateH() && !
data_->calibrateA()) {
384 DLOG(
"call calibrateReversions for (global) reversion calibration")
387 DLOG(
"call calibrate for global volatility and reversion calibration");
391 TLOG(
"LGM " <<
data_->qualifier() <<
" calibration errors:");
393 }
catch (
const std::exception& e) {
406 }
catch (
const std::exception& e) {
407 WLOG(
"An error occurred: " << e.what());
409 TLOGGERSTREAM(
"Calibration details (with time grid = calibration swaption expiries):");
413 }
catch (
const std::exception& e) {
414 WLOG(
"An error occurred: " << e.what());
416 TLOGGERSTREAM(
"Parameter details (with parameter time grid)");
419 calibrationInfo.
valid =
true;
422 std::string exceptionMessage =
"LGM (" +
data_->qualifier() +
") calibration error " + std::to_string(
error_) +
429 }
catch (
const std::exception& e) {
430 WLOG(
"An error occurred: " << e.what());
432 WLOGGERSTREAM(
"Calibration details (with time grid = calibration swaption expiries):");
436 }
catch (
const std::exception& e) {
437 WLOG(
"An error occurred: " << e.what());
439 WLOGGERSTREAM(
"Parameter details (with parameter time grid)");
442 calibrationInfo.
valid =
true;
444 QL_FAIL(exceptionMessage);
447 model_->setCalibrationInfo(calibrationInfo);
449 DLOG(
"Apply shift horizon and scale (if not 0.0 and 1.0 respectively)");
451 QL_REQUIRE(
data_->shiftHorizon() >= 0.0,
"shift horizon must be non negative");
452 QL_REQUIRE(
data_->scaling() > 0.0,
"scaling must be positive");
454 if (
data_->shiftHorizon() > 0.0) {
456 DLOG(
"Apply shift horizon " <<
data_->shiftHorizon() <<
" (C=" <<
value <<
") to the " <<
data_->qualifier()
461 if (
data_->scaling() != 1.0) {
462 DLOG(
"Apply scaling " <<
data_->scaling() <<
" to the " <<
data_->qualifier() <<
" LGM model");
468 Real& termT,
bool& expiryDateBased,
bool& termDateBased)
const {
469 std::string expiryString =
data_->optionExpiries()[j];
470 std::string termString =
data_->optionTerms()[j];
474 Date tmpExpiry = expiryDateBased ? expiryDb :
svts_->optionDateFromTenor(expiryPb);
475 Date tmpStart =
swapIndex_->iborIndex()->valueDate(
swapIndex_->iborIndex()->fixingCalendar().adjust(tmpExpiry));
481 termDb = std::max(termDb, tmpStart + 1 * Months);
482 termT =
svts_->swapLength(tmpStart, termDb);
484 termT =
svts_->swapLength(termPb);
487 if (termT < 1.0 / 12.0) {
495 DLOG(
"LgmBuilder::getStrike(" << j <<
"): '" <<
data_->optionStrikes()[j] <<
"'");
500 strikeValue = Null<Real>();
502 strikeValue = strike.
value;
504 QL_FAIL(
"strike type ATM or Absolute expected");
509 bool hasUpdated =
false;
515 Size swaptionCounter = 0;
516 for (Size j = 0; j <
data_->optionExpiries().
size(); j++) {
522 bool expiryDateBased, termDateBased;
523 Period expiryPb, termPb;
524 Date expiryDb, termDb;
527 getExpiryAndTerm(j, expiryPb, termPb, expiryDb, termDb, termT, expiryDateBased, termDateBased);
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);
539 vol =
svts_->volatility(expiryPb, termPb, strikeValue);
559 DLOG(
"build swaption basket");
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");
564 std::ostringstream
log;
566 std::vector<Time> expiryTimes;
567 std::vector<Time> maturityTimes;
574 Date lastRefCalDate = Date::minDate();
575 std::vector<Date> referenceCalibrationDates;
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>();
585 getExpiryAndTerm(j, expiryPb, termPb, expiryDb, termDb, termT, expiryDateBased, termDateBased);
590 Period termTmp =
static_cast<Size
>(termT + 0.5) * Years;
594 auto fixedDayCounter =
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();
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;
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,
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,
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,
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,
638 Date expiryDate =
helper->swaption()->exercise()->date(0);
640 std::lower_bound(referenceCalibrationDates.begin(), referenceCalibrationDates.end(), expiryDate);
641 if (refCalDate == referenceCalibrationDates.end() || *refCalDate > lastRefCalDate) {
647 Date matDate =
helper->underlyingSwap() ?
helper->underlyingSwap()->maturityDate()
648 :
helper->underlyingOvernightIndexedSwap()->maturityDate();
650 if (refCalDate != referenceCalibrationDates.end())
651 lastRefCalDate = *refCalDate;
655 std::sort(expiryTimes.begin(), expiryTimes.end());
656 auto itExpiryTime = unique(expiryTimes.begin(), expiryTimes.end());
657 expiryTimes.resize(distance(expiryTimes.begin(), itExpiryTime));
660 for (Size j = 0; j < expiryTimes.size(); j++)
663 std::sort(maturityTimes.begin(), maturityTimes.end());
664 auto itMaturityTime = unique(maturityTimes.begin(), maturityTimes.end());
665 maturityTimes.resize(distance(maturityTimes.begin(), itMaturityTime));
668 for (Size j = 0; j < maturityTimes.size(); j++)
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";
681 auto swp = QuantLib::ext::static_pointer_cast<SwaptionHelper>(
swaptionBasket_[j])->swaption();
virtual void forceRecalculate()
const std::vector< QuantLib::Date > & dates() const
void log() const
generate Boost log record to pass to corresponding sinks
std::vector< bool > swaptionActive_
Handle< SwapIndex > swapIndex_
const std::string configuration_
void forceRecalculate() override
BlackCalibrationHelper::CalibrationErrorType calibrationErrorType_
void performCalculations() const override
bool volSurfaceChanged(const bool updateCache) const
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")
std::string getBasketDetails(QuantExt::LgmCalibrationInfo &info) const
QuantLib::ext::shared_ptr< QuantExt::IrLgm1fParametrization > parametrization() const
QuantLib::ext::shared_ptr< QuantExt::MarketObserver > marketObserver_
void updateSwaptionBasketVols() const
const bool continueOnError_
Date swaptionBasketRefDate_
Array swaptionMaturities_
const Real bootstrapTolerance_
std::vector< QuantLib::ext::shared_ptr< SimpleQuote > > swaptionBasketVols_
QuantLib::ext::shared_ptr< QuantExt::IrLgm1fParametrization > parametrization_
bool requiresRecalibration() const override
std::vector< QuantLib::ext::shared_ptr< BlackCalibrationHelper > > swaptionBasket() const
Handle< YieldTermStructure > calibrationDiscountCurve_
std::vector< QuantLib::Real > swaptionVolCache_
std::vector< Real > swaptionStrike_
QuantLib::ext::shared_ptr< OptimizationMethod > optimizationMethod_
QuantLib::ext::shared_ptr< ore::data::Market > market_
Real getStrike(const Size j) const
std::vector< QuantLib::ext::shared_ptr< BlackCalibrationHelper > > swaptionBasket_
void buildSwaptionBasket() const
Handle< SwapIndex > shortSwapIndex_
const std::string referenceCalibrationGrid_
bool requiresCalibration_
Real error() const
Return calibration error.
QuantLib::ext::shared_ptr< IrLgmData > data_
void getExpiryAndTerm(const Size j, Period &expiryPb, Period &termPb, Date &expiryDb, Date &termDb, Real &termT, bool &expiryDateBased, bool &termDateBased) const
QuantLib::ext::shared_ptr< QuantExt::LGM > model() const
QuantLib::ext::shared_ptr< QuantExt::LGM > model_
const bool setCalibrationInfo_
Handle< QuantLib::SwaptionVolatilityStructure > svts_
RelinkableHandle< YieldTermStructure > modelDiscountCurve_
@ 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
Strike parseStrike(const std::string &s)
Convert text to Strike.
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.
Currency parseCurrency(const string &s)
Convert text to QuantLib::Currency.
Map text representations to QuantLib/QuantExt types.
Classes and functions for log message handling.
#define WLOGGERSTREAM(text)
#define LOG(text)
Logging Macro (Level = Notice)
#define DLOG(text)
Logging Macro (Level = Debug)
#define TLOGGERSTREAM(text)
#define WLOG(text)
Logging Macro (Level = Warning)
#define TLOG(text)
Logging Macro (Level = Data)
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)
Size size(const ValueType &v)
std::string getCalibrationDetails(LgmCalibrationInfo &info, const std::vector< QuantLib::ext::shared_ptr< BlackCalibrationHelper > > &basket, const QuantLib::ext::shared_ptr< IrLgm1fParametrization > ¶metrization)
Serializable Credit Default Swap.
Map text representations to QuantLib/QuantExt types.
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
std::vector< SwaptionData > swaptionData
Error for model calibration / building.