Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose OvernightIndexedCouponPricer #2024

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading