From 8a9852ccbdab3f765508bde10d6d945184bda357 Mon Sep 17 00:00:00 2001 From: Zetterberg <119352036+oszette@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:47:44 +0100 Subject: [PATCH 1/2] Add support for payment lag for xccy fix-float helpers Introduces support for fixedPaymentLag and floatPaymentLag parameters in CrossCcyFixFloatSwapConvention, CrossCcyFixFloatSwapHelper, and CrossCcyFixFloatMtMResetSwapHelper classes. Updates constructors, member variables, XML serialization, and swap initialization logic to handle payment lags for both fixed and floating legs. --- OREData/ored/configuration/conventions.cpp | 12 ++++++++++-- OREData/ored/configuration/conventions.hpp | 9 ++++++++- OREData/ored/marketdata/yieldcurve.cpp | 6 ++++-- .../crossccyfixfloatmtmresetswaphelper.cpp | 12 +++++++----- .../crossccyfixfloatmtmresetswaphelper.hpp | 5 ++++- .../termstructures/crossccyfixfloatswaphelper.cpp | 13 ++++++++----- .../termstructures/crossccyfixfloatswaphelper.hpp | 5 ++++- 7 files changed, 45 insertions(+), 17 deletions(-) diff --git a/OREData/ored/configuration/conventions.cpp b/OREData/ored/configuration/conventions.cpp index ca1b60fdf..97a45d693 100644 --- a/OREData/ored/configuration/conventions.cpp +++ b/OREData/ored/configuration/conventions.cpp @@ -1157,14 +1157,14 @@ CrossCcyFixFloatSwapConvention::CrossCcyFixFloatSwapConvention( const string& fixedConvention, const string& fixedDayCounter, const string& index, const string& eom, const std::string& strIsResettable, const std::string& strFloatIndexIsResettable, const string& strIncludeSpread, const string& strLookback, const string& strFixingDays, const string& strRateCutoff, - const string& strIsAveraged) + const string& strIsAveraged, const string& strFixedPaymentLag, const string& strFloatPaymentLag) : Convention(id, Type::CrossCcyFixFloat), strSettlementDays_(settlementDays), strSettlementCalendar_(settlementCalendar), strSettlementConvention_(settlementConvention), strFixedCurrency_(fixedCurrency), strFixedFrequency_(fixedFrequency), strFixedConvention_(fixedConvention), strFixedDayCounter_(fixedDayCounter), strIndex_(index), strEom_(eom), strIsResettable_(strIsResettable), strFloatIndexIsResettable_(strFloatIndexIsResettable), strIncludeSpread_(strIncludeSpread), strLookback_(strLookback), strFixingDays_(strFixingDays), strRateCutoff_(strRateCutoff), - strIsAveraged_(strIsAveraged) { + strIsAveraged_(strIsAveraged), strFixedPaymentLag_(strFixedPaymentLag), strFloatPaymentLag_(strFloatPaymentLag) { build(); } @@ -1191,6 +1191,8 @@ void CrossCcyFixFloatSwapConvention::build() { rateCutoff_ = parseInteger(strRateCutoff_); if (!strIsAveraged_.empty()) isAveraged_ = parseBool(strIsAveraged_); + fixedPaymentLag_ = strFixedPaymentLag_.empty() ? 0 : lexical_cast(strFixedPaymentLag_); + floatPaymentLag_ = strFloatPaymentLag_.empty() ? 0 : lexical_cast(strFloatPaymentLag_); } void CrossCcyFixFloatSwapConvention::fromXML(XMLNode* node) { @@ -1212,6 +1214,8 @@ void CrossCcyFixFloatSwapConvention::fromXML(XMLNode* node) { strEom_ = XMLUtils::getChildValue(node, "EOM", false); strIsResettable_ = XMLUtils::getChildValue(node, "IsResettable", false); strFloatIndexIsResettable_ = XMLUtils::getChildValue(node, "FloatIndexIsResettable", false); + strFixedPaymentLag_ = XMLUtils::getChildValue(node, "FixedPaymentLag", false); + strFloatPaymentLag_ = XMLUtils::getChildValue(node, "FloatPaymentLag", false); // OIS specific conventions @@ -1254,6 +1258,10 @@ XMLNode* CrossCcyFixFloatSwapConvention::toXML(XMLDocument& doc) const { XMLUtils::addChild(doc, node, "SpreadRateCutoff", strRateCutoff_); if (!strIsAveraged_.empty()) XMLUtils::addChild(doc, node, "SpreadIsAveraged", strIsAveraged_); + if (!strFixedPaymentLag_.empty()) + XMLUtils::addChild(doc, node, "FixedPaymentLag", strFixedPaymentLag_); + if (!strFloatPaymentLag_.empty()) + XMLUtils::addChild(doc, node, "FloatPaymentLag", strFloatPaymentLag_); return node; } diff --git a/OREData/ored/configuration/conventions.hpp b/OREData/ored/configuration/conventions.hpp index 40c83d8bf..c5c5a7869 100644 --- a/OREData/ored/configuration/conventions.hpp +++ b/OREData/ored/configuration/conventions.hpp @@ -1004,7 +1004,8 @@ class CrossCcyFixFloatSwapConvention : public Convention { const std::string& strFloatIndexIsResettable = "", const string& strIncludeSpread = "", const string& strLookback = "", const string& strFixingDays = "", const string& strRateCutoff = "", - const string& strIsAveraged = ""); + const string& strIsAveraged = "", const string& strFixedPaymentLag = "", + const string& strFloatPaymentLag = ""); //@} //! \name Inspectors @@ -1020,6 +1021,8 @@ class CrossCcyFixFloatSwapConvention : public Convention { bool eom() const { return eom_; } bool isResettable() const { return isResettable_; } bool floatIndexIsResettable() const { return floatIndexIsResettable_; } + QuantLib::Natural fixedPaymentLag() const { return fixedPaymentLag_; } + QuantLib::Natural floatPaymentLag() const { return floatPaymentLag_; } // only OIS QuantLib::ext::optional includeSpread() const { return includeSpread_; } @@ -1051,6 +1054,8 @@ class CrossCcyFixFloatSwapConvention : public Convention { bool eom_; bool isResettable_; bool floatIndexIsResettable_; + QuantLib::Natural fixedPaymentLag_; + QuantLib::Natural floatPaymentLag_; // Strings to store the inputs std::string strSettlementDays_; @@ -1071,6 +1076,8 @@ class CrossCcyFixFloatSwapConvention : public Convention { std::string strFixingDays_; std::string strRateCutoff_; std::string strIsAveraged_; + std::string strFixedPaymentLag_; + std::string strFloatPaymentLag_; // OIS Only QuantLib::ext::optional includeSpread_; diff --git a/OREData/ored/marketdata/yieldcurve.cpp b/OREData/ored/marketdata/yieldcurve.cpp index e3e4c6524..ade541a20 100644 --- a/OREData/ored/marketdata/yieldcurve.cpp +++ b/OREData/ored/marketdata/yieldcurve.cpp @@ -3043,7 +3043,8 @@ void YieldCurve::addCrossCcyFixFloatSwaps(const std::size_t index, currency_[index], swapConvention->fixedFrequency(), swapConvention->fixedConvention(), swapConvention->fixedDayCounter(), floatIndex, floatLegDisc, Handle(), swapConvention->eom(),true, segment->pillarChoice(), swapConvention->includeSpread(), swapConvention->lookback(), - swapConvention->fixingDays(), swapConvention->rateCutoff(), swapConvention->isAveraged()); + swapConvention->fixingDays(), swapConvention->rateCutoff(), swapConvention->isAveraged(), + swapConvention->fixedPaymentLag(), swapConvention->floatPaymentLag()); } else { bool resetsOnFloatLeg = swapConvention->floatIndexIsResettable(); helper = QuantLib::ext::make_shared( @@ -3053,7 +3054,8 @@ void YieldCurve::addCrossCcyFixFloatSwaps(const std::size_t index, swapConvention->fixedDayCounter(), floatIndex, floatLegDisc, Handle(), swapConvention->eom(), true, resetsOnFloatLeg, segment->pillarChoice(), swapConvention->includeSpread(), swapConvention->lookback(), - swapConvention->fixingDays(), swapConvention->rateCutoff(), swapConvention->isAveraged()); + swapConvention->fixingDays(), swapConvention->rateCutoff(), swapConvention->isAveraged(), + swapConvention->fixedPaymentLag(), swapConvention->floatPaymentLag()); } instruments.push_back(helper); } diff --git a/QuantExt/qle/termstructures/crossccyfixfloatmtmresetswaphelper.cpp b/QuantExt/qle/termstructures/crossccyfixfloatmtmresetswaphelper.cpp index 3aca4926a..d3e6cb00f 100644 --- a/QuantExt/qle/termstructures/crossccyfixfloatmtmresetswaphelper.cpp +++ b/QuantExt/qle/termstructures/crossccyfixfloatmtmresetswaphelper.cpp @@ -35,14 +35,15 @@ CrossCcyFixFloatMtMResetSwapHelper::CrossCcyFixFloatMtMResetSwapHelper( const Handle& spread, bool endOfMonth, bool resetsOnFloatLeg, bool telescopicValueDates, const QuantLib::Pillar::Choice pillarChoice, QuantLib::ext::optional includeSpread, QuantLib::ext::optional lookback, QuantLib::ext::optional fixingDays, - QuantLib::ext::optional rateCutoff, QuantLib::ext::optional isAveraged) + QuantLib::ext::optional rateCutoff, QuantLib::ext::optional isAveraged, QuantLib::ext::optional fixedPaymentLag, + QuantLib::ext::optional floatPaymentLag) : RelativeDateRateHelper(rate), spotFx_(spotFx), settlementDays_(settlementDays), paymentCalendar_(paymentCalendar), paymentConvention_(paymentConvention), tenor_(tenor), fixedCurrency_(fixedCurrency), fixedFrequency_(fixedFrequency), fixedConvention_(fixedConvention), fixedDayCount_(fixedDayCount), index_(index), floatDiscount_(floatDiscount), spread_(spread), endOfMonth_(endOfMonth), resetsOnFloatLeg_(resetsOnFloatLeg), telescopicValueDates_(telescopicValueDates), pillarChoice_(pillarChoice), includeSpread_(includeSpread), lookback_(lookback), fixingDays_(fixingDays), - rateCutoff_(rateCutoff), isAveraged_(isAveraged) { + rateCutoff_(rateCutoff), isAveraged_(isAveraged), fixedPaymentLag_(fixedPaymentLag), floatPaymentLag_(floatPaymentLag_) { QL_REQUIRE(!spotFx_.empty(), "Spot FX quote cannot be empty."); QL_REQUIRE(fixedCurrency_ != index_->currency(), "Fixed currency should not equal float leg currency."); @@ -74,7 +75,8 @@ void CrossCcyFixFloatMtMResetSwapHelper::initializeDates() { Real nominal = 1.0; // build an FX index for forward rate projection (TODO - review settlement and calendar) - Natural paymentLag = 0; + Natural fixedPaymentLag = fixedPaymentLag_ ? *fixedPaymentLag_ : 0; + Natural floatPaymentLag = floatPaymentLag_ ? *floatPaymentLag_ : 0; Spread floatSpread = spread_.empty() ? 0.0 : spread_->value(); QuantLib::ext::shared_ptr fxIdx; if (resetsOnFloatLeg_) { @@ -86,8 +88,8 @@ void CrossCcyFixFloatMtMResetSwapHelper::initializeDates() { } swap_ = QuantLib::ext::make_shared(nominal, fixedCurrency_, fixedSchedule, 0.0, fixedDayCount_, paymentConvention_, - paymentLag, paymentCalendar_, index_->currency(), floatSchedule, index_, floatSpread, paymentConvention_, - paymentLag, paymentCalendar_, fxIdx, resetsOnFloatLeg_, true, includeSpread_, lookback_, fixingDays_, rateCutoff_, isAveraged_); + fixedPaymentLag, paymentCalendar_, index_->currency(), floatSchedule, index_, floatSpread, paymentConvention_, + floatPaymentLag, paymentCalendar_, fxIdx, resetsOnFloatLeg_, true, includeSpread_, lookback_, fixingDays_, rateCutoff_, isAveraged_); // Attach engine QuantLib::ext::shared_ptr engine = QuantLib::ext::make_shared( diff --git a/QuantExt/qle/termstructures/crossccyfixfloatmtmresetswaphelper.hpp b/QuantExt/qle/termstructures/crossccyfixfloatmtmresetswaphelper.hpp index 224b5c31d..42b447093 100644 --- a/QuantExt/qle/termstructures/crossccyfixfloatmtmresetswaphelper.hpp +++ b/QuantExt/qle/termstructures/crossccyfixfloatmtmresetswaphelper.hpp @@ -64,7 +64,8 @@ class CrossCcyFixFloatMtMResetSwapHelper : public RelativeDateRateHelper { bool telescopicValueDates = false, const QuantLib::Pillar::Choice pillarChoice = QuantLib::Pillar::LastRelevantDate, QuantLib::ext::optional includeSpread = QuantLib::ext::nullopt, QuantLib::ext::optional lookback = QuantLib::ext::nullopt, QuantLib::ext::optional fixingDays = QuantLib::ext::nullopt, QuantLib::ext::optional rateCutoff = QuantLib::ext::nullopt, - QuantLib::ext::optional isAveraged = QuantLib::ext::nullopt); + QuantLib::ext::optional isAveraged = QuantLib::ext::nullopt, QuantLib::ext::optional fixedPaymenyLag = QuantLib::ext::nullopt, + QuantLib::ext::optional floatPaymentLag = QuantLib::ext::nullopt); //! \name RateHelper interface //@{ Real impliedQuote() const override; @@ -107,6 +108,8 @@ class CrossCcyFixFloatMtMResetSwapHelper : public RelativeDateRateHelper { QuantLib::ext::optional fixingDays_; QuantLib::ext::optional rateCutoff_; QuantLib::ext::optional isAveraged_; + QuantLib::ext::optional fixedPaymentLag_; + QuantLib::ext::optional floatPaymentLag_; QuantLib::ext::shared_ptr swap_; diff --git a/QuantExt/qle/termstructures/crossccyfixfloatswaphelper.cpp b/QuantExt/qle/termstructures/crossccyfixfloatswaphelper.cpp index aebd194b7..54c20f060 100644 --- a/QuantExt/qle/termstructures/crossccyfixfloatswaphelper.cpp +++ b/QuantExt/qle/termstructures/crossccyfixfloatswaphelper.cpp @@ -40,14 +40,15 @@ CrossCcyFixFloatSwapHelper::CrossCcyFixFloatSwapHelper( const QuantLib::ext::shared_ptr& index, const Handle& floatDiscount, const Handle& spread, bool endOfMonth, bool telescopicValueDates, QuantLib::Pillar::Choice pillarChoice, QuantLib::ext::optional includeSpread, QuantLib::ext::optional lookback, QuantLib::ext::optional fixingDays, - QuantLib::ext::optional rateCutoff, QuantLib::ext::optional isAveraged) + QuantLib::ext::optional rateCutoff, QuantLib::ext::optional isAveraged, + QuantLib::ext::optional fixedPaymentLag, QuantLib::ext::optional floatPaymentLag) : RelativeDateRateHelper(rate), spotFx_(spotFx), settlementDays_(settlementDays), paymentCalendar_(paymentCalendar), paymentConvention_(paymentConvention), tenor_(tenor), fixedCurrency_(fixedCurrency), fixedFrequency_(fixedFrequency), fixedConvention_(fixedConvention), fixedDayCount_(fixedDayCount), index_(index), floatDiscount_(floatDiscount), spread_(spread), endOfMonth_(endOfMonth), telescopicValueDates_(telescopicValueDates), pillarChoice_(pillarChoice), includeSpread_(includeSpread), lookback_(lookback), fixingDays_(fixingDays), rateCutoff_(rateCutoff), - isAveraged_(isAveraged) { + isAveraged_(isAveraged), fixedPaymentLag_(fixedPaymentLag), floatPaymentLag_(floatPaymentLag) { QL_REQUIRE(!spotFx_.empty(), "Spot FX quote cannot be empty."); QL_REQUIRE(fixedCurrency_ != index_->currency(), "Fixed currency should not equal float leg currency."); @@ -114,12 +115,14 @@ void CrossCcyFixFloatSwapHelper::initializeDates() { DateGeneration::Backward, endOfMonth_); // Create the swap - Natural paymentLag = 0; + Natural fixedPaymentLag = fixedPaymentLag_ ? *fixedPaymentLag_ : 0; + Natural floatPaymentLag = floatPaymentLag_ ? *floatPaymentLag_ : 0; Spread floatSpread = spread_.empty() ? 0.0 : spread_->value(); swap_.reset(new CrossCcyFixFloatSwap(CrossCcyFixFloatSwap::Payer, fixedNominal, fixedCurrency_, fixedSchedule, 0.0, - fixedDayCount_, paymentConvention_, paymentLag, paymentCalendar_, floatNominal, + fixedDayCount_, paymentConvention_, fixedPaymentLag, paymentCalendar_, + floatNominal, index_->currency(), floatSchedule, index_, floatSpread, paymentConvention_, - paymentLag, paymentCalendar_, telescopicValueDates_, includeSpread_, lookback_, + floatPaymentLag, paymentCalendar_, telescopicValueDates_, includeSpread_, lookback_, fixingDays_, rateCutoff_, isAveraged_)); earliestDate_ = swap_->startDate(); diff --git a/QuantExt/qle/termstructures/crossccyfixfloatswaphelper.hpp b/QuantExt/qle/termstructures/crossccyfixfloatswaphelper.hpp index 122a1ab80..862ad3206 100644 --- a/QuantExt/qle/termstructures/crossccyfixfloatswaphelper.hpp +++ b/QuantExt/qle/termstructures/crossccyfixfloatswaphelper.hpp @@ -50,7 +50,8 @@ class CrossCcyFixFloatSwapHelper : public RelativeDateRateHelper { const QuantLib::Pillar::Choice pillarChoice = QuantLib::Pillar::LastRelevantDate, QuantLib::ext::optional includeSpread = QuantLib::ext::nullopt, QuantLib::ext::optional lookback = QuantLib::ext::nullopt, QuantLib::ext::optional fixingDays = QuantLib::ext::nullopt, QuantLib::ext::optional rateCutoff = QuantLib::ext::nullopt, - QuantLib::ext::optional isAveraged = QuantLib::ext::nullopt); + QuantLib::ext::optional isAveraged = QuantLib::ext::nullopt, QuantLib::ext::optional fixedPaymentLag = QuantLib::ext::nullopt, + QuantLib::ext::optional floatPaymentLag = QuantLib::ext::nullopt); //! \name Observer interface //@{ @@ -99,6 +100,8 @@ class CrossCcyFixFloatSwapHelper : public RelativeDateRateHelper { QuantLib::ext::optional fixingDays_; QuantLib::ext::optional rateCutoff_; QuantLib::ext::optional isAveraged_; + QuantLib::ext::optional fixedPaymentLag_; + QuantLib::ext::optional floatPaymentLag_; QuantLib::ext::shared_ptr swap_; QuantLib::RelinkableHandle termStructureHandle_; From adb5bc79249ef258db735738c25f48b44c35195d Mon Sep 17 00:00:00 2001 From: Zetterberg <119352036+oszette@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:50:26 +0100 Subject: [PATCH 2/2] Update conventions.xsd --- xsd/conventions.xsd | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xsd/conventions.xsd b/xsd/conventions.xsd index a101ee3d4..746f7d881 100755 --- a/xsd/conventions.xsd +++ b/xsd/conventions.xsd @@ -248,6 +248,13 @@ + + + + + + +