From f97743e36bd4d7ff1caf3e63f9b6810ecd8c3cf8 Mon Sep 17 00:00:00 2001 From: Kiril1Shevtsov Date: Wed, 11 Nov 2026 16:43:52 +0300 Subject: [PATCH] Restore shared pricer state in capped YoY coupons --- ql/cashflows/capflooredinflationcoupon.cpp | 6 ++ test-suite/inflationcapflooredcoupon.cpp | 67 ++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/ql/cashflows/capflooredinflationcoupon.cpp b/ql/cashflows/capflooredinflationcoupon.cpp index 37c80bb29f3..5ccd04578e5 100644 --- a/ql/cashflows/capflooredinflationcoupon.cpp +++ b/ql/cashflows/capflooredinflationcoupon.cpp @@ -96,6 +96,12 @@ namespace QuantLib { if (isFloored_ || isCapped_) { QL_REQUIRE(couponPricer, "pricer not set"); + // The underlying rate might be cached while a shared pricer has + // since been initialized for a different coupon. + if (underlying_ != nullptr) + couponPricer->initialize(*underlying_); + else + couponPricer->initialize(*this); } Rate floorletRate = isFloored_ ? couponPricer->floorletRate(effectiveFloor()) : 0.0; diff --git a/test-suite/inflationcapflooredcoupon.cpp b/test-suite/inflationcapflooredcoupon.cpp index 001ddbbbc53..8b15991200b 100644 --- a/test-suite/inflationcapflooredcoupon.cpp +++ b/test-suite/inflationcapflooredcoupon.cpp @@ -21,6 +21,7 @@ #include "toplevelfixture.hpp" #include "utilities.hpp" +#include #include #include #include @@ -40,12 +41,16 @@ #include #include #include +#include #include #include +#include #include #include #include #include +#include +#include using namespace QuantLib; @@ -676,6 +681,68 @@ BOOST_AUTO_TEST_CASE(testDecomposition) { } } +BOOST_AUTO_TEST_CASE(testSharedPricer) { + BOOST_TEST_MESSAGE("Testing inflation capped/floored coupons with a shared pricer..."); + + Settings::instance().evaluationDate() = Date(1, January, 2027); + auto index = ext::make_shared(); + index->addFixing(Date(1, November, 2024), 0.05); + index->addFixing(Date(1, November, 2025), 0.02); + + Handle volatility( + ext::make_shared( + 0.01, 0, NullCalendar(), Unadjusted, Actual365Fixed(), + Period(2, Months), Monthly, false)); + + for (bool wrapped : {false, true}) { + for (Real gearing : std::vector{-1.0, 1.0}) { + for (Size type = 0; type < 4; ++type) { + const Spread spread = 0.001; + const Rate cap = (type & 1) != 0 ? + (gearing > 0.0 ? Real(0.03) : Real(-0.025)) : Null(); + const Rate floor = (type & 2) != 0 ? + (gearing > 0.0 ? Real(0.025) : Real(-0.03)) : Null(); + + auto pricer = ext::make_shared( + volatility, Handle()); + auto makeCoupon = [&](Year year) { + const Date start(1, January, year - 1), end(1, January, year); + ext::shared_ptr coupon; + if (wrapped) { + auto underlying = ext::make_shared( + end, 100.0, start, end, 0, index, Period(2, Months), + CPI::Flat, Actual365Fixed(), gearing, spread); + coupon = ext::make_shared( + underlying, cap, floor); + } else { + coupon = ext::make_shared( + end, 100.0, start, end, 0, index, Period(2, Months), + CPI::Flat, Actual365Fixed(), gearing, spread, cap, floor); + } + coupon->setPricer(pricer); + return coupon; + }; + + std::vector> coupons = { + makeCoupon(2025), makeCoupon(2026)}; + const std::vector fixings = {0.05, 0.02}; + for (Size i : {0, 1, 0, 1, 1, 0}) { + Real expected = gearing * fixings[i] + spread; + if (cap != Null()) + expected = std::min(expected, cap); + if (floor != Null()) + expected = std::max(expected, floor); + + BOOST_TEST_CONTEXT("wrapped=" << wrapped << ", gearing=" << gearing + << ", type=" << type << ", coupon=" << i) { + QL_CHECK_SMALL(coupons[i]->rate() - expected, 1.0e-13); + } + } + } + } + } +} + BOOST_AUTO_TEST_CASE(testInstrumentEquality) { BOOST_TEST_MESSAGE("Testing inflation capped/floored coupon against"