Skip to content

Commit

Permalink
Expose OvernightIndexedCouponPricer (#2024)
Browse files Browse the repository at this point in the history
  • Loading branch information
lballabio authored Aug 5, 2024
2 parents 2243398 + 1c9494a commit 4e5dd72
Show file tree
Hide file tree
Showing 13 changed files with 422 additions and 409 deletions.
3 changes: 2 additions & 1 deletion QuantLib.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@
<ClInclude Include="ql\cashflows\inflationcouponpricer.hpp" />
<ClInclude Include="ql\cashflows\lineartsrpricer.hpp" />
<ClInclude Include="ql\cashflows\overnightindexedcoupon.hpp" />
<ClInclude Include="ql\cashflows\overnightindexedcouponpricer.hpp" />
<ClInclude Include="ql\cashflows\rangeaccrual.hpp" />
<ClInclude Include="ql\cashflows\rateaveraging.hpp" />
<ClInclude Include="ql\cashflows\replication.hpp" />
Expand Down Expand Up @@ -1933,6 +1934,7 @@
<ClCompile Include="ql\cashflows\inflationcouponpricer.cpp" />
<ClCompile Include="ql\cashflows\lineartsrpricer.cpp" />
<ClCompile Include="ql\cashflows\overnightindexedcoupon.cpp" />
<ClCompile Include="ql\cashflows\overnightindexedcouponpricer.cpp" />
<ClCompile Include="ql\cashflows\rangeaccrual.cpp" />
<ClCompile Include="ql\cashflows\replication.cpp" />
<ClCompile Include="ql\cashflows\simplecashflow.cpp" />
Expand All @@ -1951,7 +1953,6 @@
<ClCompile Include="ql\experimental\asian\analytic_discr_geom_av_price_heston.cpp" />
<ClCompile Include="ql\experimental\averageois\arithmeticaverageois.cpp" />
<ClCompile Include="ql\experimental\averageois\arithmeticoisratehelper.cpp" />
<ClCompile Include="ql\experimental\averageois\averageoiscouponpricer.cpp" />
<ClCompile Include="ql\experimental\averageois\makearithmeticaverageois.cpp" />
<ClCompile Include="ql\experimental\barrieroption\discretizeddoublebarrieroption.cpp" />
<ClCompile Include="ql\experimental\barrieroption\mcdoublebarrierengine.cpp" />
Expand Down
9 changes: 6 additions & 3 deletions QuantLib.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,9 @@
<ClInclude Include="ql\cashflows\overnightindexedcoupon.hpp">
<Filter>cashflows</Filter>
</ClInclude>
<ClInclude Include="ql\cashflows\overnightindexedcouponpricer.hpp">
<Filter>cashflows</Filter>
</ClInclude>
<ClInclude Include="ql\cashflows\rangeaccrual.hpp">
<Filter>cashflows</Filter>
</ClInclude>
Expand Down Expand Up @@ -4544,6 +4547,9 @@
<ClCompile Include="ql\cashflows\overnightindexedcoupon.cpp">
<Filter>cashflows</Filter>
</ClCompile>
<ClCompile Include="ql\cashflows\overnightindexedcouponpricer.cpp">
<Filter>cashflows</Filter>
</ClCompile>
<ClCompile Include="ql\cashflows\rangeaccrual.cpp">
<Filter>cashflows</Filter>
</ClCompile>
Expand Down Expand Up @@ -6590,9 +6596,6 @@
<ClCompile Include="ql\experimental\averageois\arithmeticaverageois.cpp">
<Filter>experimental\averageois</Filter>
</ClCompile>
<ClCompile Include="ql\experimental\averageois\averageoiscouponpricer.cpp">
<Filter>experimental\averageois</Filter>
</ClCompile>
<ClCompile Include="ql\experimental\averageois\arithmeticoisratehelper.cpp">
<Filter>experimental\averageois</Filter>
</ClCompile>
Expand Down
3 changes: 2 additions & 1 deletion ql/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ set(QL_SOURCES
cashflows/inflationcouponpricer.cpp
cashflows/lineartsrpricer.cpp
cashflows/overnightindexedcoupon.cpp
cashflows/overnightindexedcouponpricer.cpp
cashflows/rangeaccrual.cpp
cashflows/replication.cpp
cashflows/simplecashflow.cpp
Expand All @@ -49,7 +50,6 @@ set(QL_SOURCES
experimental/asian/analytic_discr_geom_av_price_heston.cpp
experimental/averageois/arithmeticaverageois.cpp
experimental/averageois/arithmeticoisratehelper.cpp
experimental/averageois/averageoiscouponpricer.cpp
experimental/averageois/makearithmeticaverageois.cpp
experimental/barrieroption/mcdoublebarrierengine.cpp
experimental/barrieroption/discretizeddoublebarrieroption.cpp
Expand Down Expand Up @@ -961,6 +961,7 @@ set(QL_HEADERS
cashflows/inflationcouponpricer.hpp
cashflows/lineartsrpricer.hpp
cashflows/overnightindexedcoupon.hpp
cashflows/overnightindexedcouponpricer.hpp
cashflows/rangeaccrual.hpp
cashflows/rateaveraging.hpp
cashflows/replication.hpp
Expand Down
2 changes: 2 additions & 0 deletions ql/cashflows/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ this_include_HEADERS = \
inflationcouponpricer.hpp \
lineartsrpricer.hpp \
overnightindexedcoupon.hpp \
overnightindexedcouponpricer.hpp \
rangeaccrual.hpp \
rateaveraging.hpp \
replication.hpp \
Expand Down Expand Up @@ -64,6 +65,7 @@ cpp_files = \
inflationcouponpricer.cpp \
lineartsrpricer.cpp \
overnightindexedcoupon.cpp \
overnightindexedcouponpricer.cpp \
rangeaccrual.cpp \
replication.cpp \
simplecashflow.cpp \
Expand Down
1 change: 1 addition & 0 deletions ql/cashflows/all.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <ql/cashflows/inflationcouponpricer.hpp>
#include <ql/cashflows/lineartsrpricer.hpp>
#include <ql/cashflows/overnightindexedcoupon.hpp>
#include <ql/cashflows/overnightindexedcouponpricer.hpp>
#include <ql/cashflows/rangeaccrual.hpp>
#include <ql/cashflows/rateaveraging.hpp>
#include <ql/cashflows/replication.hpp>
Expand Down
229 changes: 27 additions & 202 deletions ql/cashflows/overnightindexedcoupon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
*/

#include <ql/cashflows/couponpricer.hpp>
#include <ql/experimental/averageois/averageoiscouponpricer.hpp>
#include <ql/cashflows/overnightindexedcouponpricer.hpp>
#include <ql/cashflows/overnightindexedcoupon.hpp>
#include <ql/termstructures/yieldtermstructure.hpp>
#include <ql/utilities/vectors.hpp>
Expand All @@ -34,169 +34,6 @@ using std::vector;
namespace QuantLib {

namespace {

Size determineNumberOfFixings(const vector<Date>& interestDates,
const Date& date,
bool applyObservationShift) {
Size n =
std::lower_bound(interestDates.begin(), interestDates.end(), date) - interestDates.begin();
// When using the observation shift, it may happen that
// that the end of accrual period will fall later than the last
// interest date. In which case, n will be equal to the number of
// interest dates, while we know that the number of fixing dates is
// always one less than the number of interest dates.
return n == interestDates.size() && applyObservationShift ? n - 1 : n;
}

class OvernightIndexedCouponPricer : public FloatingRateCouponPricer {
public:
void initialize(const FloatingRateCoupon& coupon) override {
coupon_ = dynamic_cast<const OvernightIndexedCoupon*>(&coupon);
QL_ENSURE(coupon_, "wrong coupon type");
}

Rate averageRate(const Date& date) const {

const Date today = Settings::instance().evaluationDate();

const ext::shared_ptr<OvernightIndex> index =
ext::dynamic_pointer_cast<OvernightIndex>(coupon_->index());
const auto& pastFixings = IndexManager::instance().getHistory(index->name());

const vector<Date>& fixingDates = coupon_->fixingDates();
const vector<Date>& valueDates = coupon_->valueDates();
const vector<Date>& interestDates = coupon_->interestDates();
const vector<Time>& dt = coupon_->dt();
const bool applyObservationShift = coupon_->applyObservationShift();

Size i = 0;
const Size n = determineNumberOfFixings(interestDates, date, applyObservationShift);

Real compoundFactor = 1.0;

// already fixed part
while (i < n && fixingDates[i] < today) {
// rate must have been fixed
const Rate fixing = pastFixings[fixingDates[i]];
QL_REQUIRE(fixing != Null<Real>(),
"Missing " << index->name() <<
" fixing for " << fixingDates[i]);
Time span = (date >= interestDates[i + 1] ?
dt[i] :
index->dayCounter().yearFraction(interestDates[i], date));
compoundFactor *= (1.0 + fixing * span);
++i;
}

// today is a border case
if (i < n && fixingDates[i] == today) {
// might have been fixed
try {
Rate fixing = pastFixings[fixingDates[i]];
if (fixing != Null<Real>()) {
Time span = (date >= interestDates[i + 1] ?
dt[i] :
index->dayCounter().yearFraction(interestDates[i], date));
compoundFactor *= (1.0 + fixing * span);
++i;
} else {
; // fall through and forecast
}
} catch (Error&) {
; // fall through and forecast
}
}

// forward part using telescopic property in order
// to avoid the evaluation of multiple forward fixings
// where possible.
if (i < n) {
const Handle<YieldTermStructure> curve = index->forwardingTermStructure();
QL_REQUIRE(!curve.empty(),
"null term structure set to this instance of " << index->name());

const auto effectiveRate = [&index, &fixingDates, &date, &interestDates,
&dt](Size position) {
Rate fixing = index->fixing(fixingDates[position]);
Time span =
(date >= interestDates[position + 1] ?
dt[position] :
index->dayCounter().yearFraction(interestDates[position], date));
return span * fixing;
};

if (!coupon_->canApplyTelescopicFormula()) {
// With lookback applied, the telescopic formula cannot be used,
// we need to project each fixing in the coupon.
// Only in one particular case when observation shift is used and
// no intrinsic index fixing delay is applied, the telescopic formula
// holds, because regardless of the fixing delay in the coupon,
// in such configuration value dates will be equal to interest dates.
// A potential lockout, which may occur in tandem with a lookback
// setting, will be handled automatically based on fixing dates.
// Same applies to a case when accrual calculation date does or
// does not occur on an interest date.
while (i < n) {
compoundFactor *= (1.0 + effectiveRate(i));
++i;
}
} else {
// No lookback, we can partially apply the telescopic formula.
// But we need to make a correction for a potential lockout.
const Size nLockout = n - coupon_->lockoutDays();
const bool isLockoutApplied = coupon_->lockoutDays() > 0;

// Lockout could already start at or before i.
// In such case the ratio of discount factors will be equal to 1.
const DiscountFactor startDiscount =
curve->discount(valueDates[std::min<Size>(nLockout, i)]);
if (interestDates[n] == date || isLockoutApplied) {
// telescopic formula up to potential lockout dates.
const DiscountFactor endDiscount =
curve->discount(valueDates[std::min<Size>(nLockout, n)]);
compoundFactor *= startDiscount / endDiscount;
// For the lockout periods the telescopic formula does not apply.
// The value dates (at which the projection is calculated) correspond
// to the locked-out fixing, while the interest dates (at which the
// interest over that fixing is accrued) are not fixed at lockout,
// hence they do not cancel out.
i = std::max(nLockout, i);

// With no lockout, the loop is skipped because i = n.
while (i < n) {
compoundFactor *= (1.0 + effectiveRate(i));
++i;
}
} else {
// No lockout and date is different than last interest date.
// The last fixing is not used for its full period (the date is between
// its start and end date). We can use the telescopic formula until the
// previous date, then we'll add the missing bit.
const DiscountFactor endDiscount = curve->discount(valueDates[n - 1]);
compoundFactor *= startDiscount / endDiscount;
compoundFactor *= (1.0 + effectiveRate(n - 1));
}
}
}

const Rate rate = (compoundFactor - 1.0) / coupon_->accruedPeriod(date);
return coupon_->gearing() * rate + coupon_->spread();
}

Rate swapletRate() const override {
return averageRate(coupon_->accrualEndDate());
}

Real swapletPrice() const override { QL_FAIL("swapletPrice not available"); }
Real capletPrice(Rate) const override { QL_FAIL("capletPrice not available"); }
Rate capletRate(Rate) const override { QL_FAIL("capletRate not available"); }
Real floorletPrice(Rate) const override { QL_FAIL("floorletPrice not available"); }
Rate floorletRate(Rate) const override { QL_FAIL("floorletRate not available"); }

protected:
const OvernightIndexedCoupon* coupon_;
};

Date applyLookbackPeriod(const ext::shared_ptr<InterestRateIndex>& index,
const Date& valueDate,
Natural lookbackDays) {
Expand Down Expand Up @@ -310,14 +147,14 @@ namespace QuantLib {
// If fixing dates of the coupon deviate from fixing days in the index
// we need to correct the value dates such that they reflect dates
// corresponding to a deposit instrument linked to the index.
// This is to ensure that future projections (which are computed
// This is to ensure that future projections (which are computed
// based on the value dates) of the index do not
// yield any convexity corrections.
// yield any convexity corrections.
valueDates_[i] = overnightIndex->valueDate(tmp);
}
}
// When lockout is used the fixing rate applied for the last k days of the
// interest period is frozen at the rate observed k days before the period ends.
// When lockout is used the fixing rate applied for the last k days of the
// interest period is frozen at the rate observed k days before the period ends.
if (lockoutDays_ != 0) {
QL_REQUIRE(lockoutDays_ > 0 && lockoutDays_ < n_,
"Lockout period cannot be negative or exceed the number of fixing days.");
Expand All @@ -333,21 +170,17 @@ namespace QuantLib {
dt_[i] = dc.yearFraction(interestDates_[i], interestDates_[i + 1]);

switch (averagingMethod) {
case RateAveraging::Simple:
QL_REQUIRE(
fixingDays_ == overnightIndex->fixingDays() && !applyObservationShift_ &&
lockoutDays_ == 0,
"Cannot price an overnight coupon with simple averaging with lookback or "
"lockout.");
setPricer(ext::shared_ptr<FloatingRateCouponPricer>(
new ArithmeticAveragedOvernightIndexedCouponPricer(telescopicValueDates)));
break;
case RateAveraging::Compound:
setPricer(
ext::shared_ptr<FloatingRateCouponPricer>(new OvernightIndexedCouponPricer));
break;
default:
QL_FAIL("unknown compounding convention (" << Integer(averagingMethod) << ")");
case RateAveraging::Simple:
QL_REQUIRE(
fixingDays_ == overnightIndex->fixingDays() && !applyObservationShift_ && lockoutDays_ == 0,
"Cannot price an overnight coupon with simple averaging with lookback or lockout.");
setPricer(ext::make_shared<ArithmeticAveragedOvernightIndexedCouponPricer>(telescopicValueDates));
break;
case RateAveraging::Compound:
setPricer(ext::make_shared<CompoundingOvernightIndexedCouponPricer>());
break;
default:
QL_FAIL("unknown compounding convention (" << Integer(averagingMethod) << ")");
}
}

Expand All @@ -366,10 +199,10 @@ namespace QuantLib {
Rate OvernightIndexedCoupon::averageRate(const Date& d) const {
QL_REQUIRE(pricer_, "pricer not set");
pricer_->initialize(*this);
const auto overnightIndexPricer = ext::dynamic_pointer_cast<OvernightIndexedCouponPricer>(pricer_);
if (overnightIndexPricer)
return overnightIndexPricer->averageRate(d);

if (const auto compoundingPricer =
ext::dynamic_pointer_cast<CompoundingOvernightIndexedCouponPricer>(pricer_)) {
return compoundingPricer->averageRate(d);
}
return pricer_->swapletRate();
}

Expand Down Expand Up @@ -493,21 +326,13 @@ namespace QuantLib {
refEnd = calendar.adjust(start + schedule_.tenor(),
paymentAdjustment_);

cashflows.push_back(ext::shared_ptr<CashFlow>(new
OvernightIndexedCoupon(paymentDate,
detail::get(notionals_, i,
notionals_.back()),
start, end,
overnightIndex_,
detail::get(gearings_, i, 1.0),
detail::get(spreads_, i, 0.0),
refStart, refEnd,
paymentDayCounter_,
telescopicValueDates_,
averagingMethod_,
lookbackDays_,
lockoutDays_,
applyObservationShift_)));
const auto overnightIndexedCoupon = ext::make_shared<OvernightIndexedCoupon>(
paymentDate, detail::get(notionals_, i, notionals_.back()), start, end,
overnightIndex_, detail::get(gearings_, i, 1.0), detail::get(spreads_, i, 0.0),
refStart, refEnd, paymentDayCounter_, telescopicValueDates_, averagingMethod_,
lookbackDays_, lockoutDays_, applyObservationShift_);

cashflows.push_back(overnightIndexedCoupon);
}
return cashflows;
}
Expand Down
Loading

0 comments on commit 4e5dd72

Please sign in to comment.