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

An OIS needs different notionals for different schedules #1777

Merged
merged 2 commits into from
Aug 30, 2023
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
48 changes: 40 additions & 8 deletions ql/instruments/overnightindexedswap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ namespace QuantLib {
averagingMethod) {}

OvernightIndexedSwap::OvernightIndexedSwap(Type type,
std::vector<Real> nominals,
const std::vector<Real>& nominals,
const Schedule& schedule,
Rate fixedRate,
DayCounter fixedDC,
Expand All @@ -65,10 +65,11 @@ namespace QuantLib {
bool telescopicValueDates,
RateAveraging::Type averagingMethod)
: OvernightIndexedSwap(type,
std::move(nominals),
nominals,
schedule,
fixedRate,
std::move(fixedDC),
nominals,
schedule,
std::move(overnightIndex),
spread,
Expand Down Expand Up @@ -96,6 +97,7 @@ namespace QuantLib {
fixedSchedule,
fixedRate,
std::move(fixedDC),
std::vector<Real>(1, nominal),
overnightSchedule,
std::move(overnightIndex),
spread,
Expand All @@ -106,10 +108,11 @@ namespace QuantLib {
averagingMethod) {}

OvernightIndexedSwap::OvernightIndexedSwap(Type type,
std::vector<Real> nominals,
std::vector<Real> fixedNominals,
Schedule fixedSchedule,
Rate fixedRate,
DayCounter fixedDC,
std::vector<Real> overnightNominals,
Schedule overnightSchedule,
ext::shared_ptr<OvernightIndex> overnightIndex,
Spread spread,
Expand All @@ -118,14 +121,14 @@ namespace QuantLib {
const Calendar& paymentCalendar,
bool telescopicValueDates,
RateAveraging::Type averagingMethod)
: Swap(2), type_(type), nominals_(std::move(nominals)),
: Swap(2), type_(type), fixedNominals_(std::move(fixedNominals)),
fixedSchedule_(std::move(fixedSchedule)), fixedRate_(fixedRate), fixedDC_(std::move(fixedDC)),
overnightSchedule_(std::move(overnightSchedule)), overnightIndex_(std::move(overnightIndex)),
spread_(spread), averagingMethod_(averagingMethod) {
overnightNominals_(std::move(overnightNominals)), overnightSchedule_(std::move(overnightSchedule)),
overnightIndex_(std::move(overnightIndex)), spread_(spread), averagingMethod_(averagingMethod) {
if (fixedDC_ == DayCounter())
fixedDC_ = overnightIndex_->dayCounter();
legs_[0] = FixedRateLeg(fixedSchedule_)
.withNotionals(nominals_)
.withNotionals(fixedNominals_)
.withCouponRates(fixedRate_, fixedDC_)
.withPaymentLag(paymentLag)
.withPaymentAdjustment(paymentAdjustment)
Expand All @@ -134,7 +137,7 @@ namespace QuantLib {

legs_[1] =
OvernightLeg(overnightSchedule_, overnightIndex_)
.withNotionals(nominals_)
.withNotionals(overnightNominals_)
.withSpreads(spread_)
.withTelescopicValueDates(telescopicValueDates)
.withPaymentLag(paymentLag)
Expand All @@ -160,6 +163,25 @@ namespace QuantLib {
default:
QL_FAIL("Unknown overnight-swap type");
}

// These bools tell us if we can support the old methods nominal() and nominals().
// There might be false negatives (i.e., if we pass constant vectors of different lengths
// as fixedNominals and floatingNominals) but we're going to assume that whoever uses the
// constructor with two vectors is mostly going to use the new methods instead.
sameNominals_ = std::equal(fixedNominals_.begin(), fixedNominals_.end(),
overnightNominals_.begin(), overnightNominals_.end());
if (!sameNominals_) {
constantNominals_ = false;
} else {
constantNominals_ = true;
Real front = fixedNominals_[0];
for (auto x : fixedNominals_) {
if (x != front) {
constantNominals_ = false;
break;
}
}
}
}

Real OvernightIndexedSwap::fairRate() const {
Expand Down Expand Up @@ -198,4 +220,14 @@ namespace QuantLib {
return legNPV_[1];
}

Real OvernightIndexedSwap::nominal() const {
QL_REQUIRE(constantNominals_, "varying nominals");
return fixedNominals_[0];
}

const std::vector<Real>& OvernightIndexedSwap::nominals() const {
QL_REQUIRE(sameNominals_, "different nominals on fixed and floating leg");
return fixedNominals_;
}

}
25 changes: 13 additions & 12 deletions ql/instruments/overnightindexedswap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ namespace QuantLib {
RateAveraging::Type averagingMethod = RateAveraging::Compound);

OvernightIndexedSwap(Type type,
std::vector<Real> nominals,
const std::vector<Real>& nominals,
const Schedule& schedule,
Rate fixedRate,
DayCounter fixedDC,
Expand All @@ -83,10 +83,11 @@ namespace QuantLib {
RateAveraging::Type averagingMethod = RateAveraging::Compound);

OvernightIndexedSwap(Type type,
std::vector<Real> nominals,
std::vector<Real> fixedNominals,
Schedule fixedSchedule,
Rate fixedRate,
DayCounter fixedDC,
std::vector<Real> overnightNominals,
Schedule overnightSchedule,
ext::shared_ptr<OvernightIndex> overnightIndex,
Spread spread = 0.0,
Expand All @@ -99,18 +100,23 @@ namespace QuantLib {
//! \name Inspectors
//@{
Type type() const { return type_; }

/*! This throws if the nominal is not constant across coupons. */
Real nominal() const;
std::vector<Real> nominals() const { return nominals_; }
/*! This throws if the nominals are not the same for the two legs. */
const std::vector<Real>& nominals() const;

Frequency paymentFrequency() const {
return std::max(fixedSchedule_.tenor().frequency(),
overnightSchedule_.tenor().frequency());
}

const std::vector<Real>& fixedNominals() const { return fixedNominals_; }
const Schedule& fixedSchedule() const { return fixedSchedule_; }
Rate fixedRate() const { return fixedRate_; }
const DayCounter& fixedDayCount() const { return fixedDC_; }

const std::vector<Real>& overnightNominals() const { return overnightNominals_; }
const Schedule& overnightSchedule() const { return overnightSchedule_; }
const ext::shared_ptr<OvernightIndex>& overnightIndex() const { return overnightIndex_; }
Spread spread() const { return spread_; }
Expand All @@ -133,25 +139,20 @@ namespace QuantLib {
//@}
private:
Type type_;
std::vector<Real> nominals_;

std::vector<Real> fixedNominals_;
Schedule fixedSchedule_;
Rate fixedRate_;
DayCounter fixedDC_;

std::vector<Real> overnightNominals_;
Schedule overnightSchedule_;
ext::shared_ptr<OvernightIndex> overnightIndex_;
Spread spread_;
RateAveraging::Type averagingMethod_;
};


// inline

inline Real OvernightIndexedSwap::nominal() const {
QL_REQUIRE(nominals_.size() == 1, "varying nominals");
return nominals_[0];
}
bool constantNominals_, sameNominals_;
};

}

Expand Down
133 changes: 133 additions & 0 deletions test-suite/overnightindexedswap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,138 @@ void OvernightIndexedSwapTest::test131BootstrapRegression() {
BOOST_CHECK_NO_THROW(curve.nodes());
}

void OvernightIndexedSwapTest::testConstructorsAndNominals() {
BOOST_TEST_MESSAGE("Testing different constructors for OIS...");

using namespace overnight_indexed_swap_test;

CommonVars vars;

Date spot = vars.calendar.advance(vars.today, 2*Days);
Real nominal = 100000.0;

// constant notional, same schedule

Schedule schedule = MakeSchedule()
.from(spot)
.to(vars.calendar.advance(spot, 2*Years))
.withCalendar(vars.calendar)
.withFrequency(Annual);

auto ois_1 = OvernightIndexedSwap(Swap::Payer,
nominal,
schedule,
0.03,
Actual360(),
vars.eoniaIndex);

BOOST_CHECK_EQUAL(ois_1.fixedSchedule().tenor(), 1*Years);
BOOST_CHECK_EQUAL(ois_1.overnightSchedule().tenor(), 1*Years);
BOOST_CHECK_EQUAL(ois_1.paymentFrequency(), Annual);

BOOST_CHECK_EQUAL(ois_1.nominal(), nominal);

BOOST_CHECK_EQUAL(ois_1.nominals().size(), Size(1));
BOOST_CHECK_EQUAL(ois_1.nominals()[0], nominal);

BOOST_CHECK_EQUAL(ois_1.fixedNominals().size(), Size(1));
BOOST_CHECK_EQUAL(ois_1.fixedNominals()[0], nominal);

BOOST_CHECK_EQUAL(ois_1.overnightNominals().size(), Size(1));
BOOST_CHECK_EQUAL(ois_1.overnightNominals()[0], nominal);

// amortizing notionals, same schedule

auto ois_2 = OvernightIndexedSwap(Swap::Payer,
{ nominal, nominal/2 },
schedule,
0.03,
Actual360(),
vars.eoniaIndex);

BOOST_CHECK_EQUAL(ois_2.fixedSchedule().tenor(), 1*Years);
BOOST_CHECK_EQUAL(ois_2.overnightSchedule().tenor(), 1*Years);
BOOST_CHECK_EQUAL(ois_2.paymentFrequency(), Annual);

BOOST_CHECK_EXCEPTION(ois_2.nominal(), Error,
ExpectedErrorMessage("varying nominal"));

BOOST_CHECK_EQUAL(ois_2.nominals().size(), Size(2));
BOOST_CHECK_EQUAL(ois_2.nominals()[0], nominal);
BOOST_CHECK_EQUAL(ois_2.nominals()[1], nominal/2);

BOOST_CHECK_EQUAL(ois_2.fixedNominals().size(), Size(2));
BOOST_CHECK_EQUAL(ois_2.fixedNominals()[0], nominal);
BOOST_CHECK_EQUAL(ois_2.fixedNominals()[1], nominal/2);

BOOST_CHECK_EQUAL(ois_2.overnightNominals().size(), Size(2));
BOOST_CHECK_EQUAL(ois_2.overnightNominals()[0], nominal);
BOOST_CHECK_EQUAL(ois_2.overnightNominals()[1], nominal/2);

// constant notional, different schedules

Schedule fixedSchedule = schedule;
Schedule overnightSchedule = MakeSchedule()
.from(spot)
.to(vars.calendar.advance(spot, 2*Years))
.withCalendar(vars.calendar)
.withFrequency(Semiannual);

auto ois_3 = OvernightIndexedSwap(Swap::Payer,
nominal,
fixedSchedule,
0.03,
Actual360(),
overnightSchedule,
vars.eoniaIndex);

BOOST_CHECK_EQUAL(ois_3.fixedSchedule().tenor(), 1*Years);
BOOST_CHECK_EQUAL(ois_3.overnightSchedule().tenor(), 6*Months);
BOOST_CHECK_EQUAL(ois_3.paymentFrequency(), Semiannual);

BOOST_CHECK_EQUAL(ois_3.nominal(), nominal);

BOOST_CHECK_EQUAL(ois_3.nominals().size(), Size(1));
BOOST_CHECK_EQUAL(ois_3.nominals()[0], nominal);

BOOST_CHECK_EQUAL(ois_3.fixedNominals().size(), Size(1));
BOOST_CHECK_EQUAL(ois_3.fixedNominals()[0], nominal);

BOOST_CHECK_EQUAL(ois_3.overnightNominals().size(), Size(1));
BOOST_CHECK_EQUAL(ois_3.overnightNominals()[0], nominal);

// amortizing notionals, different schedules

auto ois_4 = OvernightIndexedSwap(Swap::Payer,
{ nominal, nominal/2 },
fixedSchedule,
0.03,
Actual360(),
{ nominal, nominal, nominal/2, nominal/2 },
overnightSchedule,
vars.eoniaIndex);

BOOST_CHECK_EQUAL(ois_4.fixedSchedule().tenor(), 1*Years);
BOOST_CHECK_EQUAL(ois_4.overnightSchedule().tenor(), 6*Months);
BOOST_CHECK_EQUAL(ois_4.paymentFrequency(), Semiannual);

BOOST_CHECK_EXCEPTION(ois_4.nominal(), Error,
ExpectedErrorMessage("varying nominals"));

BOOST_CHECK_EXCEPTION(ois_4.nominals(), Error,
ExpectedErrorMessage("different nominals"));

BOOST_CHECK_EQUAL(ois_4.fixedNominals().size(), Size(2));
BOOST_CHECK_EQUAL(ois_4.fixedNominals()[0], nominal);
BOOST_CHECK_EQUAL(ois_4.fixedNominals()[1], nominal/2);

BOOST_CHECK_EQUAL(ois_4.overnightNominals().size(), Size(4));
BOOST_CHECK_EQUAL(ois_4.overnightNominals()[0], nominal);
BOOST_CHECK_EQUAL(ois_4.overnightNominals()[1], nominal);
BOOST_CHECK_EQUAL(ois_4.overnightNominals()[2], nominal/2);
BOOST_CHECK_EQUAL(ois_4.overnightNominals()[3], nominal/2);
}


test_suite* OvernightIndexedSwapTest::suite() {
auto* suite = BOOST_TEST_SUITE("Overnight-indexed swap tests");
Expand All @@ -529,5 +661,6 @@ test_suite* OvernightIndexedSwapTest::suite() {
suite->add(QUANTLIB_TEST_CASE(&OvernightIndexedSwapTest::testSeasonedSwaps));
suite->add(QUANTLIB_TEST_CASE(&OvernightIndexedSwapTest::testBootstrapRegression));
suite->add(QUANTLIB_TEST_CASE(&OvernightIndexedSwapTest::test131BootstrapRegression));
suite->add(QUANTLIB_TEST_CASE(&OvernightIndexedSwapTest::testConstructorsAndNominals));
return suite;
}
1 change: 1 addition & 0 deletions test-suite/overnightindexedswap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class OvernightIndexedSwapTest {
static void testSeasonedSwaps();
static void testBootstrapRegression();
static void test131BootstrapRegression();
static void testConstructorsAndNominals();
static boost::unit_test_framework::test_suite* suite();
};

Expand Down