Skip to content

Commit

Permalink
FuturesRateHelper supplying reference dates for yearFraction computat…
Browse files Browse the repository at this point in the history
…ion (#1829)
  • Loading branch information
lballabio committed Jan 31, 2024
2 parents 772d19a + 96a0e12 commit daba5ab
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 157 deletions.
231 changes: 74 additions & 157 deletions ql/termstructures/yield/ratehelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,42 @@
#include <ql/indexes/swapindex.hpp>
#include <ql/instruments/makevanillaswap.hpp>
#include <ql/instruments/simplifynotificationgraph.hpp>
#include <ql/optional.hpp>
#include <ql/pricingengines/swap/discountingswapengine.hpp>
#include <ql/quote.hpp>
#include <ql/termstructures/yield/ratehelpers.hpp>
#include <ql/time/asx.hpp>
#include <ql/time/calendars/jointcalendar.hpp>
#include <ql/time/imm.hpp>
#include <ql/utilities/null_deleter.hpp>
#include <ql/optional.hpp>
#include <utility>

namespace QuantLib {

namespace {

void CheckDate(const Date& date, const Futures::Type type) {
switch (type) {
case Futures::IMM:
QL_REQUIRE(IMM::isIMMdate(date, false), date << " is not a valid IMM date");
break;
case Futures::ASX:
QL_REQUIRE(ASX::isASXdate(date, false), date << " is not a valid ASX date");
break;
default:
QL_FAIL("unknown futures type (" << Integer(type) << ')');
}
}

Time DetermineYearFraction(const Date& earliestDate,
const Date& maturityDate,
const DayCounter& dayCounter) {
return dayCounter.yearFraction(earliestDate, maturityDate,
earliestDate, maturityDate);
}

} // namespace

FuturesRateHelper::FuturesRateHelper(const Handle<Quote>& price,
const Date& iborStartDate,
Natural lengthInMonths,
Expand All @@ -50,22 +74,12 @@ namespace QuantLib {
Handle<Quote> convAdj,
Futures::Type type)
: RateHelper(price), convAdj_(std::move(convAdj)) {
switch (type) {
case Futures::IMM:
QL_REQUIRE(IMM::isIMMdate(iborStartDate, false),
iborStartDate << " is not a valid IMM date");
break;
case Futures::ASX:
QL_REQUIRE(ASX::isASXdate(iborStartDate, false),
iborStartDate << " is not a valid ASX date");
break;
default:
QL_FAIL("unknown futures type (" << Integer(type) << ")");
}
CheckDate(iborStartDate, type);

earliestDate_ = iborStartDate;
maturityDate_ = calendar.advance(iborStartDate, lengthInMonths*Months,
convention, endOfMonth);
yearFraction_ = dayCounter.yearFraction(earliestDate_, maturityDate_);
maturityDate_ =
calendar.advance(iborStartDate, lengthInMonths * Months, convention, endOfMonth);
yearFraction_ = DetermineYearFraction(earliestDate_, maturityDate_, dayCounter);
pillarDate_ = latestDate_ = latestRelevantDate_ = maturityDate_;

registerWith(convAdj_);
Expand All @@ -80,27 +94,10 @@ namespace QuantLib {
const DayCounter& dayCounter,
Rate convAdj,
Futures::Type type)
: RateHelper(price),
convAdj_(Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(convAdj))))
{
switch (type) {
case Futures::IMM:
QL_REQUIRE(IMM::isIMMdate(iborStartDate, false),
iborStartDate << " is not a valid IMM date");
break;
case Futures::ASX:
QL_REQUIRE(ASX::isASXdate(iborStartDate, false),
iborStartDate << " is not a valid ASX date");
break;
default:
QL_FAIL("unknown futures type (" << Integer(type) << ")");
}
earliestDate_ = iborStartDate;
maturityDate_ = calendar.advance(iborStartDate, lengthInMonths*Months,
convention, endOfMonth);
yearFraction_ = dayCounter.yearFraction(earliestDate_, maturityDate_);
pillarDate_ = latestDate_ = latestRelevantDate_ = maturityDate_;
}
: FuturesRateHelper(Handle<Quote>(ext::make_shared<SimpleQuote>(price)),
iborStartDate, lengthInMonths, calendar,
convention, endOfMonth, dayCounter,
Handle<Quote>(ext::make_shared<SimpleQuote>(convAdj)), type) {}

FuturesRateHelper::FuturesRateHelper(const Handle<Quote>& price,
const Date& iborStartDate,
Expand All @@ -109,46 +106,39 @@ namespace QuantLib {
Handle<Quote> convAdj,
Futures::Type type)
: RateHelper(price), convAdj_(std::move(convAdj)) {
CheckDate(iborStartDate, type);

const auto determineMaturityDate =
[&iborStartDate, &iborEndDate](const auto nextDateCalculator) -> Date {
Date maturityDate;
if (iborEndDate == Date()) {
// advance 3 months
maturityDate = nextDateCalculator(iborStartDate);
maturityDate = nextDateCalculator(maturityDate);
maturityDate = nextDateCalculator(maturityDate);
} else {
QL_REQUIRE(iborEndDate > iborStartDate,
"end date (" << iborEndDate << ") must be greater than start date ("
<< iborStartDate << ')');
maturityDate = iborEndDate;
}
return maturityDate;
};

switch (type) {
case Futures::IMM:
QL_REQUIRE(IMM::isIMMdate(iborStartDate, false),
iborStartDate << " is not a valid IMM date");
if (iborEndDate == Date()) {
// advance 3 months
maturityDate_ = IMM::nextDate(iborStartDate, false);
maturityDate_ = IMM::nextDate(maturityDate_, false);
maturityDate_ = IMM::nextDate(maturityDate_, false);
}
else {
QL_REQUIRE(iborEndDate>iborStartDate,
"end date (" << iborEndDate <<
") must be greater than start date (" <<
iborStartDate << ")");
maturityDate_ = iborEndDate;
}
maturityDate_ = determineMaturityDate(
[](const Date date) -> Date { return IMM::nextDate(date, false); });
break;
case Futures::ASX:
QL_REQUIRE(ASX::isASXdate(iborStartDate, false),
iborStartDate << " is not a valid ASX date");
if (iborEndDate == Date()) {
// advance 3 months
maturityDate_ = ASX::nextDate(iborStartDate, false);
maturityDate_ = ASX::nextDate(maturityDate_, false);
maturityDate_ = ASX::nextDate(maturityDate_, false);
}
else {
QL_REQUIRE(iborEndDate>iborStartDate,
"end date (" << iborEndDate <<
") must be greater than start date (" <<
iborStartDate << ")");
maturityDate_ = iborEndDate;
}
maturityDate_ = determineMaturityDate(
[](const Date date) -> Date { return ASX::nextDate(date, false); });
break;
default:
QL_FAIL("unknown futures type (" << Integer(type) << ")");
QL_FAIL("unknown futures type (" << Integer(type) << ')');
}
earliestDate_ = iborStartDate;
yearFraction_ = dayCounter.yearFraction(earliestDate_, maturityDate_);
yearFraction_ = DetermineYearFraction(earliestDate_, maturityDate_, dayCounter);
pillarDate_ = latestDate_ = latestRelevantDate_ = maturityDate_;

registerWith(convAdj_);
Expand All @@ -160,109 +150,36 @@ namespace QuantLib {
const DayCounter& dayCounter,
Rate convAdj,
Futures::Type type)
: RateHelper(price),
convAdj_(Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(convAdj))))
{
switch (type) {
case Futures::IMM:
QL_REQUIRE(IMM::isIMMdate(iborStartDate, false),
iborStartDate << " is not a valid IMM date");
if (iborEndDate == Date()) {
// advance 3 months
maturityDate_ = IMM::nextDate(iborStartDate, false);
maturityDate_ = IMM::nextDate(maturityDate_, false);
maturityDate_ = IMM::nextDate(maturityDate_, false);
}
else {
QL_REQUIRE(iborEndDate>iborStartDate,
"end date (" << iborEndDate <<
") must be greater than start date (" <<
iborStartDate << ")");
maturityDate_ = iborEndDate;
}
break;
case Futures::ASX:
QL_REQUIRE(ASX::isASXdate(iborStartDate, false),
iborStartDate << " is not a valid ASX date");
if (iborEndDate == Date()) {
// advance 3 months
maturityDate_ = ASX::nextDate(iborStartDate, false);
maturityDate_ = ASX::nextDate(maturityDate_, false);
maturityDate_ = ASX::nextDate(maturityDate_, false);
}
else {
QL_REQUIRE(iborEndDate>iborStartDate,
"end date (" << iborEndDate <<
") must be greater than start date (" <<
iborStartDate << ")");
maturityDate_ = iborEndDate;
}
break;
default:
QL_FAIL("unknown futures type (" << Integer(type) << ")");
}
earliestDate_ = iborStartDate;
yearFraction_ = dayCounter.yearFraction(earliestDate_, maturityDate_);
pillarDate_ = latestDate_ = latestRelevantDate_ = maturityDate_;
}
: FuturesRateHelper(Handle<Quote>(ext::make_shared<SimpleQuote>(price)),
iborStartDate, iborEndDate, dayCounter,
Handle<Quote>(ext::make_shared<SimpleQuote>(convAdj)), type) {}

FuturesRateHelper::FuturesRateHelper(const Handle<Quote>& price,
const Date& iborStartDate,
const ext::shared_ptr<IborIndex>& i,
const ext::shared_ptr<IborIndex>& index,
const Handle<Quote>& convAdj,
Futures::Type type)
: RateHelper(price), convAdj_(convAdj) {
switch (type) {
case Futures::IMM:
QL_REQUIRE(IMM::isIMMdate(iborStartDate, false),
iborStartDate << " is not a valid IMM date");
break;
case Futures::ASX:
QL_REQUIRE(ASX::isASXdate(iborStartDate, false),
iborStartDate << " is not a valid ASX date");
break;
default:
QL_FAIL("unknown futures type (" << Integer(type) << ")");
}
CheckDate(iborStartDate, type);

earliestDate_ = iborStartDate;
const Calendar& cal = i->fixingCalendar();
maturityDate_ = cal.advance(iborStartDate, i->tenor(),
i->businessDayConvention());
yearFraction_ = i->dayCounter().yearFraction(earliestDate_,
maturityDate_);
const Calendar& cal = index->fixingCalendar();
maturityDate_ =
cal.advance(iborStartDate, index->tenor(), index->businessDayConvention());
yearFraction_ = DetermineYearFraction(earliestDate_, maturityDate_, index->dayCounter());
pillarDate_ = latestDate_ = latestRelevantDate_ = maturityDate_;

registerWith(convAdj);
}

FuturesRateHelper::FuturesRateHelper(Real price,
const Date& iborStartDate,
const ext::shared_ptr<IborIndex>& i,
const ext::shared_ptr<IborIndex>& index,
Rate convAdj,
Futures::Type type)
: RateHelper(price),
convAdj_(Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(convAdj))))
{
switch (type) {
case Futures::IMM:
QL_REQUIRE(IMM::isIMMdate(iborStartDate, false),
iborStartDate << " is not a valid IMM date");
break;
case Futures::ASX:
QL_REQUIRE(ASX::isASXdate(iborStartDate, false),
iborStartDate << " is not a valid ASX date");
break;
default:
QL_FAIL("unknown futures type (" << Integer(type) << ")");
}
earliestDate_ = iborStartDate;
const Calendar& cal = i->fixingCalendar();
maturityDate_ = cal.advance(iborStartDate, i->tenor(),
i->businessDayConvention());
yearFraction_ = i->dayCounter().yearFraction(earliestDate_,
maturityDate_);
pillarDate_ = latestDate_ = latestRelevantDate_ = maturityDate_;
}
: FuturesRateHelper(Handle<Quote>(ext::make_shared<SimpleQuote>(price)),
iborStartDate, index,
Handle<Quote>(ext::make_shared<SimpleQuote>(convAdj)), type) {}

Real FuturesRateHelper::impliedQuote() const {
QL_REQUIRE(termStructure_ != nullptr, "term structure not set");
Expand Down Expand Up @@ -817,7 +734,7 @@ namespace QuantLib {
void SwapRateHelper::initializeDates() {

// 1. do not pass the spread here, as it might be a Quote
// i.e. it can dinamically change
// i.e. it can dynamically change
// 2. input discount curve Handle might be empty now but it could
// be assigned a curve later; use a RelinkableHandle here
swap_ = MakeVanillaSwap(tenor_, iborIndex_, 0.0, fwdStart_)
Expand Down
35 changes: 35 additions & 0 deletions test-suite/piecewiseyieldcurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include <ql/termstructures/yield/piecewiseyieldcurve.hpp>
#include <ql/termstructures/yield/ratehelpers.hpp>
#include <ql/time/asx.hpp>
#include <ql/time/calendars/canada.hpp>
#include <ql/time/calendars/japan.hpp>
#include <ql/time/calendars/jointcalendar.hpp>
#include <ql/time/calendars/target.hpp>
Expand Down Expand Up @@ -1024,6 +1025,40 @@ BOOST_AUTO_TEST_CASE(testJpyLibor) {
}
}


BOOST_AUTO_TEST_CASE(testCA365Futures) {

BOOST_TEST_MESSAGE("Testing futures rate helpers with act/365 Canadian day counter...");

CommonVars vars;

Settings::instance().evaluationDate() = vars.today;

auto index =
ext::make_shared<IborIndex>("foo", 3*Months, 2, Currency(),
Canada(), ModifiedFollowing, true,
Actual365Fixed(Actual365Fixed::Canadian));

Date immDate = Date();
for (Size i = 0; i<vars.immFuts; i++) {
Handle<Quote> r(vars.immFutPrices[i]);
immDate = IMM::nextDate(immDate, false);
// if the fixing is before the evaluation date, we
// just jump forward by one future maturity
if (index->fixingDate(immDate) < Settings::instance().evaluationDate())
immDate = IMM::nextDate(immDate, false);
vars.immFutHelpers[i] =
ext::make_shared<FuturesRateHelper>(r, immDate, index, Handle<Quote>(), Futures::IMM);
}

auto termStructure =
ext::make_shared<PiecewiseYieldCurve<Discount,LogLinear>>(
vars.settlement, vars.immFutHelpers, Actual360());

BOOST_CHECK_NO_THROW(termStructure->nodes());
}


BOOST_AUTO_TEST_CASE(testDefaultInstantiation) {

BOOST_TEST_MESSAGE("Testing instantiation of curves without passing an interpolator...");
Expand Down

0 comments on commit daba5ab

Please sign in to comment.