Skip to content

Commit

Permalink
Add new constructor and new leg builder to sub-periods coupon
Browse files Browse the repository at this point in the history
  • Loading branch information
lballabio committed Oct 14, 2024
1 parent 9a3469a commit 2187fb2
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 17 deletions.
192 changes: 192 additions & 0 deletions ql/cashflows/subperiodcoupon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,43 @@

namespace QuantLib {

SubPeriodsCoupon::SubPeriodsCoupon(const Date& paymentDate,
Real nominal,
const Schedule& resetSchedule,
Natural fixingDays,
const ext::shared_ptr<IborIndex>& index,
Real gearing,
Rate couponSpread,
Rate rateSpread,
const Date& refPeriodStart,
const Date& refPeriodEnd,
const DayCounter& dayCounter,
const Date& exCouponDate)
: FloatingRateCoupon(paymentDate, nominal,
resetSchedule.front(), resetSchedule.back(),
fixingDays, index, gearing, couponSpread,
refPeriodStart, refPeriodEnd, dayCounter,
false, exCouponDate),
rateSpread_(rateSpread) {
valueDates_ = resetSchedule.dates();

// fixing dates
n_ = valueDates_.size() - 1;
if (fixingDays_ == 0) {
fixingDates_ = std::vector<Date>(valueDates_.begin(), valueDates_.end() - 1);
} else {
fixingDates_.resize(n_);
for (Size i = 0; i < n_; ++i)
fixingDates_[i] = fixingDate(valueDates_[i]);
}

// accrual times of sub-periods
dt_.resize(n_);
const DayCounter& dc = index->dayCounter();
for (Size i = 0; i < n_; ++i)
dt_[i] = dc.yearFraction(valueDates_[i], valueDates_[i + 1]);
}

SubPeriodsCoupon::SubPeriodsCoupon(const Date& paymentDate,
Real nominal,
const Date& startDate,
Expand Down Expand Up @@ -155,6 +192,160 @@ namespace QuantLib {
return coupon_->gearing() * rate + coupon_->spread();
}



MultipleResetsLeg::MultipleResetsLeg(Schedule schedule,
ext::shared_ptr<IborIndex> index,
Size resetsPerCoupon)
: schedule_(std::move(schedule)), index_(std::move(index)), resetsPerCoupon_(resetsPerCoupon),
paymentCalendar_(schedule_.calendar()) {
QL_REQUIRE(index_, "no index provided");
QL_REQUIRE(!schedule_.empty(), "empty schedule provided");
QL_REQUIRE((schedule_.size() - 1) % resetsPerCoupon_ == 0,
"number of resets per coupon does not divide exactly number of periods in schedule");
}

MultipleResetsLeg& MultipleResetsLeg::withNotionals(Real notional) {
notionals_ = std::vector<Real>(1, notional);
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withNotionals(const std::vector<Real>& notionals) {
notionals_ = notionals;
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withPaymentDayCounter(const DayCounter& dc) {
paymentDayCounter_ = dc;
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withPaymentAdjustment(BusinessDayConvention convention) {
paymentAdjustment_ = convention;
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withPaymentCalendar(const Calendar& cal) {
paymentCalendar_ = cal;
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withPaymentLag(Integer lag) {
paymentLag_ = lag;
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withFixingDays(Natural fixingDays) {
fixingDays_ = std::vector<Natural>(1, fixingDays);
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withFixingDays(const std::vector<Natural>& fixingDays) {
fixingDays_ = fixingDays;
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withGearings(Real gearing) {
gearings_ = std::vector<Real>(1, gearing);
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withGearings(const std::vector<Real>& gearings) {
gearings_ = gearings;
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withCouponSpreads(Spread spread) {
couponSpreads_ = std::vector<Spread>(1, spread);
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withCouponSpreads(const std::vector<Spread>& spreads) {
couponSpreads_ = spreads;
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withRateSpreads(Spread spread) {
rateSpreads_ = std::vector<Spread>(1, spread);
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withRateSpreads(const std::vector<Spread>& spreads) {
rateSpreads_ = spreads;
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withAveragingMethod(RateAveraging::Type averagingMethod) {
averagingMethod_ = averagingMethod;
return *this;
}

MultipleResetsLeg& MultipleResetsLeg::withExCouponPeriod(const Period& period,
const Calendar& cal,
BusinessDayConvention convention,
bool endOfMonth) {
exCouponPeriod_ = period;
exCouponCalendar_ = cal;
exCouponAdjustment_ = convention;
exCouponEndOfMonth_ = endOfMonth;
return *this;
}

MultipleResetsLeg::operator Leg() const {
Leg cashflows;
Calendar calendar = schedule_.calendar();

Size n = (schedule_.size() - 1) / resetsPerCoupon_;
QL_REQUIRE(!notionals_.empty(), "no notional given");
QL_REQUIRE(notionals_.size() <= n,
"too many nominals (" << notionals_.size() << "), only " << n << " required");
QL_REQUIRE(gearings_.size() <= n,
"too many gearings (" << gearings_.size() << "), only " << n << " required");
QL_REQUIRE(couponSpreads_.size() <= n,
"too many coupon spreads (" << couponSpreads_.size() << "), only " << n << " required");
QL_REQUIRE(rateSpreads_.size() <= n,
"too many rate spreads (" << rateSpreads_.size() << "), only " << n << " required");
QL_REQUIRE(fixingDays_.size() <= n,
"too many fixing days (" << fixingDays_.size() << "), only " << n << " required");

for (Size i = 0; i < n; ++i) {
Date start = schedule_.date(i * resetsPerCoupon_);
Date end = schedule_.date((i + 1) * resetsPerCoupon_);
auto subSchedule = schedule_.after(start).until(end);
Date paymentDate = paymentCalendar_.advance(end, paymentLag_, Days, paymentAdjustment_);
Date exCouponDate;
if (exCouponPeriod_ != Period()) {
if (exCouponCalendar_.empty()) {
exCouponDate = calendar.advance(paymentDate, -exCouponPeriod_,
exCouponAdjustment_, exCouponEndOfMonth_);
} else {
exCouponDate = exCouponCalendar_.advance(
paymentDate, -exCouponPeriod_, exCouponAdjustment_, exCouponEndOfMonth_);
}
}

cashflows.push_back(ext::make_shared<SubPeriodsCoupon>(
paymentDate, detail::get(notionals_, i, notionals_.back()), subSchedule,
detail::get(fixingDays_, i, index_->fixingDays()), index_,
detail::get(gearings_, i, 1.0), detail::get(couponSpreads_, i, 0.0),
detail::get(rateSpreads_, i, 0.0), start, end, paymentDayCounter_,
exCouponDate));
}

switch (averagingMethod_) {
case RateAveraging::Simple:
setCouponPricer(cashflows, ext::make_shared<AveragingRatePricer>());
break;
case RateAveraging::Compound:
setCouponPricer(cashflows, ext::make_shared<CompoundingRatePricer>());
break;
default:
QL_FAIL("unknown compounding convention (" << Integer(averagingMethod_) << ")");
}
return cashflows;
}


SubPeriodsLeg::SubPeriodsLeg(Schedule schedule, ext::shared_ptr<IborIndex> i)
: schedule_(std::move(schedule)), index_(std::move(i)), paymentCalendar_(schedule_.calendar()) {
QL_REQUIRE(index_, "no index provided");
Expand Down Expand Up @@ -305,4 +496,5 @@ namespace QuantLib {
}
return cashflows;
}

}
98 changes: 83 additions & 15 deletions ql/cashflows/subperiodcoupon.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,44 @@ namespace QuantLib {

class IborIndex;

//! sub-periods coupon
/*! %Coupon paying the interest, depending on the averaging convention,
due to possible multiple fixing resets in one accrual period.
//! multiple-reset coupon
/*! %Coupon paying a rate calculated by compounding or averaging
multiple fixings during its accrual period.
*/
class SubPeriodsCoupon: public FloatingRateCoupon {
public:
// The index object passed in has a tenor significantly less than the
// start/end dates.
// Thus endDate-startDate may equal 3M
// The Tenor used within the index object should be 1M for
// averaging/compounding across three coupons within the
// coupon period.
/*! \param resetSchedule the schedule for the multiple resets. The first and last
dates are also the start and end dates of the coupon.
Each period specified by the schedule is the underlying
period for one fixing; the corresponding fixing date is
the passed number of fixing days before the start of
the period.
\param couponSpread an optional spread added to the final coupon rate.
\param rateSpread an optional spread added to each of the underlying fixings.
\param gearing an optional multiplier for the final coupon rate.
*/
SubPeriodsCoupon(const Date& paymentDate,
Real nominal,
const Schedule& resetSchedule,
Natural fixingDays,
const ext::shared_ptr<IborIndex>& index,
Real gearing = 1.0,
Rate couponSpread = 0.0,
Rate rateSpread = 0.0,
const Date& refPeriodStart = Date(),
const Date& refPeriodEnd = Date(),
const DayCounter& dayCounter = DayCounter(),
const Date& exCouponDate = Date());

SubPeriodsCoupon(const Date& paymentDate,
Real nominal,
const Date& startDate,
const Date& endDate,
Natural fixingDays,
const ext::shared_ptr<IborIndex>& index,
Real gearing = 1.0,
Rate couponSpread = 0.0, // Spread added to the computed
// averaging/compounding rate.
Rate rateSpread = 0.0, // Spread to be added onto each
// fixing within the
// averaging/compounding calculation
Rate couponSpread = 0.0,
Rate rateSpread = 0.0,
const Date& refPeriodStart = Date(),
const Date& refPeriodEnd = Date(),
const DayCounter& dayCounter = DayCounter(),
Expand Down Expand Up @@ -117,7 +131,61 @@ namespace QuantLib {
Real swapletRate() const override;
};

//! helper class building a sequence of overnight coupons

//! helper class building a sequence of multiple-reset coupons
class MultipleResetsLeg {
public:
/*! \param fullResetSchedule the full schedule specifying reset periods for all coupons.
\param index the index whose fixings will be used; it should have the
same tenor as the resets.
\param resetsPerCoupon the number of resets for each coupon; the number of periods
in the schedule should be divided exactly by this number.
*/
MultipleResetsLeg(Schedule fullResetSchedule,
ext::shared_ptr<IborIndex> index,
Size resetsPerCoupon);
MultipleResetsLeg& withNotionals(Real notional);
MultipleResetsLeg& withNotionals(const std::vector<Real>& notionals);
MultipleResetsLeg& withPaymentDayCounter(const DayCounter&);
MultipleResetsLeg& withPaymentAdjustment(BusinessDayConvention);
MultipleResetsLeg& withPaymentCalendar(const Calendar&);
MultipleResetsLeg& withPaymentLag(Integer lag);
MultipleResetsLeg& withFixingDays(Natural fixingDays);
MultipleResetsLeg& withFixingDays(const std::vector<Natural>& fixingDays);
MultipleResetsLeg& withGearings(Real gearing);
MultipleResetsLeg& withGearings(const std::vector<Real>& gearings);
MultipleResetsLeg& withCouponSpreads(Spread spread);
MultipleResetsLeg& withCouponSpreads(const std::vector<Spread>& spreads);
MultipleResetsLeg& withRateSpreads(Spread spread);
MultipleResetsLeg& withRateSpreads(const std::vector<Spread>& spreads);
MultipleResetsLeg& withExCouponPeriod(const Period&,
const Calendar&,
BusinessDayConvention,
bool endOfMonth = false);
MultipleResetsLeg& withAveragingMethod(RateAveraging::Type averagingMethod);
operator Leg() const;

private:
Schedule schedule_;
ext::shared_ptr<IborIndex> index_;
Size resetsPerCoupon_;
std::vector<Real> notionals_;
DayCounter paymentDayCounter_;
Calendar paymentCalendar_;
BusinessDayConvention paymentAdjustment_ = Following;
Integer paymentLag_ = 0;
std::vector<Natural> fixingDays_;
std::vector<Real> gearings_;
std::vector<Spread> couponSpreads_;
std::vector<Spread> rateSpreads_;
RateAveraging::Type averagingMethod_ = RateAveraging::Compound;
Period exCouponPeriod_;
Calendar exCouponCalendar_;
BusinessDayConvention exCouponAdjustment_ = Unadjusted;
bool exCouponEndOfMonth_ = false;
};


class SubPeriodsLeg {
public:
SubPeriodsLeg(Schedule schedule, ext::shared_ptr<IborIndex> index);
Expand Down
26 changes: 24 additions & 2 deletions test-suite/subperiodcoupons.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,7 @@ BOOST_AUTO_TEST_CASE(testExCouponCashFlow) {
}

BOOST_AUTO_TEST_CASE(testSubPeriodsLegCashFlows) {
BOOST_TEST_MESSAGE(
"Testing sub-periods leg replication...");
BOOST_TEST_MESSAGE("Testing sub-periods leg replication...");

testSubPeriodsLegReplication(RateAveraging::Compound);
testSubPeriodsLegReplication(RateAveraging::Simple);
Expand Down Expand Up @@ -381,6 +380,29 @@ BOOST_AUTO_TEST_CASE(testSubPeriodsLegConsistencyChecks) {
Error);
}

BOOST_AUTO_TEST_CASE(testSubPeriodsLegRegression) {
BOOST_TEST_MESSAGE("Testing number of fixing dates in sub-periods coupons...");

Schedule schedule = MakeSchedule()
.from({1, August, 2024})
.to({1, August, 2025})
.withFrequency(Monthly)
.withCalendar(TARGET());

Size resetsPerCoupon = 3;
Leg leg = MultipleResetsLeg(schedule, ext::make_shared<Euribor1M>(), resetsPerCoupon)
.withNotionals(100.0)
.withAveragingMethod(RateAveraging::Compound);

for (const auto& cf : leg) {
auto c = ext::dynamic_pointer_cast<SubPeriodsCoupon>(cf);
if (c->fixingDates().size() != 3)
BOOST_ERROR("Unexpected number of fixing dates (" << c->fixingDates().size() << ") "
"in coupon paying on " << c->date());
}
}


BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()

0 comments on commit 2187fb2

Please sign in to comment.