Skip to content

Commit 3283310

Browse files
pcaspersjenkins
authored andcommitted
QPR-12201 use swap index discount curve to build calibrating swaptions
1 parent d3c98a7 commit 3283310

2 files changed

Lines changed: 48 additions & 45 deletions

File tree

OREData/ored/model/lgmbuilder.cpp

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232
#include <ored/model/structuredmodelerror.hpp>
3333
#include <ored/model/utilities.hpp>
3434
#include <ored/utilities/dategrid.hpp>
35+
#include <ored/utilities/indexparser.hpp>
3536
#include <ored/utilities/log.hpp>
3637
#include <ored/utilities/parsers.hpp>
37-
#include <ored/utilities/indexparser.hpp>
3838
#include <ored/utilities/strike.hpp>
3939

4040
using namespace QuantLib;
@@ -177,31 +177,30 @@ LgmBuilder::LgmBuilder(const boost::shared_ptr<ore::data::Market>& market, const
177177
string qualifier = data_->qualifier();
178178
currency_ = qualifier;
179179
boost::shared_ptr<IborIndex> index;
180-
if(tryParseIborIndex(qualifier,index)) {
181-
currency_ = index->currency().code();
180+
if (tryParseIborIndex(qualifier, index)) {
181+
currency_ = index->currency().code();
182182
}
183-
LOG("LgmCalibration for qualifier " << qualifier << " (ccy=" << currency_ << "), configuration is " << configuration_);
183+
LOG("LgmCalibration for qualifier " << qualifier << " (ccy=" << currency_ << "), configuration is "
184+
<< configuration_);
184185
Currency ccy = parseCurrency(currency_);
185186

186187
requiresCalibration_ =
187188
(data_->calibrateA() || data_->calibrateH()) && data_->calibrationType() != CalibrationType::None;
188189

189-
// the discount curve underlying the model might be relinked to a different curve outside this builder
190-
// the calibration curve should always stay the same though, therefore we create a different handle for this
191-
modelDiscountCurve_ = RelinkableHandle<YieldTermStructure>(*market_->discountCurve(currency_, configuration_));
192-
calibrationDiscountCurve_ = Handle<YieldTermStructure>(*modelDiscountCurve_);
190+
swapIndex_ = market_->swapIndex(market_->swapIndexBase(data_->qualifier(), configuration_), configuration_);
191+
// see the comment for dinscountCurve() in the interface
192+
modelDiscountCurve_ = RelinkableHandle<YieldTermStructure>(*swapIndex_->discountingTermStructure());
193193

194194
if (requiresCalibration_) {
195195
svts_ = market_->swaptionVol(data_->qualifier(), configuration_);
196-
swapIndex_ = market_->swapIndex(market_->swapIndexBase(data_->qualifier(), configuration_), configuration_);
197-
shortSwapIndex_ = market_->swapIndex(market_->shortSwapIndexBase(data_->qualifier(), configuration_), configuration_);
196+
shortSwapIndex_ =
197+
market_->swapIndex(market_->shortSwapIndexBase(data_->qualifier(), configuration_), configuration_);
198198
registerWith(svts_);
199199
marketObserver_->addObservable(swapIndex_->forwardingTermStructure());
200-
marketObserver_->addObservable(swapIndex_->discountingTermStructure());
201200
marketObserver_->addObservable(shortSwapIndex_->forwardingTermStructure());
202201
marketObserver_->addObservable(shortSwapIndex_->discountingTermStructure());
203202
}
204-
marketObserver_->addObservable(calibrationDiscountCurve_);
203+
marketObserver_->addObservable(swapIndex_->discountingTermStructure());
205204
registerWith(marketObserver_);
206205
// notify observers of all market data changes, not only when not calculated
207206
alwaysForwardNotifications();
@@ -321,7 +320,7 @@ void LgmBuilder::performCalculations() const {
321320
// reset lgm observer's updated flag
322321
marketObserver_->hasUpdated(true);
323322

324-
if (swaptionBasketRefDate_ != calibrationDiscountCurve_->referenceDate()) {
323+
if (swaptionBasketRefDate_ != swapIndex_->discountingTermStructure()->referenceDate()) {
325324
// build swaption basket if required, i.e. if reference date has changed since last build
326325
buildSwaptionBasket();
327326
volSurfaceChanged(true);
@@ -333,7 +332,8 @@ void LgmBuilder::performCalculations() const {
333332
}
334333

335334
for (Size j = 0; j < swaptionBasket_.size(); j++) {
336-
auto engine = boost::make_shared<QuantExt::AnalyticLgmSwaptionEngine>(model_, calibrationDiscountCurve_);
335+
auto engine =
336+
boost::make_shared<QuantExt::AnalyticLgmSwaptionEngine>(model_, swapIndex_->discountingTermStructure());
337337
engine->enableCache(!data_->calibrateH(), !data_->calibrateA());
338338
swaptionBasket_[j]->setPricingEngine(engine);
339339
// necessary if notifications are disabled (observation mode = Disable)
@@ -387,15 +387,15 @@ void LgmBuilder::performCalculations() const {
387387
if (Log::instance().filter(ORE_DATA) || setCalibrationInfo_) {
388388
TLOGGERSTREAM("Basket details:");
389389
try {
390-
auto d = getBasketDetails(calibrationInfo);
390+
auto d = getBasketDetails(calibrationInfo);
391391
TLOGGERSTREAM(d);
392392
} catch (const std::exception& e) {
393393
WLOG("An error occurred: " << e.what());
394394
}
395395
TLOGGERSTREAM("Calibration details (with time grid = calibration swaption expiries):");
396396
try {
397-
auto d = getCalibrationDetails(calibrationInfo, swaptionBasket_, parametrization_);
398-
TLOGGERSTREAM(d);
397+
auto d = getCalibrationDetails(calibrationInfo, swaptionBasket_, parametrization_);
398+
TLOGGERSTREAM(d);
399399
} catch (const std::exception& e) {
400400
WLOG("An error occurred: " << e.what());
401401
}
@@ -410,14 +410,14 @@ void LgmBuilder::performCalculations() const {
410410
StructuredModelErrorMessage(errorTemplate, exceptionMessage, id_).log();
411411
WLOGGERSTREAM("Basket details:");
412412
try {
413-
auto d = getBasketDetails(calibrationInfo);
413+
auto d = getBasketDetails(calibrationInfo);
414414
WLOGGERSTREAM(d);
415415
} catch (const std::exception& e) {
416416
WLOG("An error occurred: " << e.what());
417417
}
418418
WLOGGERSTREAM("Calibration details (with time grid = calibration swaption expiries):");
419419
try {
420-
auto d = getCalibrationDetails(calibrationInfo, swaptionBasket_, parametrization_);
420+
auto d = getCalibrationDetails(calibrationInfo, swaptionBasket_, parametrization_);
421421
WLOGGERSTREAM(d);
422422
} catch (const std::exception& e) {
423423
WLOG("An error occurred: " << e.what());
@@ -548,8 +548,6 @@ void LgmBuilder::buildSwaptionBasket() const {
548548

549549
std::ostringstream log;
550550

551-
Handle<YieldTermStructure> yts = market_->discountCurve(currency_, configuration_);
552-
553551
std::vector<Time> expiryTimes;
554552
std::vector<Time> maturityTimes;
555553
swaptionBasket_.clear();
@@ -586,7 +584,7 @@ void LgmBuilder::buildSwaptionBasket() const {
586584
RateAveraging::Type averagingMethod = RateAveraging::Compound;
587585
if (auto on = dynamic_pointer_cast<OvernightIndexedSwapIndex>(*swapIndex_)) {
588586
settlementDays = on->fixingDays();
589-
averagingMethod = on->averagingMethod();
587+
averagingMethod = on->averagingMethod();
590588
}
591589

592590
Real dummyQuote = svts_->volatilityType() == Normal ? 0.0020 : 0.10;
@@ -597,28 +595,32 @@ void LgmBuilder::buildSwaptionBasket() const {
597595

598596
if (expiryDateBased && termDateBased) {
599597
Real shift = svts_->volatilityType() == ShiftedLognormal ? svts_->shift(expiryDb, termT) : 0.0;
600-
std::tie(helper, updatedStrike) = createSwaptionHelper(
601-
expiryDb, termDb, svts_, vol, iborIndex, fixedLegTenor, fixedDayCounter, floatDayCounter, yts,
602-
calibrationErrorType_, strikeValue, shift, settlementDays, averagingMethod);
598+
std::tie(helper, updatedStrike) =
599+
createSwaptionHelper(expiryDb, termDb, svts_, vol, iborIndex, fixedLegTenor, fixedDayCounter,
600+
floatDayCounter, swapIndex_->discountingTermStructure(), calibrationErrorType_,
601+
strikeValue, shift, settlementDays, averagingMethod);
603602
}
604603
if (expiryDateBased && !termDateBased) {
605604
Real shift = svts_->volatilityType() == ShiftedLognormal ? svts_->shift(expiryDb, termPb) : 0.0;
606-
std::tie(helper, updatedStrike) = createSwaptionHelper(
607-
expiryDb, termPb, svts_, vol, iborIndex, fixedLegTenor, fixedDayCounter, floatDayCounter, yts,
608-
calibrationErrorType_, strikeValue, shift, settlementDays, averagingMethod);
605+
std::tie(helper, updatedStrike) =
606+
createSwaptionHelper(expiryDb, termPb, svts_, vol, iborIndex, fixedLegTenor, fixedDayCounter,
607+
floatDayCounter, swapIndex_->discountingTermStructure(), calibrationErrorType_,
608+
strikeValue, shift, settlementDays, averagingMethod);
609609
}
610610
if (!expiryDateBased && termDateBased) {
611611
Date expiry = svts_->optionDateFromTenor(expiryPb);
612612
Real shift = svts_->volatilityType() == ShiftedLognormal ? svts_->shift(expiryPb, termT) : 0.0;
613-
std::tie(helper, updatedStrike) = createSwaptionHelper(
614-
expiry, termDb, svts_, vol, iborIndex, fixedLegTenor, fixedDayCounter, floatDayCounter, yts,
615-
calibrationErrorType_, strikeValue, shift, settlementDays, averagingMethod);
613+
std::tie(helper, updatedStrike) =
614+
createSwaptionHelper(expiry, termDb, svts_, vol, iborIndex, fixedLegTenor, fixedDayCounter,
615+
floatDayCounter, swapIndex_->discountingTermStructure(), calibrationErrorType_,
616+
strikeValue, shift, settlementDays, averagingMethod);
616617
}
617618
if (!expiryDateBased && !termDateBased) {
618619
Real shift = svts_->volatilityType() == ShiftedLognormal ? svts_->shift(expiryPb, termPb) : 0.0;
619-
std::tie(helper, updatedStrike) = createSwaptionHelper(
620-
expiryPb, termPb, svts_, vol, iborIndex, fixedLegTenor, fixedDayCounter, floatDayCounter, yts,
621-
calibrationErrorType_, strikeValue, shift, settlementDays, averagingMethod);
620+
std::tie(helper, updatedStrike) =
621+
createSwaptionHelper(expiryPb, termPb, svts_, vol, iborIndex, fixedLegTenor, fixedDayCounter,
622+
floatDayCounter, swapIndex_->discountingTermStructure(), calibrationErrorType_,
623+
strikeValue, shift, settlementDays, averagingMethod);
622624
}
623625

624626
// check if we want to keep the helper when a reference calibration grid is given
@@ -630,10 +632,10 @@ void LgmBuilder::buildSwaptionBasket() const {
630632
swaptionBasketVols_.push_back(volQuote);
631633
swaptionBasket_.push_back(helper);
632634
swaptionStrike_.push_back(updatedStrike);
633-
expiryTimes.push_back(yts->timeFromReference(expiryDate));
635+
expiryTimes.push_back(swapIndex_->discountingTermStructure()->timeFromReference(expiryDate));
634636
Date matDate = helper->underlyingSwap() ? helper->underlyingSwap()->maturityDate()
635637
: helper->underlyingOvernightIndexedSwap()->maturityDate();
636-
maturityTimes.push_back(yts->timeFromReference(matDate));
638+
maturityTimes.push_back(swapIndex_->discountingTermStructure()->timeFromReference(matDate));
637639
if (refCalDate != referenceCalibrationDates.end())
638640
lastRefCalDate = *refCalDate;
639641
}
@@ -655,19 +657,18 @@ void LgmBuilder::buildSwaptionBasket() const {
655657
for (Size j = 0; j < maturityTimes.size(); j++)
656658
swaptionMaturities_[j] = maturityTimes[j];
657659

658-
swaptionBasketRefDate_ = calibrationDiscountCurve_->referenceDate();
660+
swaptionBasketRefDate_ = swapIndex_->discountingTermStructure()->referenceDate();
659661
}
660662

661663
std::string LgmBuilder::getBasketDetails(LgmCalibrationInfo& info) const {
662664
std::ostringstream log;
663-
Handle<YieldTermStructure> yts = market_->discountCurve(currency_, configuration_);
664665
log << std::right << std::setw(3) << "#" << std::setw(16) << "expiry" << std::setw(16) << "swapLength"
665666
<< std::setw(16) << "strike" << std::setw(16) << "atmForward" << std::setw(16) << "annuity" << std::setw(16)
666667
<< "vega" << std::setw(16) << "vol\n";
667668
info.swaptionData.clear();
668669
for (Size j = 0; j < swaptionBasket_.size(); ++j) {
669670
auto swp = boost::static_pointer_cast<SwaptionHelper>(swaptionBasket_[j])->swaption();
670-
auto sd = swaptionData(swp, yts, svts_);
671+
auto sd = swaptionData(swp, swapIndex_->discountingTermStructure(), svts_);
671672
log << std::right << std::setw(3) << j << std::setw(16) << sd.timeToExpiry << std::setw(16) << sd.swapLength
672673
<< std::setw(16) << sd.strike << std::setw(16) << sd.atmForward << std::setw(16) << sd.annuity
673674
<< std::setw(16) << sd.vega << std::setw(16) << std::setw(16) << sd.stdDev / std::sqrt(sd.timeToExpiry)

OREData/ored/model/lgmbuilder.hpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ using namespace QuantLib;
4747
*/
4848
class LgmBuilder : public QuantExt::ModelBuilder {
4949
public:
50-
/*! The configuration should refer to the calibration configuration here,
51-
alternative discounting curves are then usually set in the pricing
52-
engines for swaptions etc. */
50+
/*! The configuration refers to the configuration to read swaption vol and swap index from the market.
51+
The discounting curve to price calibrating swaptions is derived from the swap index directly though,
52+
i.e. it is not read as a discount curve from the market. */
5353
LgmBuilder(const boost::shared_ptr<ore::data::Market>& market, const boost::shared_ptr<IrLgmData>& data,
5454
const std::string& configuration = Market::defaultConfiguration, Real bootstrapTolerance = 0.001,
5555
const bool continueOnError = false, const std::string& referenceCalibrationGrid = "",
@@ -62,8 +62,11 @@ class LgmBuilder : public QuantExt::ModelBuilder {
6262
std::string qualifier() { return data_->qualifier(); }
6363
std::string ccy() { return currency_; }
6464
boost::shared_ptr<QuantExt::LGM> model() const;
65-
boost::shared_ptr<QuantExt::IrLgm1fParametrization> parametrization() const;
65+
/* the curve used to build the lgm parametrization, this can be relinked later outside this builder to e.g.
66+
calibrate fx processes using an xccy curve instead of the in currency curve that we use for the calibration of
67+
the LGM model in this builder */
6668
RelinkableHandle<YieldTermStructure> discountCurve() { return modelDiscountCurve_; }
69+
boost::shared_ptr<QuantExt::IrLgm1fParametrization> parametrization() const;
6770
std::vector<boost::shared_ptr<BlackCalibrationHelper>> swaptionBasket() const;
6871
//@}
6972

@@ -111,10 +114,9 @@ class LgmBuilder : public QuantExt::ModelBuilder {
111114
mutable Array swaptionMaturities_;
112115
mutable Date swaptionBasketRefDate_;
113116

114-
RelinkableHandle<YieldTermStructure> modelDiscountCurve_;
115-
Handle<YieldTermStructure> calibrationDiscountCurve_;
116117
Handle<QuantLib::SwaptionVolatilityStructure> svts_;
117118
Handle<SwapIndex> swapIndex_, shortSwapIndex_;
119+
RelinkableHandle<YieldTermStructure> modelDiscountCurve_;
118120

119121
// TODO: Move CalibrationErrorType, optimizer and end criteria parameters to data
120122
boost::shared_ptr<OptimizationMethod> optimizationMethod_;

0 commit comments

Comments
 (0)