From 912c94457f4f57615911a39354ea1f02e85dead5 Mon Sep 17 00:00:00 2001 From: Eugene Toder Date: Tue, 22 Oct 2024 19:18:14 -0400 Subject: [PATCH 1/4] Merge DatedOISRateHelper with OISRateHelper Add a second constructor overload to OISRateHelper which works like DatedOISRateHelper. Deprecate DatedOISRateHelper. This removes a lot of code duplication and also adds support for custom pillar dates to DatedOISRateHelper's equivalent. --- .../MulticurveBootstrapping.cpp | 2 +- ql/instruments/makeois.cpp | 3 +- ql/termstructures/bootstraphelper.hpp | 26 ++-- ql/termstructures/yield/oisratehelper.cpp | 132 +++++++----------- ql/termstructures/yield/oisratehelper.hpp | 113 +++++++-------- test-suite/overnightindexedswap.cpp | 4 +- 6 files changed, 127 insertions(+), 153 deletions(-) diff --git a/Examples/MulticurveBootstrapping/MulticurveBootstrapping.cpp b/Examples/MulticurveBootstrapping/MulticurveBootstrapping.cpp index 1a66a85af17..ad751c14299 100644 --- a/Examples/MulticurveBootstrapping/MulticurveBootstrapping.cpp +++ b/Examples/MulticurveBootstrapping/MulticurveBootstrapping.cpp @@ -153,7 +153,7 @@ int main(int, char* []) { auto startDate = q.first.first; auto endDate = q.first.second; auto quote = q.second; - auto helper = ext::make_shared( + auto helper = ext::make_shared( startDate, endDate, Handle(quote), eonia); eoniaInstruments.push_back(helper); } diff --git a/ql/instruments/makeois.cpp b/ql/instruments/makeois.cpp index 13701526c55..17efeb7f1ab 100644 --- a/ql/instruments/makeois.cpp +++ b/ql/instruments/makeois.cpp @@ -192,7 +192,8 @@ namespace QuantLib { MakeOIS& MakeOIS::withTerminationDate(const Date& terminationDate) { terminationDate_ = terminationDate; - swapTenor_ = Period(); + if (terminationDate != Date()) + swapTenor_ = Period(); return *this; } diff --git a/ql/termstructures/bootstraphelper.hpp b/ql/termstructures/bootstraphelper.hpp index 66b071b42ce..0d3c62315f6 100644 --- a/ql/termstructures/bootstraphelper.hpp +++ b/ql/termstructures/bootstraphelper.hpp @@ -128,12 +128,15 @@ namespace QuantLib { template class RelativeDateBootstrapHelper : public BootstrapHelper { public: - explicit RelativeDateBootstrapHelper(const Handle& quote); - explicit RelativeDateBootstrapHelper(Real quote); + explicit RelativeDateBootstrapHelper(const Handle& quote, + bool updateDates = true); + explicit RelativeDateBootstrapHelper(Real quote, + bool updateDates = true); //! \name Observer interface //@{ void update() override { - if (evaluationDate_ != Settings::instance().evaluationDate()) { + if (evaluationDate_ != Date() && + evaluationDate_ != Settings::instance().evaluationDate()) { evaluationDate_ = Settings::instance().evaluationDate(); initializeDates(); } @@ -212,17 +215,22 @@ namespace QuantLib { template RelativeDateBootstrapHelper::RelativeDateBootstrapHelper( - const Handle& quote) + const Handle& quote, bool updateDates) : BootstrapHelper(quote) { - this->registerWith(Settings::instance().evaluationDate()); - evaluationDate_ = Settings::instance().evaluationDate(); + if (updateDates) { + this->registerWith(Settings::instance().evaluationDate()); + evaluationDate_ = Settings::instance().evaluationDate(); + } } template - RelativeDateBootstrapHelper::RelativeDateBootstrapHelper(Real quote) + RelativeDateBootstrapHelper::RelativeDateBootstrapHelper( + Real quote, bool updateDates) : BootstrapHelper(quote) { - this->registerWith(Settings::instance().evaluationDate()); - evaluationDate_ = Settings::instance().evaluationDate(); + if (updateDates) { + this->registerWith(Settings::instance().evaluationDate()); + evaluationDate_ = Settings::instance().evaluationDate(); + } } inline std::ostream& operator<<(std::ostream& out, diff --git a/ql/termstructures/yield/oisratehelper.cpp b/ql/termstructures/yield/oisratehelper.cpp index ce211d3eed4..39a9575382f 100644 --- a/ql/termstructures/yield/oisratehelper.cpp +++ b/ql/termstructures/yield/oisratehelper.cpp @@ -50,16 +50,53 @@ namespace QuantLib { Natural lockoutDays, bool applyObservationShift, ext::shared_ptr pricer) - : RelativeDateRateHelper(fixedRate), pillarChoice_(pillar), settlementDays_(settlementDays), tenor_(tenor), + : RelativeDateRateHelper(fixedRate), settlementDays_(settlementDays), tenor_(tenor), discountHandle_(std::move(discount)), telescopicValueDates_(telescopicValueDates), paymentLag_(paymentLag), paymentConvention_(paymentConvention), paymentFrequency_(paymentFrequency), paymentCalendar_(std::move(paymentCalendar)), - forwardStart_(forwardStart), overnightSpread_(overnightSpread), + forwardStart_(forwardStart), overnightSpread_(overnightSpread), pillarChoice_(pillar), averagingMethod_(averagingMethod), endOfMonth_(endOfMonth), fixedPaymentFrequency_(fixedPaymentFrequency), fixedCalendar_(std::move(fixedCalendar)), lookbackDays_(lookbackDays), lockoutDays_(lockoutDays), applyObservationShift_(applyObservationShift), pricer_(std::move(pricer)) { + initialize(overnightIndex, customPillarDate); + } + OISRateHelper::OISRateHelper(const Date& startDate, + const Date& endDate, + const Handle& fixedRate, + const ext::shared_ptr& overnightIndex, + Handle discount, + bool telescopicValueDates, + Integer paymentLag, + BusinessDayConvention paymentConvention, + Frequency paymentFrequency, + Calendar paymentCalendar, + const Spread overnightSpread, + Pillar::Choice pillar, + Date customPillarDate, + RateAveraging::Type averagingMethod, + ext::optional endOfMonth, + ext::optional fixedPaymentFrequency, + Calendar fixedCalendar, + Natural lookbackDays, + Natural lockoutDays, + bool applyObservationShift, + ext::shared_ptr pricer) + : RelativeDateRateHelper(fixedRate, false), startDate_(startDate), endDate_(endDate), + discountHandle_(std::move(discount)), telescopicValueDates_(telescopicValueDates), + paymentLag_(paymentLag), paymentConvention_(paymentConvention), + paymentFrequency_(paymentFrequency), paymentCalendar_(std::move(paymentCalendar)), + overnightSpread_(overnightSpread), pillarChoice_(pillar), + averagingMethod_(averagingMethod), endOfMonth_(endOfMonth), + fixedPaymentFrequency_(fixedPaymentFrequency), fixedCalendar_(std::move(fixedCalendar)), + lookbackDays_(lookbackDays), lockoutDays_(lockoutDays), applyObservationShift_(applyObservationShift), + pricer_(std::move(pricer)) { + initialize(overnightIndex, customPillarDate); + } + + void OISRateHelper::initialize(const ext::shared_ptr& overnightIndex, + Date customPillarDate) { overnightIndex_ = ext::dynamic_pointer_cast(overnightIndex->clone(termStructureHandle_)); // We want to be notified of changes of fixings, but we don't @@ -75,12 +112,13 @@ namespace QuantLib { } void OISRateHelper::initializeDates() { - // input discount curve Handle might be empty now but it could // be assigned a curve later; use a RelinkableHandle here - MakeOIS tmp = MakeOIS(tenor_, overnightIndex_, 0.0, forwardStart_) + auto tmp = MakeOIS(tenor_, overnightIndex_, 0.0, forwardStart_) .withDiscountingTermStructure(discountRelinkableHandle_) - .withSettlementDays(settlementDays_) + .withSettlementDays(settlementDays_) // resets effectiveDate + .withEffectiveDate(startDate_) + .withTerminationDate(endDate_) .withTelescopicValueDates(telescopicValueDates_) .withPaymentLag(paymentLag_) .withPaymentAdjustment(paymentConvention_) @@ -187,55 +225,10 @@ namespace QuantLib { Natural lockoutDays, bool applyObservationShift, const ext::shared_ptr& pricer) - : RateHelper(fixedRate), discountHandle_(std::move(discount)), - telescopicValueDates_(telescopicValueDates), averagingMethod_(averagingMethod) { - - auto clonedOvernightIndex = - ext::dynamic_pointer_cast(overnightIndex->clone(termStructureHandle_)); - // We want to be notified of changes of fixings, but we don't - // want notifications from termStructureHandle_ (they would - // interfere with bootstrapping.) - clonedOvernightIndex->unregisterWith(termStructureHandle_); - - registerWith(clonedOvernightIndex); - registerWith(discountHandle_); - - // input discount curve Handle might be empty now but it could - // be assigned a curve later; use a RelinkableHandle here - auto tmp = MakeOIS(Period(), clonedOvernightIndex, 0.0) - .withDiscountingTermStructure(discountRelinkableHandle_) - .withEffectiveDate(startDate) - .withTerminationDate(endDate) - .withTelescopicValueDates(telescopicValueDates_) - .withPaymentLag(paymentLag) - .withPaymentAdjustment(paymentConvention) - .withPaymentFrequency(paymentFrequency) - .withPaymentCalendar(paymentCalendar) - .withOvernightLegSpread(overnightSpread) - .withAveragingMethod(averagingMethod_) - .withLookbackDays(lookbackDays) - .withLockoutDays(lockoutDays) - .withObservationShift(applyObservationShift); - if (endOfMonth) { - tmp.withEndOfMonth(*endOfMonth); - } - if (fixedPaymentFrequency) { - tmp.withFixedLegPaymentFrequency(*fixedPaymentFrequency); - } - if (!fixedCalendar.empty()) { - tmp.withFixedLegCalendar(fixedCalendar); - } - swap_ = tmp; - - if (pricer) - setCouponPricer(swap_->overnightLeg(), pricer); - - earliestDate_ = swap_->startDate(); - maturityDate_ = swap_->maturityDate(); - Date lastPaymentDate = std::max(swap_->overnightLeg().back()->date(), - swap_->fixedLeg().back()->date()); - latestRelevantDate_ = latestDate_ = std::max(maturityDate_, lastPaymentDate); - } + : OISRateHelper(startDate, endDate, fixedRate, overnightIndex, std::move(discount), telescopicValueDates, + paymentLag, paymentConvention, paymentFrequency, paymentCalendar, overnightSpread, + Pillar::LastRelevantDate, Date(), averagingMethod, endOfMonth, fixedPaymentFrequency, + fixedCalendar, lookbackDays, lockoutDays, applyObservationShift, pricer) {} DatedOISRateHelper::DatedOISRateHelper(const Date& startDate, const Date& endDate, @@ -257,35 +250,4 @@ namespace QuantLib { averagingMethod, paymentLag, paymentConvention, paymentFrequency, paymentCalendar, overnightSpread, endOfMonth, fixedPaymentFrequency, fixedCalendar) {} - void DatedOISRateHelper::setTermStructure(YieldTermStructure* t) { - // do not set the relinkable handle as an observer - - // force recalculation when needed - bool observer = false; - - ext::shared_ptr temp(t, null_deleter()); - termStructureHandle_.linkTo(temp, observer); - - if (discountHandle_.empty()) - discountRelinkableHandle_.linkTo(temp, observer); - else - discountRelinkableHandle_.linkTo(*discountHandle_, observer); - - RateHelper::setTermStructure(t); - } - - Real DatedOISRateHelper::impliedQuote() const { - QL_REQUIRE(termStructure_ != nullptr, "term structure not set"); - // we didn't register as observers - force calculation - swap_->deepUpdate(); - return swap_->fairRate(); - } - - void DatedOISRateHelper::accept(AcyclicVisitor& v) { - auto* v1 = dynamic_cast*>(&v); - if (v1 != nullptr) - v1->visit(*this); - else - RateHelper::accept(v); - } - } diff --git a/ql/termstructures/yield/oisratehelper.hpp b/ql/termstructures/yield/oisratehelper.hpp index 9135c479ee9..0b1bdb198b6 100644 --- a/ql/termstructures/yield/oisratehelper.hpp +++ b/ql/termstructures/yield/oisratehelper.hpp @@ -59,6 +59,28 @@ namespace QuantLib { Natural lockoutDays = 0, bool applyObservationShift = false, ext::shared_ptr pricer = {}); + OISRateHelper(const Date& startDate, + const Date& endDate, + const Handle& fixedRate, + const ext::shared_ptr& overnightIndex, + // exogenous discounting curve + Handle discountingCurve = {}, + bool telescopicValueDates = false, + Integer paymentLag = 0, + BusinessDayConvention paymentConvention = Following, + Frequency paymentFrequency = Annual, + Calendar paymentCalendar = Calendar(), + Spread overnightSpread = 0.0, + Pillar::Choice pillar = Pillar::LastRelevantDate, + Date customPillarDate = Date(), + RateAveraging::Type averagingMethod = RateAveraging::Compound, + ext::optional endOfMonth = ext::nullopt, + ext::optional fixedPaymentFrequency = ext::nullopt, + Calendar fixedCalendar = Calendar(), + Natural lookbackDays = Null(), + Natural lockoutDays = 0, + bool applyObservationShift = false, + ext::shared_ptr pricer = {}); //! \name RateHelper interface //@{ Real impliedQuote() const override; @@ -73,39 +95,44 @@ namespace QuantLib { //@{ void accept(AcyclicVisitor&) override; //@} - protected: - void initializeDates() override; - Pillar::Choice pillarChoice_; - - Natural settlementDays_; - Period tenor_; - ext::shared_ptr overnightIndex_; - - ext::shared_ptr swap_; - RelinkableHandle termStructureHandle_; - - Handle discountHandle_; - bool telescopicValueDates_; - RelinkableHandle discountRelinkableHandle_; - - Integer paymentLag_; - BusinessDayConvention paymentConvention_; - Frequency paymentFrequency_; - Calendar paymentCalendar_; - Period forwardStart_; - Spread overnightSpread_; - RateAveraging::Type averagingMethod_; - ext::optional endOfMonth_; - ext::optional fixedPaymentFrequency_; - Calendar fixedCalendar_; - Natural lookbackDays_; - Natural lockoutDays_; - bool applyObservationShift_; - ext::shared_ptr pricer_; + protected: + void initialize(const ext::shared_ptr& overnightIndex, + Date customPillarDate); + void initializeDates() override; + + Natural settlementDays_; + Period tenor_; + Date startDate_, endDate_; + ext::shared_ptr overnightIndex_; + + ext::shared_ptr swap_; + RelinkableHandle termStructureHandle_; + + Handle discountHandle_; + bool telescopicValueDates_; + RelinkableHandle discountRelinkableHandle_; + + Integer paymentLag_; + BusinessDayConvention paymentConvention_; + Frequency paymentFrequency_; + Calendar paymentCalendar_; + Period forwardStart_; + Spread overnightSpread_; + Pillar::Choice pillarChoice_; + RateAveraging::Type averagingMethod_; + ext::optional endOfMonth_; + ext::optional fixedPaymentFrequency_; + Calendar fixedCalendar_; + Natural lookbackDays_; + Natural lockoutDays_; + bool applyObservationShift_; + ext::shared_ptr pricer_; }; - //! Rate helper for bootstrapping over Overnight Indexed Swap rates - class DatedOISRateHelper : public RateHelper { + /*! \deprecated Use OISRateHelper instead. + Deprecated in version 1.37. + */ + class QL_DEPRECATED DatedOISRateHelper : public OISRateHelper { public: DatedOISRateHelper(const Date& startDate, const Date& endDate, @@ -149,30 +176,6 @@ namespace QuantLib { ext::optional endOfMonth = ext::nullopt, ext::optional fixedPaymentFrequency = ext::nullopt, const Calendar& fixedCalendar = Calendar()); - - //! \name RateHelper interface - //@{ - Real impliedQuote() const override; - void setTermStructure(YieldTermStructure*) override; - //@} - //! \name inspectors - //@{ - // NOLINTNEXTLINE(cppcoreguidelines-noexcept-swap,performance-noexcept-swap) - ext::shared_ptr swap() const { return swap_; } - //@} - //@} - //! \name Visitability - //@{ - void accept(AcyclicVisitor&) override; - //@} - protected: - ext::shared_ptr swap_; - RelinkableHandle termStructureHandle_; - - Handle discountHandle_; - bool telescopicValueDates_; - RelinkableHandle discountRelinkableHandle_; - RateAveraging::Type averagingMethod_; }; } diff --git a/test-suite/overnightindexedswap.cpp b/test-suite/overnightindexedswap.cpp index 96f9cff753a..83a77b73935 100644 --- a/test-suite/overnightindexedswap.cpp +++ b/test-suite/overnightindexedswap.cpp @@ -709,8 +709,8 @@ BOOST_AUTO_TEST_CASE(test131BootstrapRegression) { auto estr = ext::make_shared(); std::vector> helpers; - helpers.push_back(ext::make_shared(2, 1 * Weeks, Handle(ext::make_shared(0.070/100)), estr)); - helpers.push_back(ext::make_shared(Date(16, January, 2013), Date(13, February, 2013), Handle(ext::make_shared(0.046/100)), estr)); + helpers.push_back(ext::make_shared(2, 1 * Weeks, makeQuoteHandle(0.070/100), estr)); + helpers.push_back(ext::make_shared(Date(16, January, 2013), Date(13, February, 2013), makeQuoteHandle(0.046/100), estr)); auto curve = PiecewiseYieldCurve(0, TARGET(), helpers, Actual365Fixed()); BOOST_CHECK_NO_THROW(curve.nodes()); From db71299d7985f4f422f186023540843194600b37 Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Fri, 25 Oct 2024 08:54:53 +0200 Subject: [PATCH 2/4] Use explicit bool, not implicit null date --- ql/termstructures/bootstraphelper.hpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/ql/termstructures/bootstraphelper.hpp b/ql/termstructures/bootstraphelper.hpp index 0d3c62315f6..54809b0d569 100644 --- a/ql/termstructures/bootstraphelper.hpp +++ b/ql/termstructures/bootstraphelper.hpp @@ -38,8 +38,7 @@ namespace QuantLib { struct Pillar { - //! Enumeration for pillar determination alternatives - /*! These alternatives specify the determination of the pillar date. */ + //! Alternatives ways of determining the pillar date enum Choice { MaturityDate, //! instruments maturity date LastRelevantDate, //! last date relevant for instrument pricing @@ -135,8 +134,7 @@ namespace QuantLib { //! \name Observer interface //@{ void update() override { - if (evaluationDate_ != Date() && - evaluationDate_ != Settings::instance().evaluationDate()) { + if (updateDates_ && evaluationDate_ != Settings::instance().evaluationDate()) { evaluationDate_ = Settings::instance().evaluationDate(); initializeDates(); } @@ -146,6 +144,8 @@ namespace QuantLib { protected: virtual void initializeDates() = 0; Date evaluationDate_; + private: + bool updateDates_; }; // template definitions @@ -213,10 +213,11 @@ namespace QuantLib { QL_FAIL("not a bootstrap-helper visitor"); } + template RelativeDateBootstrapHelper::RelativeDateBootstrapHelper( const Handle& quote, bool updateDates) - : BootstrapHelper(quote) { + : BootstrapHelper(quote), updateDates_(updateDates) { if (updateDates) { this->registerWith(Settings::instance().evaluationDate()); evaluationDate_ = Settings::instance().evaluationDate(); @@ -226,12 +227,8 @@ namespace QuantLib { template RelativeDateBootstrapHelper::RelativeDateBootstrapHelper( Real quote, bool updateDates) - : BootstrapHelper(quote) { - if (updateDates) { - this->registerWith(Settings::instance().evaluationDate()); - evaluationDate_ = Settings::instance().evaluationDate(); - } - } + : RelativeDateBootstrapHelper(makeQuoteHandle(quote), updateDates) {} + inline std::ostream& operator<<(std::ostream& out, Pillar::Choice t) { From fcc3a1247ae0ff289bddafa3102d3a219dfbd04d Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Fri, 25 Oct 2024 09:08:54 +0200 Subject: [PATCH 3/4] Avoid obsolete macro --- ql/termstructures/yield/oisratehelper.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ql/termstructures/yield/oisratehelper.hpp b/ql/termstructures/yield/oisratehelper.hpp index 0b1bdb198b6..a65ff3ae532 100644 --- a/ql/termstructures/yield/oisratehelper.hpp +++ b/ql/termstructures/yield/oisratehelper.hpp @@ -132,7 +132,7 @@ namespace QuantLib { /*! \deprecated Use OISRateHelper instead. Deprecated in version 1.37. */ - class QL_DEPRECATED DatedOISRateHelper : public OISRateHelper { + class [[deprecated("Use OISRateHelper instead")]] DatedOISRateHelper : public OISRateHelper { public: DatedOISRateHelper(const Date& startDate, const Date& endDate, @@ -158,7 +158,7 @@ namespace QuantLib { /*! \deprecated Use the overload without forward start. Deprecated in version 1.35. */ - QL_DEPRECATED + [[deprecated("Use the overload without forward start")]] DatedOISRateHelper(const Date& startDate, const Date& endDate, const Handle& fixedRate, From 458f24b605fb7e8f227691a5b4740072c8d7a7fe Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Fri, 25 Oct 2024 09:57:51 +0200 Subject: [PATCH 4/4] Keep a test for the deprecated class while it's still there --- test-suite/overnightindexedswap.cpp | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test-suite/overnightindexedswap.cpp b/test-suite/overnightindexedswap.cpp index 83a77b73935..32ea199f39b 100644 --- a/test-suite/overnightindexedswap.cpp +++ b/test-suite/overnightindexedswap.cpp @@ -716,6 +716,36 @@ BOOST_AUTO_TEST_CASE(test131BootstrapRegression) { BOOST_CHECK_NO_THROW(curve.nodes()); } +BOOST_AUTO_TEST_CASE(testDeprecatedHelper) { + BOOST_TEST_MESSAGE("Testing deprecated DatedOISRateHelper class..."); + + Date today(11, December, 2012); + Settings::instance().evaluationDate() = today; + + auto estr = ext::make_shared(); + + std::vector> helpers; + helpers.push_back(ext::make_shared(2, 1 * Weeks, makeQuoteHandle(0.070/100), estr)); + QL_DEPRECATED_DISABLE_WARNING + helpers.push_back(ext::make_shared(Date(16, January, 2013), Date(13, February, 2013), makeQuoteHandle(0.046/100), estr)); + QL_DEPRECATED_ENABLE_WARNING + + auto curve = ext::make_shared>(0, TARGET(), helpers, Actual365Fixed()); + BOOST_CHECK_NO_THROW(curve->nodes()); + + estr = ext::make_shared(Handle(curve)); + ext::shared_ptr swap = + MakeOIS(Period(), estr, 0.046/100, 0 * Days) + .withEffectiveDate(Date(16, January, 2013)) + .withTerminationDate(Date(13, February, 2013)) + .withDiscountingTermStructure(Handle(curve)); + + if (std::fabs(swap->NPV()) > 1.0e-10) { + BOOST_ERROR("npv is not at par:\n" + << " swap value: " << swap->NPV()); + } +} + BOOST_AUTO_TEST_CASE(testConstructorsAndNominals) { BOOST_TEST_MESSAGE("Testing different constructors for OIS...");