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). 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()