From 1cf9e8ed07bb07451fac782d20feaac4b044ffce Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Mon, 24 Jun 2024 11:29:59 +0200 Subject: [PATCH] Account for possible adjustments (e.g. on Juneteenth) --- .../yield/overnightindexfutureratehelper.cpp | 14 ++-- test-suite/sofrfutures.cpp | 74 +++++++++++++++---- 2 files changed, 67 insertions(+), 21 deletions(-) diff --git a/ql/termstructures/yield/overnightindexfutureratehelper.cpp b/ql/termstructures/yield/overnightindexfutureratehelper.cpp index 8862dab087d..0c652e36ce9 100644 --- a/ql/termstructures/yield/overnightindexfutureratehelper.cpp +++ b/ql/termstructures/yield/overnightindexfutureratehelper.cpp @@ -28,19 +28,19 @@ namespace QuantLib { namespace { Date getValidSofrStart(Month month, Year year, Frequency freq) { - return freq == Monthly ? - UnitedStates(UnitedStates::GovernmentBond).adjust(Date(1, month, year)) : - Date::nthWeekday(3, Wednesday, month, year); + static auto calendar = UnitedStates(UnitedStates::SOFR); + return calendar.adjust(freq == Monthly ? Date(1, month, year) : + Date::nthWeekday(3, Wednesday, month, year)); } Date getValidSofrEnd(Month month, Year year, Frequency freq) { + static auto calendar = UnitedStates(UnitedStates::SOFR); if (freq == Monthly) { - Calendar dc = UnitedStates(UnitedStates::GovernmentBond); - Date d = dc.endOfMonth(Date(1, month, year)); - return dc.advance(d, 1*Days); + Date d = calendar.endOfMonth(Date(1, month, year)); + return calendar.advance(d, 1*Days); } else { Date d = getValidSofrStart(month, year, freq) + Period(freq); - return Date::nthWeekday(3, Wednesday, d.month(), d.year()); + return calendar.adjust(Date::nthWeekday(3, Wednesday, d.month(), d.year())); } } diff --git a/test-suite/sofrfutures.cpp b/test-suite/sofrfutures.cpp index 880bee605ee..c09185bf0ec 100644 --- a/test-suite/sofrfutures.cpp +++ b/test-suite/sofrfutures.cpp @@ -38,7 +38,6 @@ struct SofrQuotes { Month month; Year year; Real price; - RateAveraging::Type averagingMethod; }; @@ -49,19 +48,19 @@ BOOST_AUTO_TEST_CASE(testBootstrap) { Settings::instance().evaluationDate() = today; const SofrQuotes sofrQuotes[] = { - {Monthly, Oct, 2018, 97.8175, RateAveraging::Simple}, - {Monthly, Nov, 2018, 97.770, RateAveraging::Simple}, - {Monthly, Dec, 2018, 97.685, RateAveraging::Simple}, - {Monthly, Jan, 2019, 97.595, RateAveraging::Simple}, - {Monthly, Feb, 2019, 97.590, RateAveraging::Simple}, - {Monthly, Mar, 2019, 97.525, RateAveraging::Simple}, - {Quarterly, Mar, 2019, 97.440, RateAveraging::Compound}, - {Quarterly, Jun, 2019, 97.295, RateAveraging::Compound}, - {Quarterly, Sep, 2019, 97.220, RateAveraging::Compound}, - {Quarterly, Dec, 2019, 97.170, RateAveraging::Compound}, - {Quarterly, Mar, 2020, 97.160, RateAveraging::Compound}, - {Quarterly, Jun, 2020, 97.165, RateAveraging::Compound}, - {Quarterly, Sep, 2020, 97.175, RateAveraging::Compound}, + {Monthly, Oct, 2018, 97.8175}, + {Monthly, Nov, 2018, 97.770}, + {Monthly, Dec, 2018, 97.685}, + {Monthly, Jan, 2019, 97.595}, + {Monthly, Feb, 2019, 97.590}, + {Monthly, Mar, 2019, 97.525}, + {Quarterly, Mar, 2019, 97.440}, + {Quarterly, Jun, 2019, 97.295}, + {Quarterly, Sep, 2019, 97.220}, + {Quarterly, Dec, 2019, 97.170}, + {Quarterly, Mar, 2020, 97.160}, + {Quarterly, Jun, 2020, 97.165}, + {Quarterly, Sep, 2020, 97.175}, }; ext::shared_ptr index = ext::make_shared(); @@ -113,6 +112,53 @@ BOOST_AUTO_TEST_CASE(testBootstrap) { } } + +BOOST_AUTO_TEST_CASE(testBootstrapWithJuneteen) { + BOOST_TEST_MESSAGE( + "Testing bootstrap over SOFR futures when third Wednesday falls on Juneteenth..."); + + Date today = Date(27, February, 2024); + Settings::instance().evaluationDate() = today; + + const SofrQuotes sofrQuotes[] = { + {Quarterly, Mar, 2024, 97.295}, + {Quarterly, Jun, 2024, 97.220}, + {Quarterly, Sep, 2024, 97.170}, + {Quarterly, Dec, 2024, 97.160}, + {Quarterly, Mar, 2025, 97.165}, + {Quarterly, Jun, 2025, 97.175}, + }; + + ext::shared_ptr index = ext::make_shared(); + + std::vector > helpers; + for (const auto& sofrQuote : sofrQuotes) { + helpers.push_back(ext::make_shared( + sofrQuote.price, sofrQuote.month, sofrQuote.year, sofrQuote.freq)); + } + + ext::shared_ptr > curve = + ext::make_shared >(today, helpers, + Actual365Fixed()); + + ext::shared_ptr sofr = + ext::make_shared(Handle(curve)); + OvernightIndexFuture sf(sofr, Date(20, March, 2024), Date(20, June, 2024)); + + Real expected_price = 97.295; + Real tolerance = 1.0e-9; + + Real error = std::fabs(sf.NPV() - expected_price); + if (error > tolerance) { + BOOST_ERROR("sample futures:\n" + << std::setprecision(8) + << "\n estimated price: " << sf.NPV() + << "\n expected price: " << expected_price + << "\n error: " << error + << "\n tolerance: " << tolerance); + } +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()