From 4d5cedc5751eba504ec6e1336b06f9090240a3c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6hnenkamp?= Date: Fri, 8 Mar 2024 14:01:22 +0100 Subject: [PATCH 1/2] Allow negative payment lag Aligning with the modification in QuantLib (PR 1818), this commit implements the update to the payment lag in ORE. The adjustment now accommodates negative values using Integer data type. --- OREData/ored/portfolio/types.hpp | 10 ++-- OREData/ored/utilities/parsers.cpp | 4 +- .../qle/cashflows/averageonindexedcoupon.cpp | 2 +- .../qle/cashflows/averageonindexedcoupon.hpp | 4 +- QuantExt/qle/cashflows/cpicoupon.cpp | 2 +- QuantExt/qle/cashflows/cpicoupon.hpp | 4 +- QuantExt/qle/cashflows/equitycoupon.cpp | 2 +- QuantExt/qle/cashflows/equitycoupon.hpp | 4 +- .../qle/cashflows/overnightindexedcoupon.cpp | 2 +- .../qle/cashflows/overnightindexedcoupon.hpp | 4 +- QuantExt/test/cpileg.cpp | 51 +++++++++++++++++++ 11 files changed, 70 insertions(+), 19 deletions(-) diff --git a/OREData/ored/portfolio/types.hpp b/OREData/ored/portfolio/types.hpp index 0273c24ec9..ec8ae63a1a 100644 --- a/OREData/ored/portfolio/types.hpp +++ b/OREData/ored/portfolio/types.hpp @@ -29,18 +29,18 @@ namespace ore { namespace data { -typedef boost::variant PaymentLag; +typedef boost::variant PaymentLag; struct PaymentLagPeriod : public boost::static_visitor { public: - QuantLib::Period operator()(const QuantLib::Natural& n) const { return Period(n, Days); } + QuantLib::Period operator()(const QuantLib::Integer& n) const { return Period(n, Days); } QuantLib::Period operator()(const QuantLib::Period& p) const { return p; } }; -struct PaymentLagInteger : public boost::static_visitor { +struct PaymentLagInteger : public boost::static_visitor { public: - QuantLib::Natural operator()(const QuantLib::Natural& n) const { return n; } - QuantLib::Natural operator()(const QuantLib::Period& p) const { return static_cast(days(p)); } + QuantLib::Integer operator()(const QuantLib::Integer& n) const { return n; } + QuantLib::Integer operator()(const QuantLib::Period& p) const { return static_cast(days(p)); } }; } // namespace data diff --git a/OREData/ored/utilities/parsers.cpp b/OREData/ored/utilities/parsers.cpp index 111f8b78fc..1a8a2b9b88 100644 --- a/OREData/ored/utilities/parsers.cpp +++ b/OREData/ored/utilities/parsers.cpp @@ -622,10 +622,10 @@ Month parseMonth(const string& s) { PaymentLag parsePaymentLag(const string& s) { Period p; - Natural n; + Integer n = 0; if (tryParse(s, p, parsePeriod)) return p; - else if (tryParse(s, n, parseInteger)) + else if (tryParse(s, n, parseInteger)) return n; else return 0; diff --git a/QuantExt/qle/cashflows/averageonindexedcoupon.cpp b/QuantExt/qle/cashflows/averageonindexedcoupon.cpp index e7d20d17d0..d4bdc46847 100644 --- a/QuantExt/qle/cashflows/averageonindexedcoupon.cpp +++ b/QuantExt/qle/cashflows/averageonindexedcoupon.cpp @@ -352,7 +352,7 @@ AverageONLeg& AverageONLeg::withPaymentCalendar(const Calendar& calendar) { return *this; } -AverageONLeg& AverageONLeg::withPaymentLag(Natural lag) { +AverageONLeg& AverageONLeg::withPaymentLag(Integer lag) { paymentLag_ = lag; return *this; } diff --git a/QuantExt/qle/cashflows/averageonindexedcoupon.hpp b/QuantExt/qle/cashflows/averageonindexedcoupon.hpp index 803afa3e1e..2f50462be0 100644 --- a/QuantExt/qle/cashflows/averageonindexedcoupon.hpp +++ b/QuantExt/qle/cashflows/averageonindexedcoupon.hpp @@ -190,7 +190,7 @@ class AverageONLeg { AverageONLeg& withTelescopicValueDates(bool telescopicValueDates); AverageONLeg& withRateCutoff(Natural rateCutoff); AverageONLeg& withPaymentCalendar(const Calendar& calendar); - AverageONLeg& withPaymentLag(Natural lag); + AverageONLeg& withPaymentLag(Integer lag); AverageONLeg& withLookback(const Period& lookback); AverageONLeg& withFixingDays(const Size fixingDays); AverageONLeg& withCaps(Rate cap); @@ -215,7 +215,7 @@ class AverageONLeg { std::vector notionals_; DayCounter paymentDayCounter_; BusinessDayConvention paymentAdjustment_; - Natural paymentLag_; + Integer paymentLag_; std::vector gearings_; std::vector spreads_; bool telescopicValueDates_; diff --git a/QuantExt/qle/cashflows/cpicoupon.cpp b/QuantExt/qle/cashflows/cpicoupon.cpp index 763ac7e78b..bbb05f7e38 100644 --- a/QuantExt/qle/cashflows/cpicoupon.cpp +++ b/QuantExt/qle/cashflows/cpicoupon.cpp @@ -277,7 +277,7 @@ CPILeg& CPILeg::withPaymentCalendar(const Calendar& cal) { return *this; } -CPILeg& CPILeg::withPaymentLag(Natural lag) { +CPILeg& CPILeg::withPaymentLag(Integer lag) { paymentLag_ = lag; return *this; } diff --git a/QuantExt/qle/cashflows/cpicoupon.hpp b/QuantExt/qle/cashflows/cpicoupon.hpp index e97fea66c2..7cf368b4ff 100644 --- a/QuantExt/qle/cashflows/cpicoupon.hpp +++ b/QuantExt/qle/cashflows/cpicoupon.hpp @@ -146,7 +146,7 @@ class CPILeg { CPILeg& withPaymentDayCounter(const DayCounter&); CPILeg& withPaymentAdjustment(BusinessDayConvention); CPILeg& withPaymentCalendar(const Calendar&); - CPILeg& withPaymentLag(Natural lag); + CPILeg& withPaymentLag(Integer lag); CPILeg& withFixingDays(Natural fixingDays); CPILeg& withFixingDays(const std::vector& fixingDays); CPILeg& withObservationInterpolation(CPI::InterpolationType); @@ -175,7 +175,7 @@ class CPILeg { DayCounter paymentDayCounter_; BusinessDayConvention paymentAdjustment_; Calendar paymentCalendar_; - Natural paymentLag_; + Integer paymentLag_; std::vector fixingDays_; CPI::InterpolationType observationInterpolation_; bool subtractInflationNominal_; diff --git a/QuantExt/qle/cashflows/equitycoupon.cpp b/QuantExt/qle/cashflows/equitycoupon.cpp index 190bf4fd07..afce9e1b03 100644 --- a/QuantExt/qle/cashflows/equitycoupon.cpp +++ b/QuantExt/qle/cashflows/equitycoupon.cpp @@ -203,7 +203,7 @@ EquityLeg& EquityLeg::withPaymentAdjustment(BusinessDayConvention convention) { return *this; } -EquityLeg& EquityLeg::withPaymentLag(Natural paymentLag) { +EquityLeg& EquityLeg::withPaymentLag(Integer paymentLag) { paymentLag_ = paymentLag; return *this; } diff --git a/QuantExt/qle/cashflows/equitycoupon.hpp b/QuantExt/qle/cashflows/equitycoupon.hpp index 4173da1a13..4127f1e394 100644 --- a/QuantExt/qle/cashflows/equitycoupon.hpp +++ b/QuantExt/qle/cashflows/equitycoupon.hpp @@ -166,7 +166,7 @@ class EquityLeg { EquityLeg& withNotionals(const std::vector& notionals); EquityLeg& withPaymentDayCounter(const DayCounter& dayCounter); EquityLeg& withPaymentAdjustment(BusinessDayConvention convention); - EquityLeg& withPaymentLag(Natural paymentLag); + EquityLeg& withPaymentLag(Integer paymentLag); EquityLeg& withPaymentCalendar(const Calendar& calendar); EquityLeg& withReturnType(EquityReturnType); EquityLeg& withDividendFactor(Real); @@ -184,7 +184,7 @@ class EquityLeg { boost::shared_ptr fxIndex_; std::vector notionals_; DayCounter paymentDayCounter_; - Natural paymentLag_; + Integer paymentLag_; BusinessDayConvention paymentAdjustment_; Calendar paymentCalendar_; EquityReturnType returnType_; diff --git a/QuantExt/qle/cashflows/overnightindexedcoupon.cpp b/QuantExt/qle/cashflows/overnightindexedcoupon.cpp index 55fc25ab65..61efe0b01f 100644 --- a/QuantExt/qle/cashflows/overnightindexedcoupon.cpp +++ b/QuantExt/qle/cashflows/overnightindexedcoupon.cpp @@ -496,7 +496,7 @@ OvernightLeg& OvernightLeg::withPaymentCalendar(const Calendar& cal) { return *this; } -OvernightLeg& OvernightLeg::withPaymentLag(Natural lag) { +OvernightLeg& OvernightLeg::withPaymentLag(Integer lag) { paymentLag_ = lag; return *this; } diff --git a/QuantExt/qle/cashflows/overnightindexedcoupon.hpp b/QuantExt/qle/cashflows/overnightindexedcoupon.hpp index 3a624b1e80..966d83f03e 100644 --- a/QuantExt/qle/cashflows/overnightindexedcoupon.hpp +++ b/QuantExt/qle/cashflows/overnightindexedcoupon.hpp @@ -234,7 +234,7 @@ class OvernightLeg { OvernightLeg& withPaymentDayCounter(const DayCounter&); OvernightLeg& withPaymentAdjustment(BusinessDayConvention); OvernightLeg& withPaymentCalendar(const Calendar&); - OvernightLeg& withPaymentLag(Natural lag); + OvernightLeg& withPaymentLag(Integer lag); OvernightLeg& withGearings(Real gearing); OvernightLeg& withGearings(const std::vector& gearings); OvernightLeg& withSpreads(Spread spread); @@ -266,7 +266,7 @@ class OvernightLeg { DayCounter paymentDayCounter_; Calendar paymentCalendar_; BusinessDayConvention paymentAdjustment_; - Natural paymentLag_; + Integer paymentLag_; std::vector gearings_; std::vector spreads_; bool telescopicValueDates_; diff --git a/QuantExt/test/cpileg.cpp b/QuantExt/test/cpileg.cpp index 24a22730f4..38e6047e50 100644 --- a/QuantExt/test/cpileg.cpp +++ b/QuantExt/test/cpileg.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include using namespace QuantLib; @@ -70,6 +72,55 @@ BOOST_AUTO_TEST_CASE(testCpiLegPaymentLag) { } } +BOOST_AUTO_TEST_CASE(testCpiLegNegativePaymentLag) { + + BOOST_TEST_MESSAGE("Testing QuantExt::CPILeg for negative payment lag..."); + + Date evaluationDate(6, October, 2023); + Settings::instance().evaluationDate() = evaluationDate; + Calendar calendar = WeekendsOnly(); + DayCounter dayCounter = SimpleDayCounter(); + BusinessDayConvention dayConvention = ModifiedFollowing; + + Date startDate(6, October, 2023); + Date endDate(6, October, 2024); + Schedule fixedSchedule = MakeSchedule() + .from(startDate) + .to(endDate) + .withTenor(Period(6, Months)) + .withCalendar(calendar) + .withConvention(dayConvention) + .backwards(); + + auto flatYts = ext::shared_ptr(new FlatForward(evaluationDate, 0.025, dayCounter)); + RelinkableHandle yTS(flatYts); + + auto ukrpi = ext::make_shared(); + + Integer paymentLag = -2; + Leg cpiLeg = QuantExt::CPILeg(fixedSchedule, ukrpi, yTS, 100, Period(3, Months)) + .withNotionals(1e6) + .withFixedRates(0.01) + .withPaymentCalendar(calendar) + .withPaymentDayCounter(dayCounter) + .withPaymentAdjustment(dayConvention) + .withPaymentLag(paymentLag); + + + for (auto& coupon : cpiLeg) { + if (auto cpiCoupon = ext::dynamic_pointer_cast(coupon)) { + // The setup leg will have six regular coupons + Date testPaymentLag = calendar.advance(cpiCoupon->accrualEndDate(), paymentLag, Days, dayConvention); + + BOOST_CHECK_EQUAL(cpiCoupon->date(), testPaymentLag); + } else if (auto cpiNotionalCashflow = ext::dynamic_pointer_cast(coupon)) { + // and one of these flows + Date testPaymentLag = calendar.advance(fixedSchedule.endDate(), paymentLag, Days, dayConvention); + BOOST_CHECK_EQUAL(cpiNotionalCashflow->date(), testPaymentLag); + } + } +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() From 3d775207911a037615cfce4f0b31fb9d9798bc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6hnenkamp?= Date: Fri, 8 Mar 2024 17:49:44 +0100 Subject: [PATCH 2/2] Update user guide with change --- Docs/UserGuide/tradecomponents/legdatanotionals.tex | 2 +- Docs/UserGuide/tradedata/fxforward.tex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Docs/UserGuide/tradecomponents/legdatanotionals.tex b/Docs/UserGuide/tradecomponents/legdatanotionals.tex index 7839c3f3e2..d0d41a2357 100644 --- a/Docs/UserGuide/tradecomponents/legdatanotionals.tex +++ b/Docs/UserGuide/tradecomponents/legdatanotionals.tex @@ -65,7 +65,7 @@ \subsubsection{Leg Data and Notionals} \item PaymentLag [optional]: The payment lag applies to Fixed legs, Equity legs, and Floating legs with Ibor and OIS indices (but not to BMA/SIFMA indices), as well as CPI legs and Zero Coupon Fixed legs. \\ PaymentLag is also not supported for CapFloor Floating legs that have Ibor coupons with sub periods (HasSubPeriods = \emph{true}), nor for CapFloor Floating legs with averaged ON coupons (IsAveraged = \emph{true}). -Allowable values: Any valid period, i.e. a non-negative whole number, optionally followed by \emph{D} (days), \emph{W} (weeks), \emph{M} (months), +Allowable values: Any valid period, i.e. a positive or negative whole number, optionally followed by \emph{D} (days), \emph{W} (weeks), \emph{M} (months), \emph{Y} (years). Defaults to \emph{0D} if left blank or omitted. If a whole number is given and no letter, it is assumed that it is a number of \emph{D} (days). \item DayCounter: The day count convention of the leg coupons. Note that \lstinline!DayCounter! is mandatory for all leg types except \emph{Equity}. diff --git a/Docs/UserGuide/tradedata/fxforward.tex b/Docs/UserGuide/tradedata/fxforward.tex index 20b5fd02e8..1480123b0b 100644 --- a/Docs/UserGuide/tradedata/fxforward.tex +++ b/Docs/UserGuide/tradedata/fxforward.tex @@ -50,7 +50,7 @@ \subsubsection{FX Forward} The \lstinline!Rules! sub-node is shown in Listing \ref{lst:settlement_data_node}, and the meanings and allowable values of its elements follow below. \begin{itemize} \item PaymentLag [Optional]: The lag between the value date and the payment date. \\ - Allowable values: Any valid period, i.e.\ a non-negative whole number, optionally followed by \emph{D} (days), \emph{W} (weeks), \emph{M} (months), + Allowable values: Any valid period, i.e.\ a negative or positive whole number, optionally followed by \emph{D} (days), \emph{W} (weeks), \emph{M} (months), \emph{Y} (years). For cash settlement and if a FXIndex is specified defaults to the fx convention (field ``SpotDays'') if blank or omitted, otherwise to 0. If a whole number is given and no letter, it is assumed that it is a number of \emph{D} (days). \item PaymentCalendar [Optional]: The calendar to be used when applying the payment lag. \\ Allowable values: See Table \ref{tab:calendar} \lstinline!Calendar!. For cash settlement and if a FXIndex is specified defaults to the fx convention (field ``AdvanceCalendar'') if left blank or omitted, otherwise to NullCalendar (no holidays).