Skip to content

Commit ffe605e

Browse files
pcaspersjenkins
authored andcommitted
Merge remote-tracking branch 'origin/master' into QPR-11232-payment-dates
1 parent df0674f commit ffe605e

7 files changed

Lines changed: 137 additions & 23 deletions

File tree

Docker/.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
TAG=latest
2-
QL_TAG=1.29_d8a7100
2+
QL_TAG=1.29_bb3124b
33
BOOST_TAG=1.72.0
44
BOOST_DIR=1_72_0
55
NUM_CORES=16

OREData/ored/portfolio/legdata.cpp

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,18 @@ Leg makeFixedLeg(const LegData& data, const QuantLib::Date& openEndDateReplaceme
893893
Schedule schedule = makeSchedule(data.schedule(), openEndDateReplacement);
894894
QL_REQUIRE(schedule.size() > 1, "FixedLeg:Schedule must have at least 2 dates.");
895895

896+
// Get explicit payment dates which in most cases should be empty
897+
vector<Date> paymentDates;
898+
if (!data.paymentDates().empty()) {
899+
BusinessDayConvention paymentDatesConvention =
900+
data.paymentConvention().empty() ? Unadjusted : parseBusinessDayConvention(data.paymentConvention());
901+
Calendar paymentDatesCalendar =
902+
data.paymentCalendar().empty() ? NullCalendar() : parseCalendar(data.paymentCalendar());
903+
paymentDates = parseVectorOfValues<Date>(data.paymentDates(), &parseDate);
904+
for (Size i = 0; i < paymentDates.size(); i++)
905+
paymentDates[i] = paymentDatesCalendar.adjust(paymentDates[i], paymentDatesConvention);
906+
}
907+
896908
DayCounter dc = parseDayCounter(data.dayCounter());
897909
BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());
898910

@@ -912,9 +924,9 @@ Leg makeFixedLeg(const LegData& data, const QuantLib::Date& openEndDateReplaceme
912924
.withPaymentAdjustment(bdc)
913925
.withPaymentLag(boost::apply_visitor(PaymentLagInteger(), paymentLag))
914926
.withPaymentCalendar(paymentCalendar)
915-
.withLastPeriodDayCounter(data.lastPeriodDayCounter().empty()
916-
? DayCounter()
917-
: parseDayCounter(data.lastPeriodDayCounter()));
927+
.withLastPeriodDayCounter(
928+
data.lastPeriodDayCounter().empty() ? DayCounter() : parseDayCounter(data.lastPeriodDayCounter()))
929+
.withPaymentDates(paymentDates);
918930
return leg;
919931

920932
}
@@ -977,6 +989,19 @@ Leg makeIborLeg(const LegData& data, const boost::shared_ptr<IborIndex>& index,
977989
QL_REQUIRE(floatData, "Wrong LegType, expected Floating, got " << data.legType());
978990

979991
Schedule schedule = makeSchedule(data.schedule(), openEndDateReplacement);
992+
993+
// Get explicit payment dates which in most cases should be empty
994+
vector<Date> paymentDates;
995+
if (!data.paymentDates().empty()) {
996+
BusinessDayConvention paymentDatesConvention =
997+
data.paymentConvention().empty() ? Unadjusted : parseBusinessDayConvention(data.paymentConvention());
998+
Calendar paymentDatesCalendar =
999+
data.paymentCalendar().empty() ? NullCalendar() : parseCalendar(data.paymentCalendar());
1000+
paymentDates = parseVectorOfValues<Date>(data.paymentDates(), &parseDate);
1001+
for (Size i = 0; i < paymentDates.size(); i++)
1002+
paymentDates[i] = paymentDatesCalendar.adjust(paymentDates[i], paymentDatesConvention);
1003+
}
1004+
9801005
Calendar paymentCalendar;
9811006
if (data.paymentCalendar().empty())
9821007
paymentCalendar = schedule.calendar();
@@ -1079,7 +1104,8 @@ Leg makeIborLeg(const LegData& data, const boost::shared_ptr<IborIndex>& index,
10791104
.withFixingDays(fixingDays)
10801105
.inArrears(isInArrears)
10811106
.withGearings(gearings)
1082-
.withPaymentLag(boost::apply_visitor(PaymentLagInteger(), paymentLag));
1107+
.withPaymentLag(boost::apply_visitor(PaymentLagInteger(), paymentLag))
1108+
.withPaymentDates(paymentDates);
10831109

10841110
// If no caps or floors or in arrears fixing, return the leg
10851111
if (!hasCapsFloors && !isInArrears)
@@ -1137,7 +1163,18 @@ Leg makeOISLeg(const LegData& data, const boost::shared_ptr<OvernightIndex>& ind
11371163
DayCounter dc = parseDayCounter(data.dayCounter());
11381164
BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());
11391165
PaymentLag paymentLag = parsePaymentLag(data.paymentLag());
1140-
Calendar paymentCalendar;
1166+
1167+
// Get explicit payment dates which in most cases should be empty
1168+
vector<Date> paymentDates;
1169+
if (!data.paymentDates().empty()) {
1170+
BusinessDayConvention paymentDatesConvention =
1171+
data.paymentConvention().empty() ? Unadjusted : parseBusinessDayConvention(data.paymentConvention());
1172+
Calendar paymentDatesCalendar =
1173+
data.paymentCalendar().empty() ? NullCalendar() : parseCalendar(data.paymentCalendar());
1174+
paymentDates = parseVectorOfValues<Date>(data.paymentDates(), &parseDate);
1175+
for (Size i = 0; i < paymentDates.size(); i++)
1176+
paymentDates[i] = paymentDatesCalendar.adjust(paymentDates[i], paymentDatesConvention);
1177+
}
11411178

11421179
// try to set the rate computation period based on the schedule tenor
11431180
Period rateComputationPeriod = 0 * Days;
@@ -1146,6 +1183,7 @@ Leg makeOISLeg(const LegData& data, const boost::shared_ptr<OvernightIndex>& ind
11461183
else if (!tmp.dates().empty() && !tmp.dates().front().tenor().empty())
11471184
rateComputationPeriod = parsePeriod(tmp.dates().front().tenor());
11481185

1186+
Calendar paymentCalendar;
11491187
if (data.paymentCalendar().empty())
11501188
paymentCalendar = index->fixingCalendar();
11511189
else
@@ -1201,10 +1239,25 @@ Leg makeOISLeg(const LegData& data, const boost::shared_ptr<OvernightIndex>& ind
12011239
.withLocalCapFloor(floatData->localCapFloor())
12021240
.withAverageONIndexedCouponPricer(couponPricer)
12031241
.withCapFlooredAverageONIndexedCouponPricer(cfCouponPricer)
1204-
.withTelescopicValueDates(floatData->telescopicValueDates());
1242+
.withTelescopicValueDates(floatData->telescopicValueDates())
1243+
.withPaymentDates(paymentDates);
12051244
return leg;
12061245

12071246
} else {
1247+
1248+
boost::shared_ptr<QuantExt::OvernightIndexedCouponPricer> couponPricer =
1249+
boost::make_shared<QuantExt::OvernightIndexedCouponPricer>();
1250+
1251+
boost::shared_ptr<QuantExt::CappedFlooredOvernightIndexedCouponPricer> cfCouponPricer;
1252+
if (attachPricer && (floatData->caps().size() > 0 || floatData->floors().size() > 0)) {
1253+
auto builder = boost::dynamic_pointer_cast<CapFlooredOvernightIndexedCouponLegEngineBuilder>(
1254+
engineFactory->builder("CapFlooredOvernightIndexedCouponLeg"));
1255+
QL_REQUIRE(builder, "No builder found for CapFlooredOvernightIndexedCouponLeg");
1256+
cfCouponPricer = boost::dynamic_pointer_cast<QuantExt::CappedFlooredOvernightIndexedCouponPricer>(
1257+
builder->engine(IndexNameTranslator::instance().oreName(index->name()), rateComputationPeriod));
1258+
QL_REQUIRE(cfCouponPricer, "internal error, could not cast to CapFlooredAverageONIndexedCouponPricer");
1259+
}
1260+
12081261
Leg leg = QuantExt::OvernightLeg(schedule, index)
12091262
.withNotionals(notionals)
12101263
.withSpreads(spreads)
@@ -1228,23 +1281,16 @@ Leg makeOISLeg(const LegData& data, const boost::shared_ptr<OvernightIndex>& ind
12281281
schedule, Null<Real>()))
12291282
.withNakedOption(floatData->nakedOption())
12301283
.withLocalCapFloor(floatData->localCapFloor())
1231-
.withTelescopicValueDates(floatData->telescopicValueDates());
1284+
.withOvernightIndexedCouponPricer(couponPricer)
1285+
.withCapFlooredOvernightIndexedCouponPricer(cfCouponPricer)
1286+
.withTelescopicValueDates(floatData->telescopicValueDates())
1287+
.withPaymentDates(paymentDates);
12321288

12331289
// If the overnight index is BRL CDI, we need a special coupon pricer
12341290
boost::shared_ptr<BRLCdi> brlCdiIndex = boost::dynamic_pointer_cast<BRLCdi>(index);
12351291
if (brlCdiIndex)
12361292
QuantExt::setCouponPricer(leg, boost::make_shared<BRLCdiCouponPricer>());
12371293

1238-
// if we have caps / floors, we need a pricer for that
1239-
if (attachPricer && (floatData->caps().size() > 0 || floatData->floors().size() > 0)) {
1240-
auto builder = boost::dynamic_pointer_cast<CapFlooredOvernightIndexedCouponLegEngineBuilder>(
1241-
engineFactory->builder("CapFlooredOvernightIndexedCouponLeg"));
1242-
QL_REQUIRE(builder, "No builder found for CapFlooredOvernightIndexedCouponLeg");
1243-
auto pricer =
1244-
builder->engine(IndexNameTranslator::instance().oreName(index->name()), rateComputationPeriod);
1245-
QuantExt::setCouponPricer(leg, pricer);
1246-
}
1247-
12481294
return leg;
12491295
}
12501296
}

QuantExt/qle/cashflows/averageonindexedcoupon.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,11 @@ AverageONLeg& AverageONLeg::withLastRecentPeriodCalendar(const Calendar& lastRec
416416
return *this;
417417
}
418418

419+
AverageONLeg& AverageONLeg::withPaymentDates(const std::vector<Date>& paymentDates) {
420+
paymentDates_ = paymentDates;
421+
return *this;
422+
}
423+
419424
AverageONLeg&
420425
AverageONLeg::withAverageONIndexedCouponPricer(const boost::shared_ptr<AverageONIndexedCouponPricer>& couponPricer) {
421426
couponPricer_ = couponPricer;
@@ -448,10 +453,25 @@ AverageONLeg::operator Leg() const {
448453
Date paymentDate;
449454

450455
Size n = schedule_.size() - 1;
456+
457+
// Initial consistency checks
458+
if (!paymentDates_.empty()) {
459+
QL_REQUIRE(paymentDates_.size() == n, "Expected the number of explicit payment dates ("
460+
<< paymentDates_.size()
461+
<< ") to equal the number of calculation periods ("
462+
<< n << ")");
463+
}
464+
451465
for (Size i = 0; i < n; ++i) {
452466
refStart = start = schedule_.date(i);
453467
refEnd = end = schedule_.date(i + 1);
454-
paymentDate = paymentCalendar.advance(end, paymentLag_, Days, paymentAdjustment_);
468+
469+
// If explicit payment dates provided, use them.
470+
if (!paymentDates_.empty()) {
471+
paymentDate = paymentDates_[i];
472+
} else {
473+
paymentDate = paymentCalendar.advance(end, paymentLag_, Days, paymentAdjustment_);
474+
}
455475

456476
// determine refStart and refEnd
457477

QuantExt/qle/cashflows/averageonindexedcoupon.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ class AverageONLeg {
203203
AverageONLeg& withInArrears(const bool inArrears);
204204
AverageONLeg& withLastRecentPeriod(const boost::optional<Period>& lastRecentPeriod);
205205
AverageONLeg& withLastRecentPeriodCalendar(const Calendar& lastRecentPeriodCalendar);
206+
AverageONLeg& withPaymentDates(const std::vector<QuantLib::Date>& paymentDates);
206207
AverageONLeg& withAverageONIndexedCouponPricer(const boost::shared_ptr<AverageONIndexedCouponPricer>& couponPricer);
207208
AverageONLeg& withCapFlooredAverageONIndexedCouponPricer(
208209
const boost::shared_ptr<CapFlooredAverageONIndexedCouponPricer>& couponPricer);
@@ -229,6 +230,7 @@ class AverageONLeg {
229230
bool inArrears_;
230231
boost::optional<Period> lastRecentPeriod_;
231232
Calendar lastRecentPeriodCalendar_;
233+
std::vector<QuantLib::Date> paymentDates_;
232234
boost::shared_ptr<AverageONIndexedCouponPricer> couponPricer_;
233235
boost::shared_ptr<CapFlooredAverageONIndexedCouponPricer> capFlooredCouponPricer_;
234236
};

QuantExt/qle/cashflows/overnightindexedcoupon.cpp

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,23 @@ OvernightLeg& OvernightLeg::withLastRecentPeriodCalendar(const Calendar& lastRec
590590
return *this;
591591
}
592592

593+
OvernightLeg& OvernightLeg::withPaymentDates(const std::vector<Date>& paymentDates) {
594+
paymentDates_ = paymentDates;
595+
return *this;
596+
}
597+
598+
OvernightLeg&
599+
OvernightLeg::withOvernightIndexedCouponPricer(const boost::shared_ptr<OvernightIndexedCouponPricer>& couponPricer) {
600+
couponPricer_ = couponPricer;
601+
return *this;
602+
}
603+
604+
OvernightLeg& OvernightLeg::withCapFlooredOvernightIndexedCouponPricer(
605+
const boost::shared_ptr<CappedFlooredOvernightIndexedCouponPricer>& couponPricer) {
606+
capFlooredCouponPricer_ = couponPricer;
607+
return *this;
608+
}
609+
593610
OvernightLeg::operator Leg() const {
594611

595612
QL_REQUIRE(!notionals_.empty(), "no notional given for compounding overnight leg");
@@ -610,10 +627,25 @@ OvernightLeg::operator Leg() const {
610627
Date paymentDate;
611628

612629
Size n = schedule_.size() - 1;
630+
631+
// Initial consistency checks
632+
if (!paymentDates_.empty()) {
633+
QL_REQUIRE(paymentDates_.size() == n, "Expected the number of explicit payment dates ("
634+
<< paymentDates_.size()
635+
<< ") to equal the number of calculation periods ("
636+
<< n << ")");
637+
}
638+
613639
for (Size i = 0; i < n; ++i) {
614640
refStart = start = schedule_.date(i);
615641
refEnd = end = schedule_.date(i + 1);
616-
paymentDate = paymentCalendar.advance(end, paymentLag_, Days, paymentAdjustment_);
642+
643+
// If explicit payment dates provided, use them.
644+
if (!paymentDates_.empty()) {
645+
paymentDate = paymentDates_[i];
646+
} else {
647+
paymentDate = paymentCalendar.advance(end, paymentLag_, Days, paymentAdjustment_);
648+
}
617649

618650
// determine refStart and refEnd
619651

@@ -667,13 +699,20 @@ OvernightLeg::operator Leg() const {
667699
detail::get(gearings_, i, 1.0), detail::get(spreads_, i, 0.0), refStart, refEnd, paymentDayCounter_,
668700
telescopicValueDates_, includeSpread_, lookback_, rateCutoff_, fixingDays_, rateComputationStartDate,
669701
rateComputationEndDate);
702+
if (couponPricer_) {
703+
cpn->setPricer(couponPricer_);
704+
}
670705
Real cap = detail::get(caps_, i, Null<Real>());
671706
Real floor = detail::get(floors_, i, Null<Real>());
672707
if (cap == Null<Real>() && floor == Null<Real>()) {
673708
cashflows.push_back(cpn);
674709
} else {
675-
cashflows.push_back(ext::make_shared<CappedFlooredOvernightIndexedCoupon>(cpn, cap, floor, nakedOption_,
676-
localCapFloor_));
710+
auto cfCpn = ext::make_shared<CappedFlooredOvernightIndexedCoupon>(cpn, cap, floor, nakedOption_,
711+
localCapFloor_);
712+
if (capFlooredCouponPricer_) {
713+
cfCpn->setPricer(capFlooredCouponPricer_);
714+
}
715+
cashflows.push_back(cfCpn);
677716
}
678717
}
679718
}

QuantExt/qle/cashflows/overnightindexedcoupon.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ class OvernightLeg {
253253
OvernightLeg& withInArrears(const bool inArrears);
254254
OvernightLeg& withLastRecentPeriod(const boost::optional<Period>& lastRecentPeriod);
255255
OvernightLeg& withLastRecentPeriodCalendar(const Calendar& lastRecentPeriodCalendar);
256+
OvernightLeg& withOvernightIndexedCouponPricer(const boost::shared_ptr<OvernightIndexedCouponPricer>& couponPricer);
257+
OvernightLeg& withPaymentDates(const std::vector<Date>& paymentDates);
258+
OvernightLeg& withCapFlooredOvernightIndexedCouponPricer(
259+
const boost::shared_ptr<CappedFlooredOvernightIndexedCouponPricer>& couponPricer);
256260
operator Leg() const;
257261

258262
private:
@@ -276,6 +280,9 @@ class OvernightLeg {
276280
bool inArrears_;
277281
boost::optional<Period> lastRecentPeriod_;
278282
Calendar lastRecentPeriodCalendar_;
283+
std::vector<QuantLib::Date> paymentDates_;
284+
boost::shared_ptr<OvernightIndexedCouponPricer> couponPricer_;
285+
boost::shared_ptr<CappedFlooredOvernightIndexedCouponPricer> capFlooredCouponPricer_;
279286
};
280287

281288
} // namespace QuantExt

QuantLib

Submodule QuantLib updated from d8a7100 to bb3124b

0 commit comments

Comments
 (0)