Skip to content

Commit

Permalink
An OIS needs different notionals for different schedules (#1777)
Browse files Browse the repository at this point in the history
  • Loading branch information
lballabio authored Aug 30, 2023
2 parents c85fc78 + 08a0acb commit 5f64e20
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 20 deletions.
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

0 comments on commit 5f64e20

Please sign in to comment.