diff --git a/ql/cashflows/capflooredinflationcoupon.cpp b/ql/cashflows/capflooredinflationcoupon.cpp index 5bdec54ecb6..cb2a5fbe6a6 100644 --- a/ql/cashflows/capflooredinflationcoupon.cpp +++ b/ql/cashflows/capflooredinflationcoupon.cpp @@ -64,6 +64,7 @@ namespace QuantLib { underlying->fixingDays(), underlying->yoyIndex(), underlying->observationLag(), + underlying->interpolation(), underlying->dayCounter(), underlying->gearing(), underlying->spread(), diff --git a/ql/cashflows/capflooredinflationcoupon.hpp b/ql/cashflows/capflooredinflationcoupon.hpp index 2a2491507fc..3b48d600c38 100644 --- a/ql/cashflows/capflooredinflationcoupon.hpp +++ b/ql/cashflows/capflooredinflationcoupon.hpp @@ -79,6 +79,7 @@ namespace QuantLib { Natural fixingDays, const ext::shared_ptr& index, const Period& observationLag, + const CPI::InterpolationType interpolation, const DayCounter& dayCounter, Real gearing = 1.0, Spread spread = 0.0, @@ -87,12 +88,36 @@ namespace QuantLib { const Date& refPeriodStart = Date(), const Date& refPeriodEnd = Date()) : YoYInflationCoupon(paymentDate, nominal, startDate, endDate, - fixingDays, index, observationLag, dayCounter, - gearing, spread, refPeriodStart, refPeriodEnd), + fixingDays, index, observationLag, interpolation, + dayCounter, gearing, spread, + refPeriodStart, refPeriodEnd), isFloored_(false), isCapped_(false) { setCommon(cap, floor); } + /*! \deprecated Use the overload that passes an interpolation type instead. + Deprecated in version 1.36. + */ + [[deprecated("Use the overload that passes an interpolation type instead")]] + CappedFlooredYoYInflationCoupon(const Date& paymentDate, + Real nominal, + const Date& startDate, + const Date& endDate, + Natural fixingDays, + const ext::shared_ptr& index, + const Period& observationLag, + const DayCounter& dayCounter, + Real gearing = 1.0, + Spread spread = 0.0, + const Rate cap = Null(), + const Rate floor = Null(), + const Date& refPeriodStart = Date(), + const Date& refPeriodEnd = Date()) + : CappedFlooredYoYInflationCoupon(paymentDate, nominal, startDate, endDate, + fixingDays, index, observationLag, CPI::AsIndex, + dayCounter, gearing, spread, cap, floor, + refPeriodStart, refPeriodEnd) {} + //! \name augmented Coupon interface //@{ //! swap(let) rate diff --git a/ql/cashflows/cpicoupon.cpp b/ql/cashflows/cpicoupon.cpp index 16e66f8e044..4a33ef1fc0f 100644 --- a/ql/cashflows/cpicoupon.cpp +++ b/ql/cashflows/cpicoupon.cpp @@ -166,33 +166,14 @@ namespace QuantLib { } Real CPICashFlow::baseFixing() const { - return baseFixing_; + if (baseFixing_ != Null()) + return baseFixing_; + else + return CPI::laggedFixing(cpiIndex(), baseDate(), 0 * Months, interpolation_); } Real CPICashFlow::indexFixing() const { - if (observationDate_ != Date()) { - return CPI::laggedFixing(cpiIndex(), observationDate_, observationLag_, interpolation_); - } else { - // we get to this branch when the deprecated constructor was used; it will be phased out - return CPI::laggedFixing(cpiIndex(), fixingDate() + observationLag_, observationLag_, - interpolation_); - } - } - - Real CPICashFlow::amount() const { - Rate I0 = baseFixing(); - - // If BaseFixing is null, use the observed index fixing - if (I0 == Null()) { - I0 = IndexedCashFlow::baseFixing(); - } - - Rate I1 = indexFixing(); - - if (growthOnly()) - return notional() * (I1 / I0 - 1.0); - else - return notional() * (I1 / I0); + return CPI::laggedFixing(cpiIndex(), observationDate_, observationLag_, interpolation_); } CPILeg::CPILeg(Schedule schedule, diff --git a/ql/cashflows/cpicoupon.hpp b/ql/cashflows/cpicoupon.hpp index 8ffe3719b99..0267ece0669 100644 --- a/ql/cashflows/cpicoupon.hpp +++ b/ql/cashflows/cpicoupon.hpp @@ -191,8 +191,6 @@ namespace QuantLib { Real indexFixing() const override; - Real amount() const override; - protected: Real baseFixing_; Date observationDate_; diff --git a/ql/cashflows/indexedcashflow.cpp b/ql/cashflows/indexedcashflow.cpp index 867727b3cc6..2490a65fdb3 100644 --- a/ql/cashflows/indexedcashflow.cpp +++ b/ql/cashflows/indexedcashflow.cpp @@ -45,8 +45,9 @@ namespace QuantLib { Real I1 = indexFixing(); if (growthOnly_) - amount_ = notional_ * (I1 / I0 - 1.0); + amount_ = notional() * (I1 / I0 - 1.0); else - amount_ = notional_ * (I1 / I0); + amount_ = notional() * (I1 / I0); } + } diff --git a/ql/cashflows/yoyinflationcoupon.cpp b/ql/cashflows/yoyinflationcoupon.cpp index 9ce0e8cec34..3fe3ec2665c 100644 --- a/ql/cashflows/yoyinflationcoupon.cpp +++ b/ql/cashflows/yoyinflationcoupon.cpp @@ -28,21 +28,39 @@ namespace QuantLib { YoYInflationCoupon:: YoYInflationCoupon(const Date& paymentDate, - Real nominal, - const Date& startDate, - const Date& endDate, - Natural fixingDays, - const ext::shared_ptr& yoyIndex, - const Period& observationLag, - const DayCounter& dayCounter, - Real gearing, - Spread spread, - const Date& refPeriodStart, - const Date& refPeriodEnd) + Real nominal, + const Date& startDate, + const Date& endDate, + Natural fixingDays, + const ext::shared_ptr& yoyIndex, + const Period& observationLag, + CPI::InterpolationType interpolation, + const DayCounter& dayCounter, + Real gearing, + Spread spread, + const Date& refPeriodStart, + const Date& refPeriodEnd) : InflationCoupon(paymentDate, nominal, startDate, endDate, - fixingDays, yoyIndex, observationLag, - dayCounter, refPeriodStart, refPeriodEnd), - yoyIndex_(yoyIndex), gearing_(gearing), spread_(spread) {} + fixingDays, yoyIndex, observationLag, + dayCounter, refPeriodStart, refPeriodEnd), + yoyIndex_(yoyIndex), interpolation_(interpolation), gearing_(gearing), spread_(spread) {} + + YoYInflationCoupon:: + YoYInflationCoupon(const Date& paymentDate, + Real nominal, + const Date& startDate, + const Date& endDate, + Natural fixingDays, + const ext::shared_ptr& yoyIndex, + const Period& observationLag, + const DayCounter& dayCounter, + Real gearing, + Spread spread, + const Date& refPeriodStart, + const Date& refPeriodEnd) + : YoYInflationCoupon(paymentDate, nominal, startDate, endDate, + fixingDays, yoyIndex, observationLag, CPI::AsIndex, + dayCounter, gearing, spread, refPeriodStart, refPeriodEnd) {} void YoYInflationCoupon::accept(AcyclicVisitor& v) { @@ -53,20 +71,30 @@ namespace QuantLib { InflationCoupon::accept(v); } - bool YoYInflationCoupon::checkPricerImpl( const ext::shared_ptr&pricer) const { return static_cast( ext::dynamic_pointer_cast(pricer)); } + Rate YoYInflationCoupon::indexFixing() const { + return CPI::laggedYoYRate(yoyIndex(), accrualEndDate(), observationLag(), interpolation_); + } + yoyInflationLeg::yoyInflationLeg(Schedule schedule, Calendar paymentCalendar, ext::shared_ptr index, - const Period& observationLag) + const Period& observationLag, + CPI::InterpolationType interpolation) : schedule_(std::move(schedule)), index_(std::move(index)), observationLag_(observationLag), - paymentCalendar_(std::move(paymentCalendar)) {} + interpolation_(interpolation), paymentCalendar_(std::move(paymentCalendar)) {} + + yoyInflationLeg::yoyInflationLeg(Schedule schedule, + Calendar paymentCalendar, + ext::shared_ptr index, + const Period& observationLag) + : yoyInflationLeg(schedule, paymentCalendar, index, observationLag, CPI::AsIndex) {} yoyInflationLeg& yoyInflationLeg::withNotionals(Real notional) { @@ -196,6 +224,7 @@ namespace QuantLib { detail::get(fixingDays_, i, 0), index_, observationLag_, + interpolation_, paymentDayCounter_, detail::get(gearings_, i, 1.0), detail::get(spreads_, i, 0.0), @@ -208,6 +237,7 @@ namespace QuantLib { detail::get(fixingDays_, i, 0), index_, observationLag_, + interpolation_, paymentDayCounter_, detail::get(gearings_, i, 1.0), detail::get(spreads_, i, 0.0), diff --git a/ql/cashflows/yoyinflationcoupon.hpp b/ql/cashflows/yoyinflationcoupon.hpp index c924de770e3..c5f6fbee8b7 100644 --- a/ql/cashflows/yoyinflationcoupon.hpp +++ b/ql/cashflows/yoyinflationcoupon.hpp @@ -33,20 +33,37 @@ namespace QuantLib { //! %Coupon paying a YoY-inflation type index class YoYInflationCoupon : public InflationCoupon { - public: + public: + YoYInflationCoupon(const Date& paymentDate, + Real nominal, + const Date& startDate, + const Date& endDate, + Natural fixingDays, + const ext::shared_ptr& index, + const Period& observationLag, + CPI::InterpolationType interpolation, + const DayCounter& dayCounter, + Real gearing = 1.0, + Spread spread = 0.0, + const Date& refPeriodStart = Date(), + const Date& refPeriodEnd = Date()); + + /*! \deprecated Use the overload that passes an interpolation type instead. + Deprecated in version 1.36. + */ + [[deprecated("Use the overload that passes an interpolation type instead")]] YoYInflationCoupon(const Date& paymentDate, - Real nominal, - const Date& startDate, - const Date& endDate, - Natural fixingDays, - const ext::shared_ptr& index, - const Period& observationLag, - const DayCounter& dayCounter, - Real gearing = 1.0, - Spread spread = 0.0, - const Date& refPeriodStart = Date(), - const Date& refPeriodEnd = Date() - ); + Real nominal, + const Date& startDate, + const Date& endDate, + Natural fixingDays, + const ext::shared_ptr& index, + const Period& observationLag, + const DayCounter& dayCounter, + Real gearing = 1.0, + Spread spread = 0.0, + const Date& refPeriodStart = Date(), + const Date& refPeriodEnd = Date()); //! \name Inspectors //@{ @@ -55,20 +72,23 @@ namespace QuantLib { //! spread paid over the fixing of the underlying index Spread spread() const { return spread_; } + Rate indexFixing() const override; + Rate adjustedFixing() const; const ext::shared_ptr& yoyIndex() const; - + CPI::InterpolationType interpolation() const; //@} + //! \name Visitability //@{ void accept(AcyclicVisitor&) override; //@} - private: + private: ext::shared_ptr yoyIndex_; - protected: - + CPI::InterpolationType interpolation_; + protected: Real gearing_; Spread spread_; bool checkPricerImpl(const ext::shared_ptr&) const override; @@ -79,6 +99,10 @@ namespace QuantLib { return yoyIndex_; } + inline CPI::InterpolationType YoYInflationCoupon::interpolation() const { + return interpolation_; + } + inline Rate YoYInflationCoupon::adjustedFixing() const { return (rate()-spread())/gearing(); } @@ -87,9 +111,17 @@ namespace QuantLib { //! Helper class building a sequence of capped/floored yoy inflation coupons - //! payoff is: spread + gearing x index class yoyInflationLeg { public: + yoyInflationLeg(Schedule schedule, + Calendar cal, + ext::shared_ptr index, + const Period& observationLag, + CPI::InterpolationType interpolation); + /*! \deprecated Use the overload that passes an interpolation type instead. + Deprecated in version 1.36. + */ + [[deprecated("Use the overload that passes an interpolation type instead")]] yoyInflationLeg(Schedule schedule, Calendar cal, ext::shared_ptr index, @@ -113,6 +145,7 @@ namespace QuantLib { Schedule schedule_; ext::shared_ptr index_; Period observationLag_; + CPI::InterpolationType interpolation_; std::vector notionals_; DayCounter paymentDayCounter_; BusinessDayConvention paymentAdjustment_ = ModifiedFollowing; diff --git a/ql/cashflows/zeroinflationcashflow.cpp b/ql/cashflows/zeroinflationcashflow.cpp index 90eb676c91e..26c79fcd3c3 100644 --- a/ql/cashflows/zeroinflationcashflow.cpp +++ b/ql/cashflows/zeroinflationcashflow.cpp @@ -35,25 +35,15 @@ namespace QuantLib { : IndexedCashFlow(notional, index, startDate - observationLag, endDate - observationLag, paymentDate, growthOnly), - zeroInflationIndex_(index), observationInterpolation_(observationInterpolation), + zeroInflationIndex_(index), interpolation_(observationInterpolation), startDate_(startDate), endDate_(endDate), observationLag_(observationLag) {} - void ZeroInflationCashFlow::performCalculations() const { - - Real I0, I1; - - if (observationInterpolation_ == CPI::AsIndex) { - I0 = zeroInflationIndex_->fixing(baseDate()); - I1 = zeroInflationIndex_->fixing(fixingDate()); - } else { - I0 = CPI::laggedFixing(zeroInflationIndex_, startDate_, observationLag_, observationInterpolation_); - I1 = CPI::laggedFixing(zeroInflationIndex_, endDate_, observationLag_, observationInterpolation_); - } + Real ZeroInflationCashFlow::baseFixing() const { + return CPI::laggedFixing(zeroInflationIndex_, startDate_, observationLag_, interpolation_); + } - if (growthOnly()) - amount_ = notional() * (I1 / I0 - 1.0); - else - amount_ = notional() * (I1 / I0); + Real ZeroInflationCashFlow::indexFixing() const { + return CPI::laggedFixing(zeroInflationIndex_, endDate_, observationLag_, interpolation_); } void ZeroInflationCashFlow::accept(AcyclicVisitor& v) { diff --git a/ql/cashflows/zeroinflationcashflow.hpp b/ql/cashflows/zeroinflationcashflow.hpp index 316e34c6fc8..0b00e4f8eed 100644 --- a/ql/cashflows/zeroinflationcashflow.hpp +++ b/ql/cashflows/zeroinflationcashflow.hpp @@ -55,14 +55,16 @@ namespace QuantLib { return zeroInflationIndex_; } CPI::InterpolationType observationInterpolation() const { - return observationInterpolation_; + return interpolation_; } //@} - //! \name CashFlow interface + //! \name ZeroInflationCashFlow interface //@{ - void performCalculations() const override; + Real baseFixing() const override; + Real indexFixing() const override; //@} + //! \name Visitability //@{ void accept(AcyclicVisitor&) override; @@ -70,7 +72,7 @@ namespace QuantLib { private: ext::shared_ptr zeroInflationIndex_; - CPI::InterpolationType observationInterpolation_; + CPI::InterpolationType interpolation_; Date startDate_, endDate_; Period observationLag_; }; diff --git a/ql/experimental/inflation/interpolatedyoyoptionletstripper.hpp b/ql/experimental/inflation/interpolatedyoyoptionletstripper.hpp index 7fdabc40eef..3c6047432fb 100644 --- a/ql/experimental/inflation/interpolatedyoyoptionletstripper.hpp +++ b/ql/experimental/inflation/interpolatedyoyoptionletstripper.hpp @@ -58,8 +58,7 @@ namespace QuantLib { //@} protected: - mutable std::vector > - volCurves_; + mutable std::vector > volCurves_; // used to set up the first point on each vol curve // using assumptions on unobserved vols at start @@ -114,7 +113,7 @@ namespace QuantLib { capfloor_ = MakeYoYInflationCapFloor(type, anIndex, (Size)std::floor(0.5+surf->timeFromReference(surf->minMaturity())), - surf->calendar(), lag) + surf->calendar(), lag, CPI::AsIndex) .withNominal(10000.0) .withStrike(K); @@ -238,7 +237,8 @@ namespace QuantLib { lag_, dc, cal, fixingDays_, - anIndex, K, nT, p_))); + anIndex, CPI::Flat, + K, nT, p_))); ext::shared_ptr yoyVolBLACK( new ConstantYoYOptionletVolatility(found, settlementDays, diff --git a/ql/experimental/inflation/yoycapfloortermpricesurface.cpp b/ql/experimental/inflation/yoycapfloortermpricesurface.cpp index d9364a5627e..47f36437bb8 100644 --- a/ql/experimental/inflation/yoycapfloortermpricesurface.cpp +++ b/ql/experimental/inflation/yoycapfloortermpricesurface.cpp @@ -26,7 +26,7 @@ namespace QuantLib { Natural fixingDays, const Period& lag, const ext::shared_ptr& yii, - Rate baseRate, + CPI::InterpolationType interpolation, Handle nominal, const DayCounter& dc, const Calendar& cal, @@ -39,7 +39,7 @@ namespace QuantLib { : TermStructure(0, cal, dc), fixingDays_(fixingDays), bdc_(bdc), yoyIndex_(yii), observationLag_(lag), nominalTS_(std::move(nominal)), cStrikes_(cStrikes), fStrikes_(fStrikes), cfMaturities_(cfMaturities), cPrice_(cPrice), - fPrice_(fPrice), indexIsInterpolated_(yii->interpolated()) { + fPrice_(fPrice), indexIsInterpolated_(detail::CPI::isInterpolated(interpolation, yoyIndex_)) { // data consistency checking, enough data? QL_REQUIRE(fStrikes_.size() > 1, "not enough floor strikes"); @@ -100,6 +100,23 @@ namespace QuantLib { "cfStrikes not increasing"); } + YoYCapFloorTermPriceSurface::YoYCapFloorTermPriceSurface( + Natural fixingDays, + const Period& yyLag, + const ext::shared_ptr& yii, + Rate baseRate, + Handle nominal, + const DayCounter& dc, + const Calendar& cal, + const BusinessDayConvention& bdc, + const std::vector& cStrikes, + const std::vector& fStrikes, + const std::vector& cfMaturities, + const Matrix& cPrice, + const Matrix& fPrice) + : YoYCapFloorTermPriceSurface(fixingDays, yyLag, yii, CPI::AsIndex, nominal, dc, cal, bdc, + cStrikes, fStrikes, cfMaturities, cPrice, fPrice) {} + Date YoYCapFloorTermPriceSurface::yoyOptionDateFromTenor(const Period& p) const { return referenceDate() + p; diff --git a/ql/experimental/inflation/yoycapfloortermpricesurface.hpp b/ql/experimental/inflation/yoycapfloortermpricesurface.hpp index 8b749cc51ab..92aa0a5f66c 100644 --- a/ql/experimental/inflation/yoycapfloortermpricesurface.hpp +++ b/ql/experimental/inflation/yoycapfloortermpricesurface.hpp @@ -41,6 +41,24 @@ namespace QuantLib { */ class YoYCapFloorTermPriceSurface : public TermStructure { public: + YoYCapFloorTermPriceSurface(Natural fixingDays, + const Period& yyLag, + const ext::shared_ptr& yii, + CPI::InterpolationType interpolation, + Handle nominal, + const DayCounter& dc, + const Calendar& cal, + const BusinessDayConvention& bdc, + const std::vector& cStrikes, + const std::vector& fStrikes, + const std::vector& cfMaturities, + const Matrix& cPrice, + const Matrix& fPrice); + + /*! \deprecated Use the overload that passes an interpolation type instead. + Deprecated in version 1.36. + */ + [[deprecated("Use the overload that passes an interpolation type instead")]] YoYCapFloorTermPriceSurface(Natural fixingDays, const Period& yyLag, const ext::shared_ptr& yii, @@ -149,6 +167,27 @@ namespace QuantLib { class InterpolatedYoYCapFloorTermPriceSurface : public YoYCapFloorTermPriceSurface { public: + InterpolatedYoYCapFloorTermPriceSurface( + Natural fixingDays, + const Period &yyLag, // observation lag + const ext::shared_ptr& yii, + CPI::InterpolationType interpolation, + const Handle &nominal, + const DayCounter &dc, + const Calendar &cal, + const BusinessDayConvention &bdc, + const std::vector &cStrikes, + const std::vector &fStrikes, + const std::vector &cfMaturities, + const Matrix &cPrice, + const Matrix &fPrice, + const Interpolator2D &interpolator2d = Interpolator2D(), + const Interpolator1D &interpolator1d = Interpolator1D()); + + /*! \deprecated Use the overload that passes an interpolation type instead. + Deprecated in version 1.36. + */ + [[deprecated("Use the overload that passes an interpolation type instead")]] InterpolatedYoYCapFloorTermPriceSurface( Natural fixingDays, const Period &yyLag, // observation lag @@ -257,7 +296,7 @@ namespace QuantLib { Natural fixingDays, const Period &yyLag, const ext::shared_ptr& yii, - Rate baseRate, + CPI::InterpolationType interpolation, const Handle &nominal, const DayCounter &dc, const Calendar &cal, @@ -270,13 +309,37 @@ namespace QuantLib { const I2D &interpolator2d, const I1D &interpolator1d) : YoYCapFloorTermPriceSurface(fixingDays, yyLag, yii, - baseRate, nominal, dc, cal, bdc, + interpolation, nominal, dc, cal, bdc, cStrikes, fStrikes, cfMaturities, cPrice, fPrice), interpolator2d_(interpolator2d), interpolator1d_(interpolator1d) { performCalculations(); } + template + InterpolatedYoYCapFloorTermPriceSurface:: + InterpolatedYoYCapFloorTermPriceSurface( + Natural fixingDays, + const Period &yyLag, + const ext::shared_ptr& yii, + Rate baseRate, + const Handle &nominal, + const DayCounter &dc, + const Calendar &cal, + const BusinessDayConvention &bdc, + const std::vector &cStrikes, + const std::vector &fStrikes, + const std::vector &cfMaturities, + const Matrix &cPrice, + const Matrix &fPrice, + const I2D &interpolator2d, + const I1D &interpolator1d) + : InterpolatedYoYCapFloorTermPriceSurface(fixingDays, yyLag, yii, CPI::AsIndex, + nominal, dc, cal, bdc, + cStrikes, fStrikes, cfMaturities, + cPrice, fPrice, + interpolator2d, interpolator1d) {} + #endif template @@ -532,12 +595,13 @@ namespace QuantLib { Date maturity = nominalTS_->referenceDate() + Period(i,Years); Handle quote(ext::shared_ptr( new SimpleQuote( atmYoYSwapRate( maturity ) )));//! - ext::shared_ptr > - anInstrument( - new YearOnYearInflationSwapHelper( + auto anInstrument = + ext::make_shared( quote, observationLag(), maturity, calendar(), bdc_, dayCounter(), - yoyIndex(), nominalTS_)); + yoyIndex(), + this->indexIsInterpolated() ? CPI::Linear: CPI::Flat, + nominalTS_); YYhelpers.push_back (anInstrument); } diff --git a/ql/experimental/inflation/yoyoptionlethelpers.cpp b/ql/experimental/inflation/yoyoptionlethelpers.cpp index a7a74f891a3..b30ee2283ad 100644 --- a/ql/experimental/inflation/yoyoptionlethelpers.cpp +++ b/ql/experimental/inflation/yoyoptionlethelpers.cpp @@ -32,6 +32,7 @@ namespace QuantLib { Calendar paymentCalendar, Natural fixingDays, ext::shared_ptr index, + CPI::InterpolationType interpolation, Rate strike, Size n, ext::shared_ptr pricer) @@ -43,7 +44,7 @@ namespace QuantLib { // build the instrument to reprice (only need do this once) yoyCapFloor_ = MakeYoYInflationCapFloor(capFloorType_, index_, - n_, calendar_, lag_) + n_, calendar_, lag_, interpolation) .withNominal(notional) .withFixingDays(fixingDays_) .withPaymentDayCounter(yoyDayCounter_) @@ -63,6 +64,20 @@ namespace QuantLib { // haven't yet set the vol (term structure = surface) } + YoYOptionletHelper::YoYOptionletHelper(const Handle& price, + Real notional, + YoYInflationCapFloor::Type capFloorType, + Period& lag, + DayCounter yoyDayCounter, + Calendar paymentCalendar, + Natural fixingDays, + ext::shared_ptr index, + Rate strike, + Size n, + ext::shared_ptr pricer) + : YoYOptionletHelper(price, notional, capFloorType, lag, yoyDayCounter, paymentCalendar, + fixingDays, index, CPI::AsIndex, strike, n, pricer) {} + Real YoYOptionletHelper::impliedQuote() const { yoyCapFloor_->deepUpdate(); diff --git a/ql/experimental/inflation/yoyoptionlethelpers.hpp b/ql/experimental/inflation/yoyoptionlethelpers.hpp index bb5e9452d9f..b098726887e 100644 --- a/ql/experimental/inflation/yoyoptionlethelpers.hpp +++ b/ql/experimental/inflation/yoyoptionlethelpers.hpp @@ -36,8 +36,23 @@ namespace QuantLib { : public BootstrapHelper { public: YoYOptionletHelper(const Handle& price, - Real notional, // get the price level right - // (e.g. bps = 10,000) + Real notional, // get the price level right, e.g., bps = 10,000 + YoYInflationCapFloor::Type capFloorType, + Period& lag, + DayCounter yoyDayCounter, + Calendar paymentCalendar, + Natural fixingDays, + ext::shared_ptr index, + CPI::InterpolationType interpolation, + Rate strike, + Size n, + ext::shared_ptr pricer); + /*! \deprecated Use the overload that passes an interpolation type instead. + Deprecated in version 1.36. + */ + [[deprecated("Use the overload that passes an interpolation type instead")]] + YoYOptionletHelper(const Handle& price, + Real notional, YoYInflationCapFloor::Type capFloorType, Period& lag, DayCounter yoyDayCounter, @@ -55,8 +70,7 @@ namespace QuantLib { YoYInflationCapFloor::Type capFloorType_; Period lag_; Natural fixingDays_; - ext::shared_ptr index_; // VERY important - has - // nominal & yoy curves + ext::shared_ptr index_; Rate strike_; Size n_; // how many payments DayCounter yoyDayCounter_; diff --git a/ql/indexes/inflationindex.cpp b/ql/indexes/inflationindex.cpp index cfcdf02732c..6f54bd5b6fc 100644 --- a/ql/indexes/inflationindex.cpp +++ b/ql/indexes/inflationindex.cpp @@ -31,9 +31,7 @@ namespace QuantLib { CPI::InterpolationType interpolationType) { switch (interpolationType) { - case AsIndex: { - return index->fixing(date - observationLag); - } + case AsIndex: case Flat: { auto fixingPeriod = inflationPeriod(date - observationLag, index->frequency()); return index->fixing(fixingPeriod.first); @@ -42,16 +40,17 @@ namespace QuantLib { auto fixingPeriod = inflationPeriod(date - observationLag, index->frequency()); auto interpolationPeriod = inflationPeriod(date, index->frequency()); + auto I0 = index->fixing(fixingPeriod.first); + if (date == interpolationPeriod.first) { // special case; no interpolation. This avoids asking for // the fixing at the end of the period, which might need a // forecast curve to be set. - return index->fixing(fixingPeriod.first); + return I0; } static const auto oneDay = Period(1, Days); - auto I0 = index->fixing(fixingPeriod.first); auto I1 = index->fixing(fixingPeriod.second + oneDay); return I0 + (I1 - I0) * (date - interpolationPeriod.first) / @@ -63,6 +62,60 @@ namespace QuantLib { } + Real CPI::laggedYoYRate(const ext::shared_ptr& index, + const Date& date, + const Period& observationLag, + CPI::InterpolationType interpolationType) { + + switch (interpolationType) { + case AsIndex: { + return index->fixing(date - observationLag); + } + case Flat: { + auto fixingPeriod = inflationPeriod(date - observationLag, index->frequency()); + return index->fixing(fixingPeriod.first); + } + case Linear: { + if (index->ratio() && !index->needsForecast(date)) { + // in the case of a ratio, the convention seems to be to interpolate + // the underlying index fixings first, then take the ratio. This is + // not the same as taking the ratios and then interpolate, which is + // equivalent to what the else clause does. + // However, we can only do this if the fixings we need are in the past, + // because forecasts need to be done through the YoY forecast curve, + // and not the underlying index. + + auto underlying = index->underlyingIndex(); + Rate Z1 = CPI::laggedFixing(underlying, date, observationLag, interpolationType); + Rate Z0 = CPI::laggedFixing(underlying, date - 1*Years, observationLag, interpolationType); + + return Z1/Z0 - 1.0; + + } else { + static const auto oneDay = Period(1, Days); + + auto fixingPeriod = inflationPeriod(date - observationLag, index->frequency()); + auto interpolationPeriod = inflationPeriod(date, index->frequency()); + + auto Y0 = index->fixing(fixingPeriod.first); + + if (date == interpolationPeriod.first) { + // special case; no interpolation anyway. + return Y0; + } + + auto Y1 = index->fixing(fixingPeriod.second + oneDay); + + return Y0 + (Y1 - Y0) * (date - interpolationPeriod.first) / + (Real)((interpolationPeriod.second + oneDay) - interpolationPeriod.first); + } + } + default: + QL_FAIL("unknown CPI interpolation type: " << int(interpolationType)); + } + } + + InflationIndex::InflationIndex(std::string familyName, Region region, bool revised, @@ -324,4 +377,23 @@ namespace QuantLib { } } + CPI::InterpolationType + detail::CPI::effectiveInterpolationType(const QuantLib::CPI::InterpolationType& type, + const ext::shared_ptr& index) { + if (type == QuantLib::CPI::AsIndex) { + return index->interpolated() ? QuantLib::CPI::Linear : QuantLib::CPI::Flat; + } else { + return type; + } + } + + bool detail::CPI::isInterpolated(const QuantLib::CPI::InterpolationType& type) { + return detail::CPI::effectiveInterpolationType(type) == QuantLib::CPI::Linear; + } + + bool detail::CPI::isInterpolated(const QuantLib::CPI::InterpolationType& type, + const ext::shared_ptr& index) { + return detail::CPI::effectiveInterpolationType(type, index) == QuantLib::CPI::Linear; + } + } diff --git a/ql/indexes/inflationindex.hpp b/ql/indexes/inflationindex.hpp index ba7947927ba..114e8274f5a 100644 --- a/ql/indexes/inflationindex.hpp +++ b/ql/indexes/inflationindex.hpp @@ -34,8 +34,10 @@ namespace QuantLib { class ZeroInflationIndex; + class YoYInflationIndex; struct CPI { + //! when you observe an index, how do you interpolate between fixings? enum InterpolationType { AsIndex, //!< same interpolation as index @@ -58,6 +60,23 @@ namespace QuantLib { const Date& date, const Period& observationLag, InterpolationType interpolationType); + + + //! interpolated year-on-year inflation rate + /*! \param index The index whose fixing should be retrieved + \param date The date without lag; usually, the payment + date for some inflation-based coupon. + \param observationLag The observation lag to be subtracted from the + passed date; for instance, if the passed date is + in May and the lag is three months, the year-on-year + rate from February (and March, in case of + interpolation) will be observed. + \param interpolationType The interpolation type (flat or linear) + */ + static Real laggedYoYRate(const ext::shared_ptr& index, + const Date& date, + const Period& observationLag, + InterpolationType interpolationType); }; @@ -230,16 +249,24 @@ namespace QuantLib { namespace detail::CPI { - // Returns either CPI::Flat or CPI::Linear depending on the combination of index and - // CPI::InterpolationType. - QuantLib::CPI::InterpolationType effectiveInterpolationType( - const QuantLib::CPI::InterpolationType& type = QuantLib::CPI::AsIndex); + // Returns either CPI::Flat or CPI::Linear depending on the combination of index and + // CPI::InterpolationType. + QuantLib::CPI::InterpolationType + effectiveInterpolationType(const QuantLib::CPI::InterpolationType& type); + + QuantLib::CPI::InterpolationType + effectiveInterpolationType(const QuantLib::CPI::InterpolationType& type, + const ext::shared_ptr& index); + + // checks whether the combination of index and CPI::InterpolationType results + // effectively in CPI::Linear + bool isInterpolated(const QuantLib::CPI::InterpolationType& type); - // checks whether the combination of index and CPI::InterpolationType results - // effectively in CPI::Linear - bool isInterpolated(const QuantLib::CPI::InterpolationType& type = QuantLib::CPI::AsIndex); - } + bool isInterpolated(const QuantLib::CPI::InterpolationType& type, + const ext::shared_ptr& index); + + } // inline @@ -294,10 +321,6 @@ namespace QuantLib { return yoyInflation_; } - inline bool detail::CPI::isInterpolated(const QuantLib::CPI::InterpolationType& type) { - return detail::CPI::effectiveInterpolationType(type) == QuantLib::CPI::Linear; - } - } #endif diff --git a/ql/instruments/makeyoyinflationcapfloor.cpp b/ql/instruments/makeyoyinflationcapfloor.cpp index c886547d435..21616c87bfc 100644 --- a/ql/instruments/makeyoyinflationcapfloor.cpp +++ b/ql/instruments/makeyoyinflationcapfloor.cpp @@ -30,12 +30,21 @@ namespace QuantLib { ext::shared_ptr index, const Size& length, Calendar cal, - const Period& observationLag) + const Period& observationLag, + CPI::InterpolationType interpolation) : capFloorType_(capFloorType), length_(length), calendar_(std::move(cal)), - index_(std::move(index)), observationLag_(observationLag), strike_(Null()), + index_(std::move(index)), observationLag_(observationLag), + interpolation_(interpolation), strike_(Null()), dayCounter_(Thirty360(Thirty360::BondBasis)) {} + MakeYoYInflationCapFloor::MakeYoYInflationCapFloor(YoYInflationCapFloor::Type capFloorType, + ext::shared_ptr index, + const Size& length, + Calendar cal, + const Period& observationLag) + : MakeYoYInflationCapFloor(capFloorType, index, length, cal, observationLag, CPI::AsIndex) {} + MakeYoYInflationCapFloor::operator YoYInflationCapFloor() const { ext::shared_ptr capfloor = *this; return *capfloor; @@ -58,7 +67,7 @@ namespace QuantLib { Unadjusted, Unadjusted, // ref periods & acc periods DateGeneration::Forward, false); Leg leg = yoyInflationLeg(schedule, calendar_, index_, - observationLag_) + observationLag_, interpolation_) .withPaymentAdjustment(roll_) .withPaymentDayCounter(dayCounter_) .withNotionals(nominal_) diff --git a/ql/instruments/makeyoyinflationcapfloor.hpp b/ql/instruments/makeyoyinflationcapfloor.hpp index 8fb8aef7e00..23bb3a6f631 100644 --- a/ql/instruments/makeyoyinflationcapfloor.hpp +++ b/ql/instruments/makeyoyinflationcapfloor.hpp @@ -37,6 +37,16 @@ namespace QuantLib { */ class MakeYoYInflationCapFloor { public: + MakeYoYInflationCapFloor(YoYInflationCapFloor::Type capFloorType, + ext::shared_ptr index, + const Size& length, + Calendar cal, + const Period& observationLag, + CPI::InterpolationType interpolation); + /*! \deprecated Use the overload that passes an interpolation type instead. + Deprecated in version 1.36. + */ + [[deprecated("Use the overload that passes an interpolation type instead")]] MakeYoYInflationCapFloor(YoYInflationCapFloor::Type capFloorType, ext::shared_ptr index, const Size& length, @@ -66,6 +76,7 @@ namespace QuantLib { Calendar calendar_; ext::shared_ptr index_; Period observationLag_; + CPI::InterpolationType interpolation_; Rate strike_; bool firstCapletExcluded_ = false, asOptionlet_ = false; Date effectiveDate_; diff --git a/ql/instruments/yearonyearinflationswap.cpp b/ql/instruments/yearonyearinflationswap.cpp index b4c8d71adc8..6760edc6003 100644 --- a/ql/instruments/yearonyearinflationswap.cpp +++ b/ql/instruments/yearonyearinflationswap.cpp @@ -39,6 +39,7 @@ namespace QuantLib { Schedule yoySchedule, ext::shared_ptr yoyIndex, const Period& observationLag, + CPI::InterpolationType interpolation, Spread spread, DayCounter yoyDayCount, Calendar paymentCalendar, @@ -54,7 +55,8 @@ namespace QuantLib { .withCouponRates(fixedRate_, fixedDayCount_) // Simple compounding by default .withPaymentAdjustment(paymentConvention_); - Leg yoyLeg = yoyInflationLeg(yoySchedule_, paymentCalendar_, yoyIndex_, observationLag_) + Leg yoyLeg = yoyInflationLeg(yoySchedule_, paymentCalendar_, yoyIndex_, + observationLag_, interpolation) .withNotionals(nominal_) .withPaymentDayCounter(yoyDayCount_) .withPaymentAdjustment(paymentConvention_) @@ -75,6 +77,21 @@ namespace QuantLib { } } + YearOnYearInflationSwap::YearOnYearInflationSwap(Type type, + Real nominal, + Schedule fixedSchedule, + Rate fixedRate, + DayCounter fixedDayCount, + Schedule yoySchedule, + ext::shared_ptr yoyIndex, + const Period& observationLag, + Spread spread, + DayCounter yoyDayCount, + Calendar paymentCalendar, + BusinessDayConvention paymentConvention) + : YearOnYearInflationSwap(type, nominal, fixedSchedule, fixedRate, fixedDayCount, + yoySchedule, yoyIndex, observationLag, CPI::AsIndex, + spread, yoyDayCount, paymentCalendar, paymentConvention) {} void YearOnYearInflationSwap::setupArguments(PricingEngine::arguments* args) const { diff --git a/ql/instruments/yearonyearinflationswap.hpp b/ql/instruments/yearonyearinflationswap.hpp index 2143e167c80..50657d14edf 100644 --- a/ql/instruments/yearonyearinflationswap.hpp +++ b/ql/instruments/yearonyearinflationswap.hpp @@ -26,12 +26,12 @@ #define quantlib_yyiis_hpp #include +#include #include #include #include namespace QuantLib { - class YoYInflationIndex; //! Year-on-year inflation-indexed swap /*! Quoted as a fixed rate \f$ K \f$. At start: @@ -49,6 +49,24 @@ namespace QuantLib { class arguments; class results; class engine; + YearOnYearInflationSwap( + Type type, + Real nominal, + Schedule fixedSchedule, + Rate fixedRate, + DayCounter fixedDayCount, + Schedule yoySchedule, + ext::shared_ptr yoyIndex, + const Period& observationLag, + CPI::InterpolationType interpolation, + Spread spread, + DayCounter yoyDayCount, + Calendar paymentCalendar, // inflation index does not have a calendar + BusinessDayConvention paymentConvention = ModifiedFollowing); + /*! \deprecated Use the overload that passes an interpolation type instead. + Deprecated in version 1.36. + */ + [[deprecated("Use the overload that passes an interpolation type instead")]] YearOnYearInflationSwap( Type type, Real nominal, @@ -142,7 +160,7 @@ namespace QuantLib { }; class YearOnYearInflationSwap::engine : public GenericEngine {}; + YearOnYearInflationSwap::results> {}; // inline definitions diff --git a/ql/termstructures/inflation/inflationhelpers.cpp b/ql/termstructures/inflation/inflationhelpers.cpp index 7ea04500768..2bc16d494a5 100644 --- a/ql/termstructures/inflation/inflationhelpers.cpp +++ b/ql/termstructures/inflation/inflationhelpers.cpp @@ -44,24 +44,23 @@ namespace QuantLib { observationInterpolation_(observationInterpolation), nominalTermStructure_(std::move(nominalTermStructure)) { - std::pair limStart = inflationPeriod(maturity_ - swapObsLag_, zii_->frequency()); - std::pair interpolationPeriod = inflationPeriod(maturity, zii_->frequency()); + auto fixingPeriod = inflationPeriod(maturity_ - swapObsLag_, zii_->frequency()); + auto interpolationPeriod = inflationPeriod(maturity, zii_->frequency()); - if ((detail::CPI::effectiveInterpolationType(observationInterpolation_) == CPI::Linear) && - (maturity > interpolationPeriod.first)) { + if (detail::CPI::isInterpolated(observationInterpolation_) && maturity > interpolationPeriod.first) { // if interpolated, we need to cover the end of the interpolation period - earliestDate_ = limStart.first; - latestDate_ = limStart.second + 1; + earliestDate_ = fixingPeriod.first; + latestDate_ = fixingPeriod.second + 1; } else { // if not interpolated, the date of the initial fixing is enough - earliestDate_ = limStart.first; - latestDate_ = limStart.first; + earliestDate_ = fixingPeriod.first; + latestDate_ = fixingPeriod.first; } // check that the observation lag of the swap // is compatible with the availability lag of the index AND // it's interpolation (assuming the start day is spot) - if (detail::CPI::effectiveInterpolationType(observationInterpolation_) == CPI::Linear) { + if (detail::CPI::isInterpolated(observationInterpolation_)) { Period pShift(zii_->frequency()); QL_REQUIRE(swapObsLag_ - pShift >= zii_->availabilityLag(), "inconsistency between swap observation lag " @@ -117,43 +116,53 @@ namespace QuantLib { BusinessDayConvention paymentConvention, DayCounter dayCounter, ext::shared_ptr yii, + CPI::InterpolationType interpolation, Handle nominalTermStructure) : BootstrapHelper(quote), swapObsLag_(swapObsLag), maturity_(maturity), calendar_(std::move(calendar)), paymentConvention_(paymentConvention), - dayCounter_(std::move(dayCounter)), yii_(std::move(yii)), + dayCounter_(std::move(dayCounter)), yii_(std::move(yii)), interpolation_(interpolation), nominalTermStructure_(std::move(nominalTermStructure)) { - if (yii_->interpolated()) { - // if interpolated then simple - earliestDate_ = maturity_ - swapObsLag_; - latestDate_ = maturity_ - swapObsLag_; + auto fixingPeriod = inflationPeriod(maturity_ - swapObsLag_, yii_->frequency()); + auto interpolationPeriod = inflationPeriod(maturity, yii_->frequency()); + + if (detail::CPI::isInterpolated(interpolation_, yii_) && maturity > interpolationPeriod.first) { + // if interpolated, we need to cover the end of the interpolation period + earliestDate_ = fixingPeriod.first; + latestDate_ = fixingPeriod.second + 1; } else { - // but if NOT interpolated then the value is valid - // for every day in an inflation period so you actually - // get an extended validity, however for curve building - // just put the first date because using that convention - // for the base date throughout - std::pair limStart = - inflationPeriod(maturity_ - swapObsLag_, yii_->frequency()); - earliestDate_ = limStart.first; - latestDate_ = limStart.first; + // if not interpolated, the date of the initial fixing is enough + earliestDate_ = fixingPeriod.first; + latestDate_ = fixingPeriod.first; } // check that the observation lag of the swap // is compatible with the availability lag of the index AND - // it's interpolation (assuming the start day is spot) - if (yii_->interpolated()) { + // its interpolation (assuming the start day is spot) + if (detail::CPI::isInterpolated(interpolation_, yii_)) { Period pShift(yii_->frequency()); QL_REQUIRE(swapObsLag_ - pShift >= yii_->availabilityLag(), "inconsistency between swap observation lag " - << swapObsLag_ << ", index period " << pShift << " and index availability " - << yii_->availabilityLag() << ": need (obsLag-index period) >= availLag"); + << swapObsLag_ << ", index period " << pShift << " and index availability " + << yii_->availabilityLag() << ": need (obsLag-index period) >= availLag"); } registerWith(Settings::instance().evaluationDate()); registerWith(nominalTermStructure_); } + YearOnYearInflationSwapHelper::YearOnYearInflationSwapHelper( + const Handle& quote, + const Period& swapObsLag, + const Date& maturity, + Calendar calendar, + BusinessDayConvention paymentConvention, + DayCounter dayCounter, + ext::shared_ptr yii, + Handle nominalTermStructure) + : YearOnYearInflationSwapHelper(quote, swapObsLag, maturity, calendar, paymentConvention, + dayCounter, yii, CPI::AsIndex, nominalTermStructure) {} + Real YearOnYearInflationSwapHelper::impliedQuote() const { yyiis_->deepUpdate(); @@ -194,9 +203,8 @@ namespace QuantLib { Real nominal = 1000000.0; // has to be something but doesn't matter what yyiis_ = ext::make_shared( Swap::Payer, nominal, fixedSchedule, fixedRate, dayCounter_, - yoySchedule, new_yii, swapObsLag_, spread, dayCounter_, - calendar_, // inflation index does not have a calendar - paymentConvention_); + yoySchedule, new_yii, swapObsLag_, interpolation_, + spread, dayCounter_, calendar_, paymentConvention_); // The instrument takes a standard discounting swap engine. // The inflation-related work is done by the coupons. diff --git a/ql/termstructures/inflation/inflationhelpers.hpp b/ql/termstructures/inflation/inflationhelpers.hpp index aa30c86e82e..7797a4f82a8 100644 --- a/ql/termstructures/inflation/inflationhelpers.hpp +++ b/ql/termstructures/inflation/inflationhelpers.hpp @@ -65,6 +65,20 @@ namespace QuantLib { //! Year-on-year inflation-swap bootstrap helper class YearOnYearInflationSwapHelper : public BootstrapHelper { public: + YearOnYearInflationSwapHelper(const Handle& quote, + const Period& swapObsLag_, + const Date& maturity, + Calendar calendar, + BusinessDayConvention paymentConvention, + DayCounter dayCounter, + ext::shared_ptr yii, + CPI::InterpolationType interpolation, + Handle nominalTermStructure); + + /*! \deprecated Use the overload that passes an interpolation type instead. + Deprecated in version 1.36. + */ + [[deprecated("Use the overload that passes an interpolation type instead")]] YearOnYearInflationSwapHelper(const Handle& quote, const Period& swapObsLag_, const Date& maturity, @@ -84,6 +98,7 @@ namespace QuantLib { BusinessDayConvention paymentConvention_; DayCounter dayCounter_; ext::shared_ptr yii_; + CPI::InterpolationType interpolation_; ext::shared_ptr yyiis_; Handle nominalTermStructure_; }; diff --git a/test-suite/inflation.cpp b/test-suite/inflation.cpp index 70d99762523..4401bde2222 100644 --- a/test-suite/inflation.cpp +++ b/test-suite/inflation.cpp @@ -1121,7 +1121,7 @@ BOOST_AUTO_TEST_CASE(testYYTermStructure) { // now build the helpers ... auto makeHelper = [&](const Handle& quote, const Date& maturity) { return ext::make_shared( - quote, observationLag, maturity, calendar, bdc, dc, iir, + quote, observationLag, maturity, calendar, bdc, dc, iir, CPI::AsIndex, Handle(nominalTS)); }; auto helpers = makeHelpers(yyData, makeHelper); @@ -1164,6 +1164,7 @@ BOOST_AUTO_TEST_CASE(testYYTermStructure) { yoySchedule, iir, observationLag, + CPI::Flat, 0.0, //spread on index dc, UnitedKingdom()); @@ -1200,6 +1201,7 @@ BOOST_AUTO_TEST_CASE(testYYTermStructure) { yoySchedule, iir, observationLag, + CPI::Flat, 0.0, //spread on index dc, UnitedKingdom()); @@ -1276,7 +1278,7 @@ BOOST_AUTO_TEST_CASE(testYYTermStructureWithLag) { // now build the helpers ... auto makeHelper = [&](const Handle& quote, const Date& maturity) { return ext::make_shared( - quote, observationLag, maturity, calendar, bdc, dc, iir, + quote, observationLag, maturity, calendar, bdc, dc, iir, CPI::AsIndex, Handle(nominalTS)); }; auto helpers = makeHelpers(yyData, makeHelper); @@ -1320,6 +1322,7 @@ BOOST_AUTO_TEST_CASE(testYYTermStructureWithLag) { yoySchedule, iir, observationLag, + CPI::Flat, 0.0, //spread on index dc, UnitedKingdom()); @@ -1356,6 +1359,7 @@ BOOST_AUTO_TEST_CASE(testYYTermStructureWithLag) { yoySchedule, iir, observationLag, + CPI::Flat, 0.0, //spread on index dc, UnitedKingdom()); @@ -1448,7 +1452,7 @@ BOOST_AUTO_TEST_CASE(testPeriod) { } BOOST_AUTO_TEST_CASE(testCpiFlatInterpolation) { - BOOST_TEST_MESSAGE("Testing CPI flat interpolation..."); + BOOST_TEST_MESSAGE("Testing CPI flat interpolation for inflation fixings..."); Settings::instance().evaluationDate() = Date(10, February, 2022); @@ -1463,30 +1467,21 @@ BOOST_AUTO_TEST_CASE(testCpiFlatInterpolation) { Real calculated = CPI::laggedFixing(testIndex, Date(10, February, 2021), 3 * Months, CPI::Flat); Real expected = 293.5; - BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8, - "failed to retrieve inflation fixing" << - "\n expected: " << expected << - "\n calculated: " << calculated); + QL_CHECK_CLOSE(calculated, expected, 1e-8); calculated = CPI::laggedFixing(testIndex, Date(12, May, 2021), 3 * Months, CPI::Flat); expected = 296.0; - BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8, - "failed to retrieve inflation fixing" << - "\n expected: " << expected << - "\n calculated: " << calculated); + QL_CHECK_CLOSE(calculated, expected, 1e-8); calculated = CPI::laggedFixing(testIndex, Date(25, June, 2021), 3 * Months, CPI::Flat); expected = 296.9; - BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8, - "failed to retrieve inflation fixing" << - "\n expected: " << expected << - "\n calculated: " << calculated); + QL_CHECK_CLOSE(calculated, expected, 1e-8); } BOOST_AUTO_TEST_CASE(testCpiLinearInterpolation) { - BOOST_TEST_MESSAGE("Testing CPI linear interpolation..."); + BOOST_TEST_MESSAGE("Testing CPI linear interpolation for inflation fixings..."); Settings::instance().evaluationDate() = Date(10, February, 2022); @@ -1501,18 +1496,12 @@ BOOST_AUTO_TEST_CASE(testCpiLinearInterpolation) { Real calculated = CPI::laggedFixing(testIndex, Date(10, February, 2021), 3 * Months, CPI::Linear); Real expected = 293.5 * (19/28.0) + 295.4 * (9/28.0); - BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8, - "failed to retrieve inflation fixing" << - "\n expected: " << expected << - "\n calculated: " << calculated); + QL_CHECK_CLOSE(calculated, expected, 1e-8); calculated = CPI::laggedFixing(testIndex, Date(12, May, 2021), 3 * Months, CPI::Linear); expected = 296.0 * (20/31.0) + 296.9 * (11/31.0); - BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8, - "failed to retrieve inflation fixing" << - "\n expected: " << expected << - "\n calculated: " << calculated); + QL_CHECK_CLOSE(calculated, expected, 1e-8); // this would require April's fixing BOOST_CHECK_THROW( @@ -1523,14 +1512,11 @@ BOOST_AUTO_TEST_CASE(testCpiLinearInterpolation) { calculated = CPI::laggedFixing(testIndex, Date(1, June, 2021), 3 * Months, CPI::Linear); expected = 296.9; - BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8, - "failed to retrieve inflation fixing" << - "\n expected: " << expected << - "\n calculated: " << calculated); + QL_CHECK_CLOSE(calculated, expected, 1e-8); } BOOST_AUTO_TEST_CASE(testCpiAsIndexInterpolation) { - BOOST_TEST_MESSAGE("Testing CPI as-index interpolation..."); + BOOST_TEST_MESSAGE("Testing CPI as-index interpolation for inflation fixings..."); Date today = Date(10, February, 2022); Settings::instance().evaluationDate() = today; @@ -1551,28 +1537,210 @@ BOOST_AUTO_TEST_CASE(testCpiAsIndexInterpolation) { Real calculated = CPI::laggedFixing(testIndex, Date(10, February, 2021), 3 * Months, CPI::AsIndex); Real expected = 293.5; - BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8, - "failed to retrieve inflation fixing" << - "\n expected: " << expected << - "\n calculated: " << calculated); + QL_CHECK_CLOSE(calculated, expected, 1e-8); calculated = CPI::laggedFixing(testIndex, Date(12, May, 2021), 3 * Months, CPI::AsIndex); expected = 296.0; - BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8, - "failed to retrieve inflation fixing" << - "\n expected: " << expected << - "\n calculated: " << calculated); + QL_CHECK_CLOSE(calculated, expected, 1e-8); calculated = CPI::laggedFixing(testIndex, Date(25, June, 2021), 3 * Months, CPI::AsIndex); expected = 296.9; - BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8, - "failed to retrieve inflation fixing" << - "\n expected: " << expected << - "\n calculated: " << calculated); + QL_CHECK_CLOSE(calculated, expected, 1e-8); } +BOOST_AUTO_TEST_CASE(testCpiYoYQuotedFlatInterpolation) { + BOOST_TEST_MESSAGE("Testing CPI flat interpolation for year-on-year quoted rates..."); + + Settings::instance().evaluationDate() = Date(10, February, 2022); + + auto testIndex1 = ext::make_shared(false); + auto testIndex2 = ext::make_shared(true); + + testIndex1->addFixing(Date(1, November, 2020), 0.02935); + testIndex1->addFixing(Date(1, December, 2020), 0.02954); + testIndex1->addFixing(Date(1, January, 2021), 0.02946); + testIndex1->addFixing(Date(1, February, 2021), 0.02960); + testIndex1->addFixing(Date(1, March, 2021), 0.02969); + + Real calculated = CPI::laggedYoYRate(testIndex1, Date(10, February, 2021), 3 * Months, CPI::Flat); + Real expected = 0.02935; + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + // same expected flat fixing for interpolated and not interpolated + calculated = CPI::laggedYoYRate(testIndex2, Date(10, February, 2021), 3 * Months, CPI::Flat); + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + calculated = CPI::laggedYoYRate(testIndex1, Date(25, June, 2021), 3 * Months, CPI::Flat); + expected = 0.02969; + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + calculated = CPI::laggedYoYRate(testIndex2, Date(25, June, 2021), 3 * Months, CPI::Flat); + + QL_CHECK_CLOSE(calculated, expected, 1e-8); +} + +BOOST_AUTO_TEST_CASE(testCpiYoYQuotedLinearInterpolation) { + BOOST_TEST_MESSAGE("Testing CPI linear interpolation for year-on-year quoted rates..."); + + Settings::instance().evaluationDate() = Date(10, February, 2022); + + auto testIndex1 = ext::make_shared(false); + auto testIndex2 = ext::make_shared(true); + + testIndex1->addFixing(Date(1, November, 2020), 0.02935); + testIndex1->addFixing(Date(1, December, 2020), 0.02954); + testIndex1->addFixing(Date(1, January, 2021), 0.02946); + testIndex1->addFixing(Date(1, February, 2021), 0.02960); + testIndex1->addFixing(Date(1, March, 2021), 0.02969); + + Real calculated = CPI::laggedYoYRate(testIndex1, Date(10, February, 2021), 3 * Months, CPI::Linear); + Real expected = 0.02935 * (19/28.0) + 0.02954 * (9/28.0); + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + calculated = CPI::laggedYoYRate(testIndex2, Date(10, February, 2021), 3 * Months, CPI::Linear); + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + calculated = CPI::laggedYoYRate(testIndex1, Date(12, May, 2021), 3 * Months, CPI::Linear); + expected = 0.02960 * (20/31.0) + 0.02969 * (11/31.0); + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + calculated = CPI::laggedYoYRate(testIndex2, Date(12, May, 2021), 3 * Months, CPI::Linear); + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + // this would require April's fixing + BOOST_CHECK_EXCEPTION( + CPI::laggedYoYRate(testIndex1, Date(25, June, 2021), 3 * Months, CPI::Linear), + Error, ExpectedErrorMessage("Missing UK YY_RPI fixing")); + + BOOST_CHECK_EXCEPTION( + CPI::laggedYoYRate(testIndex2, Date(25, June, 2021), 3 * Months, CPI::Linear), + Error, ExpectedErrorMessage("Missing UK YY_RPI fixing")); + + // however, this is a special case + calculated = CPI::laggedYoYRate(testIndex1, Date(1, June, 2021), 3 * Months, CPI::Linear); + expected = 0.02969; + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + calculated = CPI::laggedYoYRate(testIndex2, Date(1, June, 2021), 3 * Months, CPI::Linear); + + QL_CHECK_CLOSE(calculated, expected, 1e-8); +} + + +BOOST_AUTO_TEST_CASE(testCpiYoYRatioFlatInterpolation) { + BOOST_TEST_MESSAGE("Testing CPI flat interpolation for year-on-year ratios..."); + + Settings::instance().evaluationDate() = Date(10, February, 2022); + + auto underlying = ext::make_shared(); + + auto testIndex1 = ext::make_shared(underlying, false); + auto testIndex2 = ext::make_shared(underlying, true); + + underlying->addFixing(Date(1, November, 2019), 291.0); + underlying->addFixing(Date(1, December, 2019), 291.9); + underlying->addFixing(Date(1, January, 2020), 290.6); + underlying->addFixing(Date(1, February, 2020), 292.0); + underlying->addFixing(Date(1, March, 2020), 292.6); + + underlying->addFixing(Date(1, November, 2020), 293.5); + underlying->addFixing(Date(1, December, 2020), 295.4); + underlying->addFixing(Date(1, January, 2021), 294.6); + underlying->addFixing(Date(1, February, 2021), 296.0); + underlying->addFixing(Date(1, March, 2021), 296.9); + + Real calculated = CPI::laggedYoYRate(testIndex1, Date(10, February, 2021), 3 * Months, CPI::Flat); + Real expected = 293.5/291.0 - 1; + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + // same expected flat fixing for interpolated and not interpolated + calculated = CPI::laggedYoYRate(testIndex2, Date(10, February, 2021), 3 * Months, CPI::Flat); + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + calculated = CPI::laggedYoYRate(testIndex1, Date(25, June, 2021), 3 * Months, CPI::Flat); + expected = 296.9/292.6 - 1; + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + calculated = CPI::laggedYoYRate(testIndex2, Date(25, June, 2021), 3 * Months, CPI::Flat); + + QL_CHECK_CLOSE(calculated, expected, 1e-8); +} + +BOOST_AUTO_TEST_CASE(testCpiYoYRatioLinearInterpolation) { + BOOST_TEST_MESSAGE("Testing CPI linear interpolation for year-on-year ratios..."); + + Settings::instance().evaluationDate() = Date(10, February, 2022); + + auto underlying = ext::make_shared(); + + auto testIndex1 = ext::make_shared(underlying, false); + auto testIndex2 = ext::make_shared(underlying, true); + + underlying->addFixing(Date(1, November, 2019), 291.0); + underlying->addFixing(Date(1, December, 2019), 291.9); + underlying->addFixing(Date(1, January, 2020), 290.6); + underlying->addFixing(Date(1, February, 2020), 292.0); + underlying->addFixing(Date(1, March, 2020), 292.6); + + underlying->addFixing(Date(1, November, 2020), 293.5); + underlying->addFixing(Date(1, December, 2020), 295.4); + underlying->addFixing(Date(1, January, 2021), 294.6); + underlying->addFixing(Date(1, February, 2021), 296.0); + underlying->addFixing(Date(1, March, 2021), 296.9); + + Real calculated = CPI::laggedYoYRate(testIndex1, Date(10, February, 2021), 3 * Months, CPI::Linear); + // interpolate, then take ratio + Real expected = (293.5 * (19/28.0) + 295.4 * (9/28.0)) / (291.0 * (20/29.0) + 291.9 * (9/29.0)) - 1; + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + calculated = CPI::laggedYoYRate(testIndex2, Date(10, February, 2021), 3 * Months, CPI::Linear); + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + calculated = CPI::laggedYoYRate(testIndex1, Date(12, May, 2021), 3 * Months, CPI::Linear); + expected = (296.0 * (20/31.0) + 296.9 * (11/31.0)) / (292.0 * (20/31.0) + 292.6 * (11/31.0)) - 1; + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + calculated = CPI::laggedYoYRate(testIndex2, Date(12, May, 2021), 3 * Months, CPI::Linear); + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + // this would require April's fixing + BOOST_CHECK_EXCEPTION( + CPI::laggedYoYRate(testIndex1, Date(25, June, 2021), 3 * Months, CPI::Linear), + Error, ExpectedErrorMessage("Missing UK RPI fixing")); + + BOOST_CHECK_EXCEPTION( + CPI::laggedYoYRate(testIndex2, Date(25, June, 2021), 3 * Months, CPI::Linear), + Error, ExpectedErrorMessage("Missing UK RPI fixing")); + + // however, this is a special case + calculated = CPI::laggedYoYRate(testIndex1, Date(1, June, 2021), 3 * Months, CPI::Linear); + expected = 296.9/292.6 - 1; + + QL_CHECK_CLOSE(calculated, expected, 1e-8); + + calculated = CPI::laggedYoYRate(testIndex2, Date(1, June, 2021), 3 * Months, CPI::Linear); + + QL_CHECK_CLOSE(calculated, expected, 1e-8); +} + + BOOST_AUTO_TEST_CASE(testNotifications) { BOOST_TEST_MESSAGE("Testing notifications from zero-inflation cash flow..."); diff --git a/test-suite/inflationcapfloor.cpp b/test-suite/inflationcapfloor.cpp index 98d5a95a472..20434d196ff 100644 --- a/test-suite/inflationcapfloor.cpp +++ b/test-suite/inflationcapfloor.cpp @@ -61,23 +61,24 @@ struct Datum { Rate rate; }; -template -std::vector > > makeHelpers( +std::vector > > makeHelpers( const std::vector& iiData, - const ext::shared_ptr &ii, const Period &observationLag, + const ext::shared_ptr &ii, + CPI::InterpolationType interpolation, + const Period &observationLag, const Calendar &calendar, const BusinessDayConvention &bdc, const DayCounter &dc, const Handle& discountCurve) { - std::vector > > instruments; + std::vector > > instruments; for (Datum datum : iiData) { Date maturity = datum.date; Handle quote(ext::shared_ptr( new SimpleQuote(datum.rate/100.0))); - ext::shared_ptr > anInstrument(new U( + auto anInstrument = ext::make_shared( quote, observationLag, maturity, - calendar, bdc, dc, ii, discountCurve)); + calendar, bdc, dc, ii, interpolation, discountCurve); instruments.push_back(anInstrument); } @@ -169,11 +170,11 @@ struct CommonVars { // now build the helpers ... std::vector > > helpers = - makeHelpers(yyData, iir, - observationLag, - calendar, convention, dc, - Handle(nominalTS)); + makeHelpers(yyData, iir, + CPI::Flat, + observationLag, + calendar, convention, dc, + Handle(nominalTS)); Date baseDate = rpi->lastFixingDate(); Rate baseYYRate = yyData[0].rate/100.0; @@ -195,7 +196,7 @@ struct CommonVars { Schedule schedule(startDate, endDate, Period(frequency), calendar, Unadjusted,Unadjusted,// ref periods & acc periods DateGeneration::Forward, false); - return yoyInflationLeg(schedule, calendar, ii, observationLag) + return yoyInflationLeg(schedule, calendar, ii, observationLag, CPI::Flat) .withNotionals(nominals) .withPaymentDayCounter(dc) .withPaymentAdjustment(convention); @@ -424,7 +425,7 @@ BOOST_AUTO_TEST_CASE(testParity) { YearOnYearInflationSwap swap(Swap::Payer, 1000000.0, yoySchedule, // fixed schedule, but same as yoy strike, vars.dc, yoySchedule, vars.iir, - vars.observationLag, + vars.observationLag, CPI::Flat, 0.0, // spread on index vars.dc, UnitedKingdom()); diff --git a/test-suite/inflationcapflooredcoupon.cpp b/test-suite/inflationcapflooredcoupon.cpp index 568d9e77544..bcd0a742a21 100644 --- a/test-suite/inflationcapflooredcoupon.cpp +++ b/test-suite/inflationcapflooredcoupon.cpp @@ -62,23 +62,24 @@ struct Datum { Rate rate; }; -template -std::vector > > makeHelpers( - const std::vector& iiData, - const ext::shared_ptr &ii, const Period &observationLag, - const Calendar &calendar, - const BusinessDayConvention &bdc, - const DayCounter &dc, - const Handle& discountCurve) { - - std::vector > > instruments; +std::vector > > +makeHelpers(const std::vector& iiData, + const ext::shared_ptr &ii, + const Period &observationLag, + CPI::InterpolationType interpolation, + const Calendar &calendar, + const BusinessDayConvention &bdc, + const DayCounter &dc, + const Handle& discountCurve) { + + std::vector > > instruments; for (Datum datum : iiData) { Date maturity = datum.date; Handle quote(ext::shared_ptr( new SimpleQuote(datum.rate/100.0))); - ext::shared_ptr > anInstrument(new U( + auto anInstrument = ext::make_shared( quote, observationLag, maturity, - calendar, bdc, dc, ii, discountCurve)); + calendar, bdc, dc, ii, interpolation, discountCurve); instruments.push_back(anInstrument); } @@ -177,11 +178,11 @@ struct CommonVars { // now build the helpers ... std::vector > > helpers = - makeHelpers(yyData, iir, - observationLag, - calendar, convention, dc, - Handle(nominalTS)); + makeHelpers(yyData, iir, + observationLag, + CPI::Flat, + calendar, convention, dc, + Handle(nominalTS)); Date baseDate = rpi->lastFixingDate(); Rate baseYYRate = yyData[0].rate/100.0; @@ -210,7 +211,7 @@ struct CommonVars { std::vector gearingVector(length, gearing); std::vector spreadVector(length, spread); - Leg yoyLeg = yoyInflationLeg(schedule, calendar, ii, observationLag) + Leg yoyLeg = yoyInflationLeg(schedule, calendar, ii, observationLag, CPI::Flat) .withNotionals(nominals) .withPaymentDayCounter(dc) .withGearings(gearingVector) @@ -285,7 +286,7 @@ struct CommonVars { Unadjusted,Unadjusted,// ref periods & acc periods DateGeneration::Forward, false); - Leg yoyLeg = yoyInflationLeg(schedule, calendar, ii, observationLag) + Leg yoyLeg = yoyInflationLeg(schedule, calendar, ii, observationLag, CPI::Flat) .withNotionals(nominals) .withPaymentDayCounter(dc) .withPaymentAdjustment(convention) @@ -725,6 +726,7 @@ BOOST_AUTO_TEST_CASE(testInstrumentEquality) { yoySchedule, vars.iir, vars.observationLag, + CPI::Flat, 0.0, //spread on index vars.dc, UnitedKingdom()); diff --git a/test-suite/inflationcpicapfloor.cpp b/test-suite/inflationcpicapfloor.cpp index b05676a1de3..8b61e8c26c3 100644 --- a/test-suite/inflationcpicapfloor.cpp +++ b/test-suite/inflationcpicapfloor.cpp @@ -401,8 +401,8 @@ BOOST_AUTO_TEST_CASE(cpicapfloorpricer) { Calendar fixCalendar = UnitedKingdom(), payCalendar = UnitedKingdom(); BusinessDayConvention fixConvention(Unadjusted), payConvention(ModifiedFollowing); Rate strike(0.03); - Real baseCPI = common.ii->fixing(fixCalendar.adjust(startDate-common.observationLag,fixConvention)); CPI::InterpolationType observationInterpolation = CPI::AsIndex; + Real baseCPI = CPI::laggedFixing(common.ii, startDate, common.observationLag, observationInterpolation); CPICapFloor aCap(Option::Call, nominal, startDate, // start date of contract (only) diff --git a/test-suite/inflationcpiswap.cpp b/test-suite/inflationcpiswap.cpp index d0762525ca2..3a36f63f20e 100644 --- a/test-suite/inflationcpiswap.cpp +++ b/test-suite/inflationcpiswap.cpp @@ -387,8 +387,7 @@ BOOST_AUTO_TEST_CASE(zciisconsistency) { bool subtractInflationNominal = true; Real dummySpread=0.0, dummyFixedRate=0.0; Natural fixingDays = 0; - Date baseDate = startDate - observationLag; - Real baseCPI = common.ii->fixing(baseDate); + Real baseCPI = CPI::laggedFixing(common.ii, startDate, observationLag, CPI::AsIndex); ext::shared_ptr dummyFloatIndex; diff --git a/test-suite/inflationvolatility.cpp b/test-suite/inflationvolatility.cpp index 2d1b2480b49..52f07f2589a 100644 --- a/test-suite/inflationvolatility.cpp +++ b/test-suite/inflationvolatility.cpp @@ -247,7 +247,6 @@ void setupPriceSurface() { Natural fixingDays = 0; Size lag = 3;// must be 3 because we use an interpolated index (EU) Period yyLag = Period(lag,Months); - Rate baseRate = 1; // not really used DayCounter dc = Actual365Fixed(); TARGET cal; BusinessDayConvention bdc = ModifiedFollowing; @@ -256,7 +255,7 @@ void setupPriceSurface() { ext::shared_ptr > cfEUprices(new InterpolatedYoYCapFloorTermPriceSurface( fixingDays, - yyLag, yoyIndexEU, baseRate, + yyLag, yoyIndexEU, CPI::Linear, n, dc, cal, bdc, cStrikesEU, fStrikesEU, cfMaturitiesEU,