From abc6aed4dfeddb72a30bdfe4f9f2cf620491b40a Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Wed, 30 Aug 2023 13:30:18 +0200 Subject: [PATCH 1/2] Pass different notionals for different schedules --- ql/instruments/overnightindexedswap.cpp | 48 ++++++++++++++++++++----- ql/instruments/overnightindexedswap.hpp | 25 ++++++------- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/ql/instruments/overnightindexedswap.cpp b/ql/instruments/overnightindexedswap.cpp index b4bf682b2bd..0481b11ebbb 100644 --- a/ql/instruments/overnightindexedswap.cpp +++ b/ql/instruments/overnightindexedswap.cpp @@ -53,7 +53,7 @@ namespace QuantLib { averagingMethod) {} OvernightIndexedSwap::OvernightIndexedSwap(Type type, - std::vector nominals, + const std::vector& nominals, const Schedule& schedule, Rate fixedRate, DayCounter fixedDC, @@ -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, @@ -96,6 +97,7 @@ namespace QuantLib { fixedSchedule, fixedRate, std::move(fixedDC), + std::vector(1, nominal), overnightSchedule, std::move(overnightIndex), spread, @@ -106,10 +108,11 @@ namespace QuantLib { averagingMethod) {} OvernightIndexedSwap::OvernightIndexedSwap(Type type, - std::vector nominals, + std::vector fixedNominals, Schedule fixedSchedule, Rate fixedRate, DayCounter fixedDC, + std::vector overnightNominals, Schedule overnightSchedule, ext::shared_ptr overnightIndex, Spread spread, @@ -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) @@ -134,7 +137,7 @@ namespace QuantLib { legs_[1] = OvernightLeg(overnightSchedule_, overnightIndex_) - .withNotionals(nominals_) + .withNotionals(overnightNominals_) .withSpreads(spread_) .withTelescopicValueDates(telescopicValueDates) .withPaymentLag(paymentLag) @@ -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 { @@ -198,4 +220,14 @@ namespace QuantLib { return legNPV_[1]; } + Real OvernightIndexedSwap::nominal() const { + QL_REQUIRE(constantNominals_, "varying nominals"); + return fixedNominals_[0]; + } + + const std::vector& OvernightIndexedSwap::nominals() const { + QL_REQUIRE(sameNominals_, "different nominals on fixed and floating leg"); + return fixedNominals_; + } + } diff --git a/ql/instruments/overnightindexedswap.hpp b/ql/instruments/overnightindexedswap.hpp index d38c72d5cd3..661e8602f57 100644 --- a/ql/instruments/overnightindexedswap.hpp +++ b/ql/instruments/overnightindexedswap.hpp @@ -56,7 +56,7 @@ namespace QuantLib { RateAveraging::Type averagingMethod = RateAveraging::Compound); OvernightIndexedSwap(Type type, - std::vector nominals, + const std::vector& nominals, const Schedule& schedule, Rate fixedRate, DayCounter fixedDC, @@ -83,10 +83,11 @@ namespace QuantLib { RateAveraging::Type averagingMethod = RateAveraging::Compound); OvernightIndexedSwap(Type type, - std::vector nominals, + std::vector fixedNominals, Schedule fixedSchedule, Rate fixedRate, DayCounter fixedDC, + std::vector overnightNominals, Schedule overnightSchedule, ext::shared_ptr overnightIndex, Spread spread = 0.0, @@ -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 nominals() const { return nominals_; } + /*! This throws if the nominals are not the same for the two legs. */ + const std::vector& nominals() const; Frequency paymentFrequency() const { return std::max(fixedSchedule_.tenor().frequency(), overnightSchedule_.tenor().frequency()); } + const std::vector& fixedNominals() const { return fixedNominals_; } const Schedule& fixedSchedule() const { return fixedSchedule_; } Rate fixedRate() const { return fixedRate_; } const DayCounter& fixedDayCount() const { return fixedDC_; } + const std::vector& overnightNominals() const { return overnightNominals_; } const Schedule& overnightSchedule() const { return overnightSchedule_; } const ext::shared_ptr& overnightIndex() const { return overnightIndex_; } Spread spread() const { return spread_; } @@ -133,25 +139,20 @@ namespace QuantLib { //@} private: Type type_; - std::vector nominals_; + std::vector fixedNominals_; Schedule fixedSchedule_; Rate fixedRate_; DayCounter fixedDC_; + std::vector overnightNominals_; Schedule overnightSchedule_; ext::shared_ptr 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_; + }; } From 08a0acb44a72fb0becdb90ee31938de5454b6a46 Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Wed, 30 Aug 2023 18:21:17 +0200 Subject: [PATCH 2/2] Add test --- test-suite/overnightindexedswap.cpp | 133 ++++++++++++++++++++++++++++ test-suite/overnightindexedswap.hpp | 1 + 2 files changed, 134 insertions(+) diff --git a/test-suite/overnightindexedswap.cpp b/test-suite/overnightindexedswap.cpp index 8ab88fce50d..c38b7b38a37 100644 --- a/test-suite/overnightindexedswap.cpp +++ b/test-suite/overnightindexedswap.cpp @@ -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"); @@ -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; } diff --git a/test-suite/overnightindexedswap.hpp b/test-suite/overnightindexedswap.hpp index 9654352ea7c..5c84c5d1c2f 100644 --- a/test-suite/overnightindexedswap.hpp +++ b/test-suite/overnightindexedswap.hpp @@ -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(); };