Skip to content

Commit a0b5961

Browse files
pcaspersjenkins
authored andcommitted
QPR-12379 -- Add separate payment lag for swap notional flows, and apply X-CCY reset swap payment lag more consistently
1 parent 077acfa commit a0b5961

5 files changed

Lines changed: 43 additions & 24 deletions

File tree

Docs/UserGuide/tradecomponents/legdatanotionals.tex

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ \subsubsection{Leg Data and Notionals}
5252

5353
Allowable values: See Table \ref{tab:currency} \lstinline!Currency!. When \lstinline!LegType! is \emph{Equity}, Minor Currencies in Table \ref{tab:currency} are also allowable.
5454

55-
\item PaymentCalendar [Optional]: The payment calendar of the leg coupons. The \lstinline!PaymentCalendar! is used in conjunction with the \lstinline!PaymentConvention! and the \lstinline!PaymentLag! to determine the payments dates, unless the \lstinline!PaymentDates! node is used which defines the payment dates explicitly.
55+
\item PaymentCalendar [Optional]: The payment calendar of the leg coupons. The \lstinline!PaymentCalendar! is used in conjunction with the \lstinline!PaymentConvention!, \lstinline!PaymentLag! and \lstinline!NotionalPaymentLag! to determine the payments dates, unless the \lstinline!PaymentDates! node is used which defines the payment dates explicitly.
5656

5757
Allowable values: See Table \ref{tab:calendar} \lstinline!Calendar!. If left blank or omitted, defaults to the calendar in the \lstinline!ScheduleData! node, unless \lstinline!LegType! is \emph{Floating} and \lstinline!Index! is OIS, in which case this defaults to the index calendar.
5858

@@ -62,12 +62,16 @@ \subsubsection{Leg Data and Notionals}
6262

6363
Allowable values: See Table \ref{tab:convention}.
6464

65-
\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 CMS legs, CMSSpread legs, CPI legs and Zero Coupon Fixed legs. \\
65+
\item PaymentLag [optional]: The payment lag applies to the coupons on Fixed legs, Equity legs, and Floating legs with Ibor and OIS indices (but not to BMA/SIFMA indices), as well as CMS legs, CMSSpread legs, CPI legs and Zero Coupon Fixed legs. \\
6666
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}).
6767

6868
Allowable values: Any valid period, i.e. a non-negative whole number, optionally followed by \emph{D} (days), \emph{W} (weeks), \emph{M} (months),
6969
\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).
7070

71+
\item NotionalPaymentLag [optional]: The notional payment lag (in days) applied to any notional exchanges.
72+
73+
Allowable values: Any non-negative integer. Defaults to zero if left blank or omitted.
74+
7175
\item DayCounter: The day count convention of the leg coupons. Note that \lstinline!DayCounter! is mandatory for all leg types except \emph{Equity}.
7276

7377
Allowable values: See \lstinline!DayCount Convention! in Table \ref{tab:daycount}. For \emph{Equity} legs, if left blank or omitted, it defaults to \emph{ACT/365}.

OREData/ored/portfolio/commodityapo.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ Leg CommodityAveragePriceOption::buildLeg(const QuantLib::ext::shared_ptr<Engine
239239
vector<string> paymentDates = paymentDate_.empty() ? vector<string>() : vector<string>(1, paymentDate_);
240240
LegData legData(commLegData, true, currency_, scheduleData, "", vector<Real>(), vector<string>(),
241241
paymentConvention_, false, false, false, true, "", 0, "", vector<AmortizationData>(),
242-
paymentLag_, paymentCalendar_, paymentDates);
242+
paymentLag_, "", paymentCalendar_, paymentDates);
243243

244244
// Get the leg builder, set the allAveraging_ flag and build the leg
245245
auto legBuilder = engineFactory->legBuilder(legData.legType());

OREData/ored/portfolio/legdata.cpp

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ LegData::LegData(const QuantLib::ext::shared_ptr<LegAdditionalData>& concreteLeg
735735
const bool notionalAmortizingExchange, const bool isNotResetXCCY, const string& foreignCurrency,
736736
const double foreignAmount, const string& fxIndex,
737737
const std::vector<AmortizationData>& amortizationData, const string& paymentLag,
738-
const string& paymentCalendar, const vector<string>& paymentDates,
738+
const string& notionalPaymentLag, const string& paymentCalendar, const vector<string>& paymentDates,
739739
const std::vector<Indexing>& indexing, const bool indexingFromAssetLeg,
740740
const string& lastPeriodDayCounter)
741741
: concreteLegData_(concreteLegData), isPayer_(isPayer), currency_(currency), schedule_(scheduleData),
@@ -744,8 +744,8 @@ LegData::LegData(const QuantLib::ext::shared_ptr<LegAdditionalData>& concreteLeg
744744
notionalFinalExchange_(notionalFinalExchange), notionalAmortizingExchange_(notionalAmortizingExchange),
745745
isNotResetXCCY_(isNotResetXCCY), foreignCurrency_(foreignCurrency), foreignAmount_(foreignAmount),
746746
fxIndex_(fxIndex), amortizationData_(amortizationData), paymentLag_(paymentLag),
747-
paymentCalendar_(paymentCalendar), paymentDates_(paymentDates), indexing_(indexing),
748-
indexingFromAssetLeg_(indexingFromAssetLeg), lastPeriodDayCounter_(lastPeriodDayCounter) {
747+
notionalPaymentLag_(notionalPaymentLag), paymentCalendar_(paymentCalendar), paymentDates_(paymentDates),
748+
indexing_(indexing), indexingFromAssetLeg_(indexingFromAssetLeg), lastPeriodDayCounter_(lastPeriodDayCounter) {
749749

750750
indices_ = concreteLegData_->indices();
751751

@@ -765,6 +765,7 @@ void LegData::fromXML(XMLNode* node) {
765765
dayCounter_ = XMLUtils::getChildValue(node, "DayCounter"); // optional
766766
paymentConvention_ = XMLUtils::getChildValue(node, "PaymentConvention");
767767
paymentLag_ = XMLUtils::getChildValue(node, "PaymentLag");
768+
notionalPaymentLag_ = XMLUtils::getChildValue(node, "NotionalPaymentLag");
768769
paymentCalendar_ = XMLUtils::getChildValue(node, "PaymentCalendar", false);
769770
// if not given, default of getChildValueAsBool is true, which fits our needs here
770771
notionals_ = XMLUtils::getChildrenValuesWithAttributes<Real>(node, "Notionals", "Notional", "startDate",
@@ -863,6 +864,8 @@ XMLNode* LegData::toXML(XMLDocument& doc) const {
863864
XMLUtils::addChild(doc, node, "PaymentConvention", paymentConvention_);
864865
if (!paymentLag_.empty())
865866
XMLUtils::addChild(doc, node, "PaymentLag", paymentLag_);
867+
if (!notionalPaymentLag_.empty())
868+
XMLUtils::addChild(doc, node, "NotionalPaymentLag", notionalPaymentLag_);
866869
if (!paymentCalendar_.empty())
867870
XMLUtils::addChild(doc, node, "PaymentCalendar", paymentCalendar_);
868871
if (dayCounter_ != "")
@@ -1562,7 +1565,7 @@ Leg makeBMALeg(const LegData& data, const QuantLib::ext::shared_ptr<QuantExt::BM
15621565
}
15631566

15641567
Leg makeNotionalLeg(const Leg& refLeg, const bool initNomFlow, const bool finalNomFlow, const bool amortNomFlow,
1565-
const Natural paymentLag, const BusinessDayConvention paymentConvention,
1568+
const Natural notionalPaymentLag, const BusinessDayConvention paymentConvention,
15661569
const Calendar paymentCalendar, const bool excludeIndexing) {
15671570

15681571
// Assumption - Cashflows on Input Leg are all coupons
@@ -1575,7 +1578,7 @@ Leg makeNotionalLeg(const Leg& refLeg, const bool initNomFlow, const bool finalN
15751578
QL_REQUIRE(coupon, "makeNotionalLeg does not support non-coupon legs");
15761579
double initFlowAmt = (excludeIndexing ? unpackIndexedCoupon(coupon) : coupon)->nominal();
15771580
Date initDate = coupon->accrualStartDate();
1578-
initDate = paymentCalendar.advance(initDate, paymentLag, Days, paymentConvention);
1581+
initDate = paymentCalendar.advance(initDate, notionalPaymentLag, Days, paymentConvention);
15791582
if (initFlowAmt != 0)
15801583
leg.push_back(QuantLib::ext::shared_ptr<CashFlow>(new SimpleCashFlow(-initFlowAmt, initDate)));
15811584
}
@@ -1588,7 +1591,7 @@ Leg makeNotionalLeg(const Leg& refLeg, const bool initNomFlow, const bool finalN
15881591
auto coupon2 = QuantLib::ext::dynamic_pointer_cast<QuantLib::Coupon>(refLeg[i - 1]);
15891592
QL_REQUIRE(coupon, "makeNotionalLeg does not support non-coupon legs");
15901593
Date flowDate = coupon->accrualStartDate();
1591-
flowDate = paymentCalendar.advance(flowDate, paymentLag, Days, paymentConvention);
1594+
flowDate = paymentCalendar.advance(flowDate, notionalPaymentLag, Days, paymentConvention);
15921595
Real initNom = (excludeIndexing ? unpackIndexedCoupon(coupon2) : coupon2)->nominal();
15931596
Real newNom = (excludeIndexing ? unpackIndexedCoupon(coupon) : coupon)->nominal();
15941597
Real flow = initNom - newNom;
@@ -1603,7 +1606,7 @@ Leg makeNotionalLeg(const Leg& refLeg, const bool initNomFlow, const bool finalN
16031606
QL_REQUIRE(coupon, "makeNotionalLeg does not support non-coupon legs");
16041607
double finalNomFlow = (excludeIndexing ? unpackIndexedCoupon(coupon) : coupon)->nominal();
16051608
Date finalDate = coupon->accrualEndDate();
1606-
finalDate = paymentCalendar.advance(finalDate, paymentLag, Days, paymentConvention);
1609+
finalDate = paymentCalendar.advance(finalDate, notionalPaymentLag, Days, paymentConvention);
16071610
if (finalNomFlow != 0)
16081611
leg.push_back(QuantLib::ext::shared_ptr<CashFlow>(new SimpleCashFlow(finalNomFlow, finalDate)));
16091612
}
@@ -2736,49 +2739,58 @@ Leg buildNotionalLeg(const LegData& data, const Leg& leg, RequiredFixings& requi
27362739
auto fxIndex =
27372740
buildFxIndex(data.fxIndex(), data.currency(), data.foreignCurrency(), market, configuration, true);
27382741

2742+
PaymentLag notionalPayLag = parsePaymentLag(data.notionalPaymentLag());
2743+
Natural payLagInteger = boost::apply_visitor(PaymentLagInteger(), notionalPayLag);
2744+
const Calendar& payCalendar = parseCalendar(data.paymentCalendar());
2745+
const BusinessDayConvention& payConvention = parseBusinessDayConvention(data.paymentConvention());
2746+
27392747
Leg resettingLeg;
27402748
for (Size j = 0; j < leg.size(); j++) {
27412749

27422750
QuantLib::ext::shared_ptr<Coupon> c = QuantLib::ext::dynamic_pointer_cast<Coupon>(leg[j]);
27432751
QL_REQUIRE(c, "Expected each cashflow in FX resetting leg to be of type Coupon");
27442752

2753+
const Date& initFlowDate = payCalendar.advance(c->accrualStartDate(), payLagInteger, Days, payConvention);
2754+
const Date& finalFlowDate = payCalendar.advance(c->accrualEndDate(), payLagInteger, Days, payConvention);
2755+
27452756
// Build a pair of notional flows, one at the start and one at the end of the accrual period.
27462757
// They both have the same FX fixing date => same amount in this leg's currency.
27472758
QuantLib::ext::shared_ptr<CashFlow> outCf;
27482759
QuantLib::ext::shared_ptr<CashFlow> inCf;
27492760
Date fixingDate;
27502761
if (j == 0) {
2762+
27512763
// Two possibilities for first coupon:
27522764
// 1. we have not been given a domestic notional so it is an FX linked coupon
27532765
// 2. we have been given an explicit domestic notional so it is a simple cashflow
27542766
if (data.notionals().size() == 0) {
27552767
fixingDate = fxIndex->fixingDate(c->accrualStartDate());
27562768
if (data.notionalInitialExchange()) {
2757-
outCf = QuantLib::ext::make_shared<FXLinkedCashFlow>(c->accrualStartDate(), fixingDate,
2758-
-foreignNotional, fxIndex);
2769+
outCf = QuantLib::ext::make_shared<FXLinkedCashFlow>(initFlowDate, fixingDate, -foreignNotional,
2770+
fxIndex);
27592771
}
27602772
// if there is only one period we generate the cash flow at the period end
27612773
// only if there is a final notional exchange
27622774
if (leg.size() > 1 || data.notionalFinalExchange()) {
2763-
inCf = QuantLib::ext::make_shared<FXLinkedCashFlow>(c->accrualEndDate(), fixingDate, foreignNotional,
2764-
fxIndex);
2775+
inCf = QuantLib::ext::make_shared<FXLinkedCashFlow>(finalFlowDate, fixingDate, foreignNotional,
2776+
fxIndex);
27652777
}
27662778
} else {
27672779
if (data.notionalInitialExchange()) {
2768-
outCf = QuantLib::ext::make_shared<SimpleCashFlow>(-c->nominal(), c->accrualStartDate());
2780+
outCf = QuantLib::ext::make_shared<SimpleCashFlow>(-c->nominal(), initFlowDate);
27692781
}
27702782
if (leg.size() > 1 || data.notionalFinalExchange()) {
2771-
inCf = QuantLib::ext::make_shared<SimpleCashFlow>(c->nominal(), c->accrualEndDate());
2783+
inCf = QuantLib::ext::make_shared<SimpleCashFlow>(c->nominal(), finalFlowDate);
27722784
}
27732785
}
27742786
} else {
27752787
fixingDate = fxIndex->fixingDate(c->accrualStartDate());
27762788
outCf =
2777-
QuantLib::ext::make_shared<FXLinkedCashFlow>(c->accrualStartDate(), fixingDate, -foreignNotional, fxIndex);
2789+
QuantLib::ext::make_shared<FXLinkedCashFlow>(initFlowDate, fixingDate, -foreignNotional, fxIndex);
27782790
// we don't want a final one, unless there is notional exchange
27792791
if (j < leg.size() - 1 || data.notionalFinalExchange()) {
27802792
inCf =
2781-
QuantLib::ext::make_shared<FXLinkedCashFlow>(c->accrualEndDate(), fixingDate, foreignNotional, fxIndex);
2793+
QuantLib::ext::make_shared<FXLinkedCashFlow>(finalFlowDate, fixingDate, foreignNotional, fxIndex);
27822794
}
27832795
}
27842796

@@ -2806,11 +2818,11 @@ Leg buildNotionalLeg(const LegData& data, const Leg& leg, RequiredFixings& requi
28062818

28072819
// check for notional exchanges on non FX reseting trades
28082820

2809-
PaymentLag payLag = parsePaymentLag(data.paymentLag());
2810-
Natural payLagInteger = boost::apply_visitor(PaymentLagInteger(), payLag);
2821+
PaymentLag notionalPayLag = parsePaymentLag(data.notionalPaymentLag());
2822+
Natural notionalPayLagInteger = boost::apply_visitor(PaymentLagInteger(), notionalPayLag);
28112823

28122824
return makeNotionalLeg(leg, data.notionalInitialExchange(), data.notionalFinalExchange(),
2813-
data.notionalAmortizingExchange(), payLagInteger,
2825+
data.notionalAmortizingExchange(), notionalPayLagInteger,
28142826
parseBusinessDayConvention(data.paymentConvention()),
28152827
parseCalendar(data.paymentCalendar()), true);
28162828
} else {

OREData/ored/portfolio/legdata.hpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,8 @@ class LegData : public XMLSerializable {
855855
const bool notionalAmortizingExchange = false, const bool isNotResetXCCY = true,
856856
const string& foreignCurrency = "", const double foreignAmount = 0, const string& fxIndex = "",
857857
const std::vector<AmortizationData>& amortizationData = std::vector<AmortizationData>(),
858-
const string& paymentLag = "", const std::string& paymentCalendar = "",
858+
const string& paymentLag = "", const string& notionalPaymentLag = "",
859+
const std::string& paymentCalendar = "",
859860
const std::vector<std::string>& paymentDates = std::vector<std::string>(),
860861
const std::vector<Indexing>& indexing = {}, const bool indexingFromAssetLeg = false,
861862
const string& lastPeriodDayCounter = "");
@@ -883,6 +884,7 @@ class LegData : public XMLSerializable {
883884
double foreignAmount() const { return foreignAmount_; }
884885
const string& fxIndex() const { return fxIndex_; }
885886
const string& paymentLag() const { return paymentLag_; }
887+
const string& notionalPaymentLag() const { return notionalPaymentLag_; }
886888
const std::vector<AmortizationData>& amortizationData() const { return amortizationData_; }
887889
const std::string& paymentCalendar() const { return paymentCalendar_; }
888890
const string& legType() const { return concreteLegData_->legType(); }
@@ -941,7 +943,7 @@ class LegData : public XMLSerializable {
941943
double foreignAmount_ = 0.0;
942944
string fxIndex_;
943945
std::vector<AmortizationData> amortizationData_;
944-
string paymentLag_;
946+
string paymentLag_, notionalPaymentLag_;
945947
std::string paymentCalendar_;
946948
std::vector<std::string> paymentDates_;
947949
std::vector<Indexing> indexing_;
@@ -966,7 +968,7 @@ Leg makeBMALeg(const LegData& data, const QuantLib::ext::shared_ptr<QuantExt::BM
966968
const QuantLib::Date& openEndDateReplacement = Null<Date>());
967969
Leg makeSimpleLeg(const LegData& data);
968970
Leg makeNotionalLeg(const Leg& refLeg, const bool initNomFlow, const bool finalNomFlow, const bool amortNomFlow,
969-
const QuantLib::Natural paymentLag, const BusinessDayConvention paymentConvention,
971+
const QuantLib::Natural notionalPaymentLag, const BusinessDayConvention paymentConvention,
970972
const Calendar paymentCalendar, const bool excludeIndexing = true);
971973
Leg makeCPILeg(const LegData& data, const QuantLib::ext::shared_ptr<ZeroInflationIndex>& index,
972974
const QuantLib::ext::shared_ptr<EngineFactory>& engineFactory,

xsd/instruments.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,7 @@
795795
<xs:element type="extendedCurrencyCode" name="Currency" minOccurs="0"/>
796796
<xs:element type="businessDayConvention" name="PaymentConvention" minOccurs="0"/>
797797
<xs:element type="paymentLag" name="PaymentLag" minOccurs="0"/>
798+
<xs:element type="xs:integer" name="NotionalPaymentLag" minOccurs="0"/>
798799
<xs:element type="xs:string" name="PaymentCalendar" minOccurs="0"/>
799800
<xs:element type="dayCounter" name="DayCounter" minOccurs="0"/>
800801
<xs:element name="Amortizations" minOccurs="0" maxOccurs="1">

0 commit comments

Comments
 (0)