From 4bbf6916f8fbd60907e312820c61a2bbc318776c Mon Sep 17 00:00:00 2001 From: Eugene Toder Date: Fri, 25 Oct 2024 09:59:47 -0400 Subject: [PATCH 1/2] Add SwapRateHelper constructor with fixed dates Also known as "DatedSwapRateHelper". Similar to the new constructor for OISRateHelper. --- ql/instruments/makevanillaswap.cpp | 3 ++- ql/termstructures/yield/ratehelpers.cpp | 33 ++++++++++++++++++++++++- ql/termstructures/yield/ratehelpers.hpp | 20 +++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/ql/instruments/makevanillaswap.cpp b/ql/instruments/makevanillaswap.cpp index ac329f29f74..0fe6daf71c3 100644 --- a/ql/instruments/makevanillaswap.cpp +++ b/ql/instruments/makevanillaswap.cpp @@ -217,7 +217,8 @@ namespace QuantLib { MakeVanillaSwap& MakeVanillaSwap::withTerminationDate(const Date& terminationDate) { terminationDate_ = terminationDate; - swapTenor_ = Period(); + if (terminationDate != Date()) + swapTenor_ = Period(); return *this; } diff --git a/ql/termstructures/yield/ratehelpers.cpp b/ql/termstructures/yield/ratehelpers.cpp index 199f0415967..62b7b80609b 100644 --- a/ql/termstructures/yield/ratehelpers.cpp +++ b/ql/termstructures/yield/ratehelpers.cpp @@ -556,7 +556,11 @@ namespace QuantLib { fixedDayCount_(std::move(fixedDayCount)), spread_(std::move(spread)), endOfMonth_(endOfMonth), fwdStart_(fwdStart), discountHandle_(std::move(discount)), useIndexedCoupons_(useIndexedCoupons) { + initialize(iborIndex, customPillarDate); + } + void SwapRateHelper::initialize(const ext::shared_ptr& iborIndex, + Date customPillarDate) { // take fixing into account iborIndex_ = iborIndex->clone(termStructureHandle_); // We want to be notified of changes of fixings, but we don't @@ -603,6 +607,31 @@ namespace QuantLib { std::move(fixedDayCount), iborIndex, std::move(spread), fwdStart, std::move(discount), settlementDays, pillarChoice, customPillarDate, endOfMonth, useIndexedCoupons) {} + SwapRateHelper::SwapRateHelper(const Handle& rate, + const Date& startDate, + const Date& endDate, + Calendar calendar, + Frequency fixedFrequency, + BusinessDayConvention fixedConvention, + DayCounter fixedDayCount, + const ext::shared_ptr& iborIndex, + Handle spread, + Handle discount, + Pillar::Choice pillarChoice, + Date customPillarDate, + bool endOfMonth, + const ext::optional& useIndexedCoupons) + : RelativeDateRateHelper(rate, false), startDate_(startDate), endDate_(endDate), + pillarChoice_(pillarChoice), calendar_(std::move(calendar)), + fixedConvention_(fixedConvention), fixedFrequency_(fixedFrequency), + fixedDayCount_(std::move(fixedDayCount)), spread_(std::move(spread)), endOfMonth_(endOfMonth), + discountHandle_(std::move(discount)), useIndexedCoupons_(useIndexedCoupons) { + QL_REQUIRE(fixedFrequency != Once, + "fixedFrequency == Once is not supported when passing explicit " + "startDate and endDate"); + initialize(iborIndex, customPillarDate); + } + void SwapRateHelper::initializeDates() { // 1. do not pass the spread here, as it might be a Quote @@ -610,7 +639,9 @@ namespace QuantLib { // 2. input discount curve Handle might be empty now but it could // be assigned a curve later; use a RelinkableHandle here swap_ = MakeVanillaSwap(tenor_, iborIndex_, 0.0, fwdStart_) - .withSettlementDays(settlementDays_) + .withSettlementDays(settlementDays_) // resets effectiveDate + .withEffectiveDate(startDate_) + .withTerminationDate(endDate_) .withDiscountingTermStructure(discountRelinkableHandle_) .withFixedLegDayCount(fixedDayCount_) .withFixedLegTenor(fixedFrequency_ == Once ? tenor_ : Period(fixedFrequency_)) diff --git a/ql/termstructures/yield/ratehelpers.hpp b/ql/termstructures/yield/ratehelpers.hpp index 3cd68a686f4..437d6dd0f01 100644 --- a/ql/termstructures/yield/ratehelpers.hpp +++ b/ql/termstructures/yield/ratehelpers.hpp @@ -313,6 +313,23 @@ namespace QuantLib { Date customPillarDate = Date(), bool endOfMonth = false, const ext::optional& useIndexedCoupons = ext::nullopt); + SwapRateHelper(const Handle& rate, + const Date& startDate, + const Date& endDate, + Calendar calendar, + // fixed leg + Frequency fixedFrequency, + BusinessDayConvention fixedConvention, + DayCounter fixedDayCount, + // floating leg + const ext::shared_ptr& iborIndex, + Handle spread = {}, + // exogenous discounting curve + Handle discountingCurve = {}, + Pillar::Choice pillar = Pillar::LastRelevantDate, + Date customPillarDate = Date(), + bool endOfMonth = false, + const ext::optional& useIndexedCoupons = ext::nullopt); //! \name RateHelper interface //@{ Real impliedQuote() const override; @@ -330,9 +347,12 @@ namespace QuantLib { void accept(AcyclicVisitor&) override; //@} protected: + void initialize(const ext::shared_ptr& iborIndex, + Date customPillarDate); void initializeDates() override; Natural settlementDays_; Period tenor_; + Date startDate_, endDate_; Pillar::Choice pillarChoice_; Calendar calendar_; BusinessDayConvention fixedConvention_; From 90c9cbaf19ad73e202e9c862d71a75dea8a9c63e Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Mon, 28 Oct 2024 08:39:58 +0100 Subject: [PATCH 2/2] Add basic consistency test --- test-suite/piecewiseyieldcurve.cpp | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test-suite/piecewiseyieldcurve.cpp b/test-suite/piecewiseyieldcurve.cpp index 481609e004d..0302bd6d432 100644 --- a/test-suite/piecewiseyieldcurve.cpp +++ b/test-suite/piecewiseyieldcurve.cpp @@ -1563,6 +1563,69 @@ BOOST_AUTO_TEST_CASE(testSwapHelpersWithOnceFrequency) { } +BOOST_AUTO_TEST_CASE(testDatedSwapHelpers) { + BOOST_TEST_MESSAGE("Testing dated swap rate helpers..."); + + Date today { 28, October, 2024 }; + Settings::instance().evaluationDate() = today; + + std::tuple swapData[] = { + {{1, November, 2024}, {1, November, 2025}, 4.54 }, + {{15, October, 2024}, {15, October, 2026}, 4.63 }, + {{28, October, 2024}, {1, November, 2029}, 4.99 }, + {{4, November, 2024}, {4, November, 2034}, 5.47 }, + {{11, October, 2024}, {11, October, 2044}, 5.89 } + }; + + auto euribor6m = ext::make_shared(); + euribor6m->addFixing({9, October, 2024}, 0.0447); + euribor6m->addFixing({11, October, 2024}, 0.045); + euribor6m->addFixing({24, October, 2024}, 0.0442); + + auto calendar = TARGET(); + auto fixedLegFrequency = Annual; + auto fixedLegConvention = Unadjusted; + auto fixedLegDayCounter = Thirty360(Thirty360::BondBasis); + + std::vector> helpers; + for (auto [start, end, q] : swapData) { + Handle r(ext::make_shared(q/100)); + helpers.push_back(ext::make_shared( + r, start, end, + calendar, + fixedLegFrequency, fixedLegConvention, + fixedLegDayCounter, euribor6m)); + } + + auto curve = ext::make_shared>( + today, helpers, Actual365Fixed()); + Handle h(curve); + euribor6m = ext::make_shared(h); + + for (auto [start, end, q] : swapData) { + VanillaSwap swap = MakeVanillaSwap(Period(), euribor6m, 0.0) + .withEffectiveDate(start) + .withTerminationDate(end) + .withFixedLegDayCount(fixedLegDayCounter) + .withFixedLegTenor(Period(fixedLegFrequency)) + .withFixedLegConvention(fixedLegConvention) + .withFixedLegTerminationDateConvention(fixedLegConvention); + + Rate expectedRate = q/100, + estimatedRate = swap.fairRate(); + Spread error = std::fabs(expectedRate-estimatedRate); + Real tolerance = 1e-9; + if (error > tolerance) { + BOOST_ERROR("swap from " << start << " to " << end << ":\n" + << std::setprecision(8) + << "\n estimated rate: " << io::rate(estimatedRate) + << "\n expected rate: " << io::rate(expectedRate) + << "\n error: " << io::rate(error) + << "\n tolerance: " << io::rate(tolerance)); + } + } +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()