From b2654b6823bf7c666b5c54c32ec1e34d812a3ae5 Mon Sep 17 00:00:00 2001 From: Eugene Toder Date: Tue, 22 Oct 2024 19:18:14 -0400 Subject: [PATCH] Support custom pillar dates in DatedOISRateHelper Also, factor out common code into a helper base class to avoid adding more copy-paste. --- ql/termstructures/yield/oisratehelper.cpp | 179 ++++++++-------------- ql/termstructures/yield/oisratehelper.hpp | 106 ++++++++----- 2 files changed, 133 insertions(+), 152 deletions(-) diff --git a/ql/termstructures/yield/oisratehelper.cpp b/ql/termstructures/yield/oisratehelper.cpp index ce211d3eed..abba49a2f4 100644 --- a/ql/termstructures/yield/oisratehelper.cpp +++ b/ql/termstructures/yield/oisratehelper.cpp @@ -23,11 +23,64 @@ #include #include #include -#include #include namespace QuantLib { + namespace detail { + template + OISRateHelperBase::OISRateHelperBase( + const Handle& fixedRate, + const ext::shared_ptr& overnightIndex, + Handle discountingCurve, + Pillar::Choice pillar, + Date customPillarDate) + : Base(fixedRate), discountHandle_(std::move(discountingCurve)), pillarChoice_(pillar) { + overnightIndex_ = 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.) + overnightIndex_->unregisterWith(termStructureHandle_); + + this->registerWith(overnightIndex_); + this->registerWith(discountHandle_); + + pillarDate_ = customPillarDate; + } + + template + void OISRateHelperBase::setPillarDate() { + earliestDate_ = swap_->startDate(); + maturityDate_ = swap_->maturityDate(); + Date lastPaymentDate = std::max(swap_->overnightLeg().back()->date(), + swap_->fixedLeg().back()->date()); + latestRelevantDate_ = latestDate_ = std::max(maturityDate_, lastPaymentDate); + + switch (pillarChoice_) { + case Pillar::MaturityDate: + pillarDate_ = maturityDate_; + break; + case Pillar::LastRelevantDate: + pillarDate_ = latestRelevantDate_; + break; + case Pillar::CustomDate: + // pillarDate_ already assigned at construction time + QL_REQUIRE(pillarDate_ >= earliestDate_, + "pillar date (" << pillarDate_ << ") must be later " + "than or equal to the instrument's earliest date (" << + earliestDate_ << ")"); + QL_REQUIRE(pillarDate_ <= latestRelevantDate_, + "pillar date (" << pillarDate_ << ") must be before " + "or equal to the instrument's latest relevant date (" << + latestRelevantDate_ << ")"); + break; + default: + QL_FAIL("unknown Pillar::Choice(" << Integer(pillarChoice_) << ")"); + } + } + } + OISRateHelper::OISRateHelper(Natural settlementDays, const Period& tenor, // swap maturity const Handle& fixedRate, @@ -50,8 +103,8 @@ namespace QuantLib { Natural lockoutDays, bool applyObservationShift, ext::shared_ptr pricer) - : RelativeDateRateHelper(fixedRate), pillarChoice_(pillar), settlementDays_(settlementDays), tenor_(tenor), - discountHandle_(std::move(discount)), telescopicValueDates_(telescopicValueDates), + : OISRateHelperBase(fixedRate, overnightIndex, std::move(discount), pillar, customPillarDate), + settlementDays_(settlementDays), tenor_(tenor), telescopicValueDates_(telescopicValueDates), paymentLag_(paymentLag), paymentConvention_(paymentConvention), paymentFrequency_(paymentFrequency), paymentCalendar_(std::move(paymentCalendar)), forwardStart_(forwardStart), overnightSpread_(overnightSpread), @@ -59,23 +112,10 @@ namespace QuantLib { fixedPaymentFrequency_(fixedPaymentFrequency), fixedCalendar_(std::move(fixedCalendar)), lookbackDays_(lookbackDays), lockoutDays_(lockoutDays), applyObservationShift_(applyObservationShift), pricer_(std::move(pricer)) { - - overnightIndex_ = - 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.) - overnightIndex_->unregisterWith(termStructureHandle_); - - registerWith(overnightIndex_); - registerWith(discountHandle_); - - pillarDate_ = customPillarDate; OISRateHelper::initializeDates(); } 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_) @@ -107,57 +147,7 @@ namespace QuantLib { simplifyNotificationGraph(*swap_, true); - earliestDate_ = swap_->startDate(); - maturityDate_ = swap_->maturityDate(); - - Date lastPaymentDate = std::max(swap_->overnightLeg().back()->date(), - swap_->fixedLeg().back()->date()); - latestRelevantDate_ = latestDate_ = std::max(maturityDate_, lastPaymentDate); - - switch (pillarChoice_) { - case Pillar::MaturityDate: - pillarDate_ = maturityDate_; - break; - case Pillar::LastRelevantDate: - pillarDate_ = latestRelevantDate_; - break; - case Pillar::CustomDate: - // pillarDate_ already assigned at construction time - QL_REQUIRE(pillarDate_ >= earliestDate_, - "pillar date (" << pillarDate_ << ") must be later " - "than or equal to the instrument's earliest date (" << - earliestDate_ << ")"); - QL_REQUIRE(pillarDate_ <= latestRelevantDate_, - "pillar date (" << pillarDate_ << ") must be before " - "or equal to the instrument's latest relevant date (" << - latestRelevantDate_ << ")"); - break; - default: - QL_FAIL("unknown Pillar::Choice(" << Integer(pillarChoice_) << ")"); - } - } - - void OISRateHelper::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); - - RelativeDateRateHelper::setTermStructure(t); - } - - Real OISRateHelper::impliedQuote() const { - QL_REQUIRE(termStructure_ != nullptr, "term structure not set"); - // we didn't register as observers - force calculation - swap_->deepUpdate(); - return swap_->fairRate(); + setPillarDate(); } void OISRateHelper::accept(AcyclicVisitor& v) { @@ -186,33 +176,23 @@ namespace QuantLib { Natural lookbackDays, 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_); - + const ext::shared_ptr& pricer, + Pillar::Choice pillar, + Date customPillarDate) + : OISRateHelperBase(fixedRate, overnightIndex, std::move(discount), pillar, customPillarDate) { // 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) + auto tmp = MakeOIS(Period(), overnightIndex_, 0.0) .withDiscountingTermStructure(discountRelinkableHandle_) .withEffectiveDate(startDate) .withTerminationDate(endDate) - .withTelescopicValueDates(telescopicValueDates_) + .withTelescopicValueDates(telescopicValueDates) .withPaymentLag(paymentLag) .withPaymentAdjustment(paymentConvention) .withPaymentFrequency(paymentFrequency) .withPaymentCalendar(paymentCalendar) .withOvernightLegSpread(overnightSpread) - .withAveragingMethod(averagingMethod_) + .withAveragingMethod(averagingMethod) .withLookbackDays(lookbackDays) .withLockoutDays(lockoutDays) .withObservationShift(applyObservationShift); @@ -230,11 +210,9 @@ namespace QuantLib { 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); + simplifyNotificationGraph(*swap_, true); + + setPillarDate(); } DatedOISRateHelper::DatedOISRateHelper(const Date& startDate, @@ -257,29 +235,6 @@ 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) diff --git a/ql/termstructures/yield/oisratehelper.hpp b/ql/termstructures/yield/oisratehelper.hpp index 9135c479ee..3fcbeae045 100644 --- a/ql/termstructures/yield/oisratehelper.hpp +++ b/ql/termstructures/yield/oisratehelper.hpp @@ -28,13 +28,47 @@ #include #include #include +#include namespace QuantLib { class FloatingRateCouponPricer; + namespace detail { + template + class OISRateHelperBase : public Base { + public: + OISRateHelperBase(const Handle& fixedRate, + const ext::shared_ptr& overnightIndex, + Handle discountingCurve, + Pillar::Choice pillar, + Date customPillarDate); + //! \name RateHelper interface + //@{ + Real impliedQuote() const override; + void setTermStructure(YieldTermStructure*) override; + //@} + //! \name OISRateHelperBase inspectors + //@{ + // NOLINTNEXTLINE(cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + ext::shared_ptr swap() const { return swap_; } + //@} + protected: + void setPillarDate(); + + using Base::earliestDate_, Base::latestDate_; + using Base::maturityDate_, Base::latestRelevantDate_, Base::pillarDate_; + ext::shared_ptr overnightIndex_; + ext::shared_ptr swap_; + RelinkableHandle termStructureHandle_; + Handle discountHandle_; + RelinkableHandle discountRelinkableHandle_; + Pillar::Choice pillarChoice_; + }; + } + //! Rate helper for bootstrapping over Overnight Indexed Swap rates - class OISRateHelper : public RelativeDateRateHelper { + class OISRateHelper : public detail::OISRateHelperBase { public: OISRateHelper(Natural settlementDays, const Period& tenor, // swap maturity @@ -59,35 +93,16 @@ namespace QuantLib { Natural lockoutDays = 0, bool applyObservationShift = false, ext::shared_ptr pricer = {}); - //! \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: 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_; @@ -105,7 +120,7 @@ namespace QuantLib { }; //! Rate helper for bootstrapping over Overnight Indexed Swap rates - class DatedOISRateHelper : public RateHelper { + class DatedOISRateHelper : public detail::OISRateHelperBase { public: DatedOISRateHelper(const Date& startDate, const Date& endDate, @@ -126,7 +141,9 @@ namespace QuantLib { Natural lookbackDays = Null(), Natural lockoutDays = 0, bool applyObservationShift = false, - const ext::shared_ptr& pricer = {}); + const ext::shared_ptr& pricer = {}, + Pillar::Choice pillar = Pillar::LastRelevantDate, + Date customPillarDate = Date()); /*! \deprecated Use the overload without forward start. Deprecated in version 1.35. @@ -150,31 +167,40 @@ namespace QuantLib { 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_; }; + + namespace detail { + template + void OISRateHelperBase::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); + + Base::setTermStructure(t); + } + + template + Real OISRateHelperBase::impliedQuote() const { + QL_REQUIRE(this->termStructure_ != nullptr, "term structure not set"); + // we didn't register as observers - force calculation + swap_->deepUpdate(); + return swap_->fairRate(); + } + } } #endif