diff --git a/ql/termstructures/yield/oisratehelper.cpp b/ql/termstructures/yield/oisratehelper.cpp index da7ea65eceb..1bcc38508c9 100644 --- a/ql/termstructures/yield/oisratehelper.cpp +++ b/ql/termstructures/yield/oisratehelper.cpp @@ -44,14 +44,18 @@ namespace QuantLib { RateAveraging::Type averagingMethod, ext::optional endOfMonth, ext::optional fixedPaymentFrequency, - Calendar fixedCalendar) + Calendar fixedCalendar, + Natural lookbackDays, + Natural lockoutDays, + bool applyObservationShift) : RelativeDateRateHelper(fixedRate), pillarChoice_(pillar), settlementDays_(settlementDays), tenor_(tenor), discountHandle_(std::move(discount)), telescopicValueDates_(telescopicValueDates), paymentLag_(paymentLag), paymentConvention_(paymentConvention), paymentFrequency_(paymentFrequency), paymentCalendar_(std::move(paymentCalendar)), forwardStart_(forwardStart), overnightSpread_(overnightSpread), averagingMethod_(averagingMethod), endOfMonth_(endOfMonth), - fixedPaymentFrequency_(fixedPaymentFrequency), fixedCalendar_(std::move(fixedCalendar)) { + fixedPaymentFrequency_(fixedPaymentFrequency), fixedCalendar_(std::move(fixedCalendar)), + lookbackDays_(lookbackDays), lockoutDays_(lockoutDays), applyObservationShift_(applyObservationShift) { overnightIndex_ = ext::dynamic_pointer_cast(overnightIndex->clone(termStructureHandle_)); @@ -80,7 +84,10 @@ namespace QuantLib { .withPaymentFrequency(paymentFrequency_) .withPaymentCalendar(paymentCalendar_) .withOvernightLegSpread(overnightSpread_) - .withAveragingMethod(averagingMethod_); + .withAveragingMethod(averagingMethod_) + .withLookbackDays(lookbackDays_) + .withLockoutDays(lockoutDays_) + .withObservationShift(applyObservationShift_); if (endOfMonth_) { tmp.withEndOfMonth(*endOfMonth_); } @@ -171,7 +178,10 @@ namespace QuantLib { Spread overnightSpread, ext::optional endOfMonth, ext::optional fixedPaymentFrequency, - const Calendar& fixedCalendar) + const Calendar& fixedCalendar, + Natural lookbackDays, + Natural lockoutDays, + bool applyObservationShift) : RateHelper(fixedRate), discountHandle_(std::move(discount)), telescopicValueDates_(telescopicValueDates), averagingMethod_(averagingMethod) { @@ -197,7 +207,10 @@ namespace QuantLib { .withPaymentFrequency(paymentFrequency) .withPaymentCalendar(paymentCalendar) .withOvernightLegSpread(overnightSpread) - .withAveragingMethod(averagingMethod_); + .withAveragingMethod(averagingMethod_) + .withLookbackDays(lookbackDays) + .withLockoutDays(lockoutDays) + .withObservationShift(applyObservationShift); if (endOfMonth) { tmp.withEndOfMonth(*endOfMonth); } diff --git a/ql/termstructures/yield/oisratehelper.hpp b/ql/termstructures/yield/oisratehelper.hpp index 605acfdf6b2..2df6eda3bd8 100644 --- a/ql/termstructures/yield/oisratehelper.hpp +++ b/ql/termstructures/yield/oisratehelper.hpp @@ -52,7 +52,10 @@ namespace QuantLib { RateAveraging::Type averagingMethod = RateAveraging::Compound, ext::optional endOfMonth = ext::nullopt, ext::optional fixedPaymentFrequency = ext::nullopt, - Calendar fixedCalendar = Calendar()); + Calendar fixedCalendar = Calendar(), + Natural lookbackDays = Null(), + Natural lockoutDays = 0, + bool applyObservationShift = false); //! \name RateHelper interface //@{ Real impliedQuote() const override; @@ -92,6 +95,9 @@ namespace QuantLib { ext::optional endOfMonth_; ext::optional fixedPaymentFrequency_; Calendar fixedCalendar_; + Natural lookbackDays_; + Natural lockoutDays_; + bool applyObservationShift_; }; //! Rate helper for bootstrapping over Overnight Indexed Swap rates @@ -112,7 +118,10 @@ namespace QuantLib { Spread overnightSpread = 0.0, ext::optional endOfMonth = ext::nullopt, ext::optional fixedPaymentFrequency = ext::nullopt, - const Calendar& fixedCalendar = Calendar()); + const Calendar& fixedCalendar = Calendar(), + Natural lookbackDays = Null(), + Natural lockoutDays = 0, + bool applyObservationShift = false); /*! \deprecated Use the overload without forward start. Deprecated in version 1.35. diff --git a/test-suite/overnightindexedswap.cpp b/test-suite/overnightindexedswap.cpp index 9f8cb26af79..9047591b662 100644 --- a/test-suite/overnightindexedswap.cpp +++ b/test-suite/overnightindexedswap.cpp @@ -152,6 +152,25 @@ struct CommonVars { .withAveragingMethod(averagingMethod); } + ext::shared_ptr + makeSwapWithLookback(Period length, + Rate fixedRate, + Integer paymentLag, + Natural lookbackDays, + Natural lockoutDays, + bool applyObservationShift, + bool telescopicValueDates) { + return MakeOIS(length, eoniaIndex, fixedRate, 0 * Days) + .withEffectiveDate(settlement) + .withNominal(nominal) + .withPaymentLag(paymentLag) + .withDiscountingTermStructure(eoniaTermStructure) + .withLookbackDays(lookbackDays) + .withLockoutDays(lockoutDays) + .withObservationShift(applyObservationShift) + .withTelescopicValueDates(telescopicValueDates); + } + CommonVars() { type = Swap::Payer; settlementDays = 2; @@ -389,6 +408,131 @@ BOOST_AUTO_TEST_CASE(testBootstrapWithTelescopicDatesAndArithmeticAverage) { testBootstrap(true, RateAveraging::Simple, 1.0e-5); } + +void testBootstrapWithLookback(Natural lookbackDays, + Natural lockoutDays, + bool applyObservationShift, + bool telescopicValueDates, + Natural paymentLag) { + + CommonVars vars; + + std::vector > eoniaHelpers; + + ext::shared_ptr eonia(new Eonia); + + for (auto& i : eoniaSwapData) { + Real rate = 0.01 * i.rate; + auto quote = ext::make_shared(rate); + Period term = i.n * i.unit; + auto helper = ext::make_shared(i.settlementDays, + term, + Handle(quote), + eonia, + Handle(), + telescopicValueDates, + paymentLag, + Following, + Annual, + Calendar(), + 0 * Days, + 0.0, + Pillar::LastRelevantDate, + Date(), + RateAveraging::Compound, + ext::nullopt, + ext::nullopt, + Calendar(), + lookbackDays, + lockoutDays, + applyObservationShift); + eoniaHelpers.push_back(helper); + } + + auto eoniaTS = ext::make_shared>(vars.today, eoniaHelpers, Actual365Fixed()); + + vars.eoniaTermStructure.linkTo(eoniaTS); + + // test curve consistency + for (auto& i : eoniaSwapData) { + Rate expected = i.rate / 100; + Period term = i.n * i.unit; + ext::shared_ptr swap = + vars.makeSwapWithLookback(term, 0.0, paymentLag, lookbackDays, lockoutDays, + applyObservationShift, telescopicValueDates); + Rate calculated = swap->fairRate(); + Rate error = std::fabs(expected-calculated); + Real tolerance = 1e-8; + + if (error>tolerance) + BOOST_FAIL("curve inconsistency:" << std::setprecision(10) << + "\n swap length: " << term << + "\n quoted rate: " << expected << + "\n calculated rate: " << calculated << + "\n error: " << error << + "\n tolerance: " << tolerance); + } +} + + + +BOOST_AUTO_TEST_CASE(testBootstrapWithLookbackDays) { + BOOST_TEST_MESSAGE("Testing Eonia-swap curve building with lookback days..."); + + auto lookbackDays = 2; + auto lockoutDays = 0; + auto applyObservationShift = false; + auto paymentLag = 2; + + testBootstrapWithLookback(lookbackDays, lockoutDays, applyObservationShift, false, paymentLag); + + BOOST_CHECK_EXCEPTION( + testBootstrapWithLookback(lookbackDays, lockoutDays, applyObservationShift, true, paymentLag), + Error, ExpectedErrorMessage("Telescopic formula cannot be applied")); +} + +BOOST_AUTO_TEST_CASE(testBootstrapWithLookbackDaysAndShift) { + BOOST_TEST_MESSAGE("Testing Eonia-swap curve building with lookback days and observation shift..."); + + auto lookbackDays = 2; + auto lockoutDays = 0; + auto applyObservationShift = true; + auto paymentLag = 2; + + testBootstrapWithLookback(lookbackDays, lockoutDays, applyObservationShift, false, paymentLag); + + testBootstrapWithLookback(lookbackDays, lockoutDays, applyObservationShift, true, paymentLag); +} + +BOOST_AUTO_TEST_CASE(testBootstrapWithLockoutDays) { + BOOST_TEST_MESSAGE("Testing Eonia-swap curve building with lookback and lockout days..."); + + auto lookbackDays = 2; + auto lockoutDays = 2; + auto applyObservationShift = false; + auto paymentLag = 0; + + testBootstrapWithLookback(lookbackDays, lockoutDays, applyObservationShift, false, paymentLag); + + BOOST_CHECK_EXCEPTION( + testBootstrapWithLookback(lookbackDays, lockoutDays, applyObservationShift, true, paymentLag), + Error, ExpectedErrorMessage("Telescopic formula cannot be applied")); +} + +BOOST_AUTO_TEST_CASE(testBootstrapWithLockoutDaysAndShift) { + BOOST_TEST_MESSAGE("Testing Eonia-swap curve building with lookback and lockout days and observation shift..."); + + auto lookbackDays = 2; + auto lockoutDays = 2; + auto applyObservationShift = true; + auto paymentLag = 0; + + testBootstrapWithLookback(lookbackDays, lockoutDays, applyObservationShift, false, paymentLag); + + testBootstrapWithLookback(lookbackDays, lockoutDays, applyObservationShift, true, paymentLag); +} + + BOOST_AUTO_TEST_CASE(testSeasonedSwaps) { BOOST_TEST_MESSAGE("Testing seasoned Eonia-swap calculation...");