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

FuturesRateHelper supplying reference dates for yearFraction computation #1829

Merged
merged 8 commits into from
Jan 31, 2024
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
Loading