From 2a30164d719e3abc8c96dbe9f9d1645a4fc6e755 Mon Sep 17 00:00:00 2001 From: Francois Botha Date: Wed, 18 Oct 2023 10:16:45 +0200 Subject: [PATCH 01/11] Fix indentation after #1040 changed it --- test-suite/bonds.cpp | 109 +++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/test-suite/bonds.cpp b/test-suite/bonds.cpp index f94196413c4..fd4fc632345 100644 --- a/test-suite/bonds.cpp +++ b/test-suite/bonds.cpp @@ -147,15 +147,16 @@ BOOST_AUTO_TEST_CASE(testYield) { bond, calculated, bondDayCount, n, frequencie); if (std::fabs(price - price2) / price > tolerance) { BOOST_ERROR("\nyield recalculation failed:" - "\n issue: " - << issue << "\n maturity: " << maturity - << "\n coupon: " << io::rate(coupon) - << "\n frequency: " << frequencie - << "\n yield: " << io::rate(m) - << (n == Compounded ? " compounded" : " continuous") - << std::setprecision(7) << "\n clean price: " - << price << "\n yield': " << io::rate(calculated) - << "\n clean price': " << price2); + "\n issue: " << issue << + "\n maturity: " << maturity << + "\n coupon: " << io::rate(coupon) << + "\n frequency: " << frequencie << + "\n yield: " << io::rate(m) << + (n == Compounded ? " compounded" : " continuous") << + std::setprecision(7) << + "\n clean price: " << price << + "\n yield': " << io::rate(calculated) << + "\n clean price': " << price2); } } @@ -171,15 +172,16 @@ BOOST_AUTO_TEST_CASE(testYield) { bond, calculated, bondDayCount, n, frequencie); if (std::fabs(price - price2) / price > tolerance) { BOOST_ERROR("\nyield recalculation failed:" - "\n issue: " - << issue << "\n maturity: " << maturity - << "\n coupon: " << io::rate(coupon) - << "\n frequency: " << frequencie - << "\n yield: " << io::rate(m) - << (n == Compounded ? " compounded" : " continuous") - << std::setprecision(7) << "\n dirty price: " - << price << "\n yield': " << io::rate(calculated) - << "\n dirty price': " << price2); + "\n issue: " << issue << + "\n maturity: " << maturity << + "\n coupon: " << io::rate(coupon) << + "\n frequency: " << frequencie << + "\n yield: " << io::rate(m) << + (n == Compounded ? " compounded" : " continuous") << + std::setprecision(7) << + "\n dirty price: " << price << + "\n yield': " << io::rate(calculated) << + "\n dirty price': " << price2); } } } @@ -233,14 +235,15 @@ BOOST_AUTO_TEST_CASE(testAtmRate) { if (std::fabs(coupon - calculated) > tolerance) { BOOST_ERROR("\natm rate recalculation failed:" - "\n today: " - << vars.today << "\n settlement date: " << bond.settlementDate() - << "\n issue: " << issue << "\n maturity: " - << maturity << "\n coupon: " << io::rate(coupon) - << "\n frequency: " << frequencie - << "\n clean price: " << price - << "\n dirty price: " << price + bond.accruedAmount() - << "\n atm rate: " << io::rate(calculated)); + "\n today: " << vars.today << + "\n settlement date: " << bond.settlementDate() << + "\n issue: " << issue << + "\n maturity: " << maturity << + "\n coupon: " << io::rate(coupon) << + "\n frequency: " << frequencie << + "\n clean price: " << price << + "\n dirty price: " << price + bond.accruedAmount() << + "\n atm rate: " << io::rate(calculated)); } } } @@ -305,16 +308,16 @@ BOOST_AUTO_TEST_CASE(testZspread) { bond, *discountCurve, calculated, bondDayCount, n, frequencie); if (std::fabs(price - price2) / price > tolerance) { BOOST_ERROR("\nZ-spread recalculation failed:" - "\n issue: " - << issue << "\n maturity: " << maturity - << "\n coupon: " << io::rate(coupon) - << "\n frequency: " << frequencie - << "\n Z-spread: " << io::rate(spread) - << (n == Compounded ? " compounded" : " continuous") - << std::setprecision(7) - << "\n price: " << price - << "\n Z-spread': " << io::rate(calculated) - << "\n price': " << price2); + "\n issue: " << issue << + "\n maturity: " << maturity << + "\n coupon: " << io::rate(coupon) << + "\n frequency: " << frequencie << + "\n Z-spread: " << io::rate(spread) << + (n == Compounded ? " compounded" : " continuous") << + std::setprecision(7) << + "\n price: " << price << + "\n Z-spread': " << io::rate(calculated) << + "\n price': " << price2); } } } @@ -375,27 +378,31 @@ BOOST_AUTO_TEST_CASE(testTheoretical) { Real calculatedPrice = bond.cleanPrice(); if (std::fabs(price - calculatedPrice) > tolerance) { - BOOST_ERROR("price calculation failed:" - << "\n issue: " << issue << "\n maturity: " - << maturity << "\n coupon: " << io::rate(coupon) - << "\n frequency: " << frequencie - << "\n yield: " << io::rate(m) << std::setprecision(7) - << "\n expected: " << price - << "\n calculated': " << calculatedPrice - << "\n error': " << price - calculatedPrice); + BOOST_ERROR("price calculation failed:" << + "\n issue: " << issue << + "\n maturity: " << maturity << + "\n coupon: " << io::rate(coupon) << + "\n frequency: " << frequencie << + "\n yield: " << io::rate(m) << + std::setprecision(7) << + "\n expected: " << price << + "\n calculated': " << calculatedPrice << + "\n error': " << price - calculatedPrice); } Rate calculatedYield = BondFunctions::yield( bond, calculatedPrice, bondDayCount, Continuous, frequencie, bond.settlementDate(), tolerance, maxEvaluations); if (std::fabs(m - calculatedYield) > tolerance) { - BOOST_ERROR("yield calculation failed:" - << "\n issue: " << issue << "\n maturity: " - << maturity << "\n coupon: " << io::rate(coupon) - << "\n frequency: " << frequencie - << "\n yield: " << io::rate(m) << std::setprecision(7) - << "\n price: " << price - << "\n yield': " << io::rate(calculatedYield)); + BOOST_ERROR("yield calculation failed:" << + "\n issue: " << issue << + "\n maturity: " << maturity << + "\n coupon: " << io::rate(coupon) << + "\n frequency: " << frequencie << + "\n yield: " << io::rate(m) << + std::setprecision(7) << + "\n price: " << price << + "\n yield': " << io::rate(calculatedYield)); } } } From 53346f890af4d2577b380af949092da8ec203f83 Mon Sep 17 00:00:00 2001 From: Francois Botha Date: Tue, 17 Oct 2023 17:13:00 +0200 Subject: [PATCH 02/11] Fix spelling of 'frequency' --- test-suite/bonds.cpp | 50 ++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/test-suite/bonds.cpp b/test-suite/bonds.cpp index fd4fc632345..532037f508d 100644 --- a/test-suite/bonds.cpp +++ b/test-suite/bonds.cpp @@ -117,14 +117,14 @@ BOOST_AUTO_TEST_CASE(testYield) { for (int issueMonth : issueMonths) { for (int length : lengths) { for (Real& coupon : coupons) { - for (auto& frequencie : frequencies) { + for (auto& frequency : frequencies) { for (auto& n : compounding) { Date dated = vars.calendar.advance(vars.today, issueMonth, Months); Date issue = dated; Date maturity = vars.calendar.advance(issue, length, Years); - Schedule sch(dated, maturity, Period(frequencie), vars.calendar, + Schedule sch(dated, maturity, Period(frequency), vars.calendar, accrualConvention, accrualConvention, DateGeneration::Backward, false); @@ -135,22 +135,22 @@ BOOST_AUTO_TEST_CASE(testYield) { for (Real m : yields) { Real price = - BondFunctions::cleanPrice(bond, m, bondDayCount, n, frequencie); + BondFunctions::cleanPrice(bond, m, bondDayCount, n, frequency); Rate calculated = BondFunctions::yield( - bond, price, bondDayCount, n, frequencie, Date(), tolerance, + bond, price, bondDayCount, n, frequency, Date(), tolerance, maxEvaluations, 0.05, Bond::Price::Clean); if (std::fabs(m - calculated) > tolerance) { // the difference might not matter Real price2 = BondFunctions::cleanPrice( - bond, calculated, bondDayCount, n, frequencie); + bond, calculated, bondDayCount, n, frequency); if (std::fabs(price - price2) / price > tolerance) { BOOST_ERROR("\nyield recalculation failed:" "\n issue: " << issue << "\n maturity: " << maturity << "\n coupon: " << io::rate(coupon) << - "\n frequency: " << frequencie << + "\n frequency: " << frequency << "\n yield: " << io::rate(m) << (n == Compounded ? " compounded" : " continuous") << std::setprecision(7) << @@ -160,22 +160,22 @@ BOOST_AUTO_TEST_CASE(testYield) { } } - price = BondFunctions::dirtyPrice(bond, m, bondDayCount, n, frequencie); + price = BondFunctions::dirtyPrice(bond, m, bondDayCount, n, frequency); calculated = BondFunctions::yield( - bond, price, bondDayCount, n, frequencie, Date(), tolerance, + bond, price, bondDayCount, n, frequency, Date(), tolerance, maxEvaluations, 0.05, Bond::Price::Dirty); if (std::fabs(m - calculated) > tolerance) { // the difference might not matter Real price2 = BondFunctions::dirtyPrice( - bond, calculated, bondDayCount, n, frequencie); + bond, calculated, bondDayCount, n, frequency); if (std::fabs(price - price2) / price > tolerance) { BOOST_ERROR("\nyield recalculation failed:" "\n issue: " << issue << "\n maturity: " << maturity << "\n coupon: " << io::rate(coupon) << - "\n frequency: " << frequencie << + "\n frequency: " << frequency << "\n yield: " << io::rate(m) << (n == Compounded ? " compounded" : " continuous") << std::setprecision(7) << @@ -215,12 +215,12 @@ BOOST_AUTO_TEST_CASE(testAtmRate) { for (int issueMonth : issueMonths) { for (int length : lengths) { for (Real& coupon : coupons) { - for (auto& frequencie : frequencies) { + for (auto& frequency : frequencies) { Date dated = vars.calendar.advance(vars.today, issueMonth, Months); Date issue = dated; Date maturity = vars.calendar.advance(issue, length, Years); - Schedule sch(dated, maturity, Period(frequencie), vars.calendar, + Schedule sch(dated, maturity, Period(frequency), vars.calendar, accrualConvention, accrualConvention, DateGeneration::Backward, false); @@ -240,7 +240,7 @@ BOOST_AUTO_TEST_CASE(testAtmRate) { "\n issue: " << issue << "\n maturity: " << maturity << "\n coupon: " << io::rate(coupon) << - "\n frequency: " << frequencie << + "\n frequency: " << frequency << "\n clean price: " << price << "\n dirty price: " << price + bond.accruedAmount() << "\n atm rate: " << io::rate(calculated)); @@ -279,14 +279,14 @@ BOOST_AUTO_TEST_CASE(testZspread) { for (int issueMonth : issueMonths) { for (int length : lengths) { for (Real& coupon : coupons) { - for (auto& frequencie : frequencies) { + for (auto& frequency : frequencies) { for (auto& n : compounding) { Date dated = vars.calendar.advance(vars.today, issueMonth, Months); Date issue = dated; Date maturity = vars.calendar.advance(issue, length, Years); - Schedule sch(dated, maturity, Period(frequencie), vars.calendar, + Schedule sch(dated, maturity, Period(frequency), vars.calendar, accrualConvention, accrualConvention, DateGeneration::Backward, false); @@ -297,21 +297,21 @@ BOOST_AUTO_TEST_CASE(testZspread) { for (Real spread : spreads) { Real price = BondFunctions::cleanPrice(bond, *discountCurve, spread, - bondDayCount, n, frequencie); + bondDayCount, n, frequency); Spread calculated = BondFunctions::zSpread( - bond, price, *discountCurve, bondDayCount, n, frequencie, Date(), + bond, price, *discountCurve, bondDayCount, n, frequency, Date(), tolerance, maxEvaluations); if (std::fabs(spread - calculated) > tolerance) { // the difference might not matter Real price2 = BondFunctions::cleanPrice( - bond, *discountCurve, calculated, bondDayCount, n, frequencie); + bond, *discountCurve, calculated, bondDayCount, n, frequency); if (std::fabs(price - price2) / price > tolerance) { BOOST_ERROR("\nZ-spread recalculation failed:" "\n issue: " << issue << "\n maturity: " << maturity << "\n coupon: " << io::rate(coupon) << - "\n frequency: " << frequencie << + "\n frequency: " << frequency << "\n Z-spread: " << io::rate(spread) << (n == Compounded ? " compounded" : " continuous") << std::setprecision(7) << @@ -350,7 +350,7 @@ BOOST_AUTO_TEST_CASE(testTheoretical) { for (unsigned long length : lengths) { for (Real& coupon : coupons) { - for (auto& frequencie : frequencies) { + for (auto& frequency : frequencies) { Date dated = vars.today; Date issue = dated; @@ -359,7 +359,7 @@ BOOST_AUTO_TEST_CASE(testTheoretical) { ext::shared_ptr rate(new SimpleQuote(0.0)); Handle discountCurve(flatRate(vars.today, rate, bondDayCount)); - Schedule sch(dated, maturity, Period(frequencie), vars.calendar, accrualConvention, + Schedule sch(dated, maturity, Period(frequency), vars.calendar, accrualConvention, accrualConvention, DateGeneration::Backward, false); FixedRateBond bond(settlementDays, vars.faceAmount, sch, @@ -374,7 +374,7 @@ BOOST_AUTO_TEST_CASE(testTheoretical) { rate->setValue(m); Real price = - BondFunctions::cleanPrice(bond, m, bondDayCount, Continuous, frequencie); + BondFunctions::cleanPrice(bond, m, bondDayCount, Continuous, frequency); Real calculatedPrice = bond.cleanPrice(); if (std::fabs(price - calculatedPrice) > tolerance) { @@ -382,7 +382,7 @@ BOOST_AUTO_TEST_CASE(testTheoretical) { "\n issue: " << issue << "\n maturity: " << maturity << "\n coupon: " << io::rate(coupon) << - "\n frequency: " << frequencie << + "\n frequency: " << frequency << "\n yield: " << io::rate(m) << std::setprecision(7) << "\n expected: " << price << @@ -391,14 +391,14 @@ BOOST_AUTO_TEST_CASE(testTheoretical) { } Rate calculatedYield = BondFunctions::yield( - bond, calculatedPrice, bondDayCount, Continuous, frequencie, + bond, calculatedPrice, bondDayCount, Continuous, frequency, bond.settlementDate(), tolerance, maxEvaluations); if (std::fabs(m - calculatedYield) > tolerance) { BOOST_ERROR("yield calculation failed:" << "\n issue: " << issue << "\n maturity: " << maturity << "\n coupon: " << io::rate(coupon) << - "\n frequency: " << frequencie << + "\n frequency: " << frequency << "\n yield: " << io::rate(m) << std::setprecision(7) << "\n price: " << price << From ceef8c0d96e213a050066c9c8947adbeb7cc570c Mon Sep 17 00:00:00 2001 From: Francois Botha Date: Mon, 26 Feb 2024 20:38:19 +0200 Subject: [PATCH 03/11] Use Bond::Price class instead of separate Real/Bond::Price::Type arguments. Mark old methods as deprecated. --- Examples/Bonds/Bonds.cpp | 5 +- Examples/FittedBondCurve/FittedBondCurve.cpp | 2 +- Examples/Repo/Repo.cpp | 2 +- ql/instruments/bond.cpp | 17 +- ql/instruments/bond.hpp | 11 ++ ql/instruments/bonds/btp.cpp | 15 +- ql/pricingengines/bond/bondfunctions.cpp | 14 +- ql/pricingengines/bond/bondfunctions.hpp | 36 ++++- .../yield/fittedbonddiscountcurve.cpp | 5 +- test-suite/bonds.cpp | 152 ++++++++---------- test-suite/fittedbonddiscountcurve.cpp | 6 +- 11 files changed, 154 insertions(+), 111 deletions(-) diff --git a/Examples/Bonds/Bonds.cpp b/Examples/Bonds/Bonds.cpp index 099b27d9021..173c67da118 100644 --- a/Examples/Bonds/Bonds.cpp +++ b/Examples/Bonds/Bonds.cpp @@ -541,7 +541,10 @@ int main(int, char* []) { << floatingRateBond.cleanPrice(floatingRateBond.yield(Actual360(),Compounded,Annual),Actual360(),Compounded,Annual,settlementDate) << std::endl; std::cout << "Clean Price to Yield: " - << io::rate(floatingRateBond.yield(floatingRateBond.cleanPrice(),Actual360(),Compounded,Annual,settlementDate)) << std::endl; + << io::rate(floatingRateBond.yield( + Bond::Price(floatingRateBond.cleanPrice(), Bond::Price::Clean), + Actual360(), Compounded, Annual, settlementDate)) + << std::endl; /* "Yield to Price" "Price to Yield" */ diff --git a/Examples/FittedBondCurve/FittedBondCurve.cpp b/Examples/FittedBondCurve/FittedBondCurve.cpp index c0d1f6c61ff..a04b58d493a 100644 --- a/Examples/FittedBondCurve/FittedBondCurve.cpp +++ b/Examples/FittedBondCurve/FittedBondCurve.cpp @@ -592,7 +592,7 @@ int main(int, char* []) { Real P = instrumentsA[k]->quote()->value(); const Bond& b = *instrumentsA[k]->bond(); - Rate ytm = BondFunctions::yield(b, P, + Rate ytm = BondFunctions::yield(b, Bond::Price(P, Bond::Price::Clean), dc, Compounded, frequency, today); Time dur = BondFunctions::duration(b, ytm, diff --git a/Examples/Repo/Repo.cpp b/Examples/Repo/Repo.cpp index 52eaea9bf17..2caac6f0152 100644 --- a/Examples/Repo/Repo.cpp +++ b/Examples/Repo/Repo.cpp @@ -73,7 +73,7 @@ int main(int, char* []) { // unknown what fincad is using. this may affect accrued calculation Integer bondSettlementDays = 0; BusinessDayConvention bondBusinessDayConvention = Unadjusted; - Real bondCleanPrice = 89.97693786; + Bond::Price bondCleanPrice(89.97693786, Bond::Price::Clean); Real bondRedemption = 100.0; Real faceAmount = 100.0; diff --git a/ql/instruments/bond.cpp b/ql/instruments/bond.cpp index abecef6add6..829a2ceefd6 100644 --- a/ql/instruments/bond.cpp +++ b/ql/instruments/bond.cpp @@ -206,12 +206,12 @@ namespace QuantLib { if (currentNotional == 0.0) return 0.0; - Real price = priceType == Bond::Price::Clean ? cleanPrice() : dirtyPrice(); + Bond::Price price(priceType == Bond::Price::Clean ? cleanPrice() : dirtyPrice(), priceType); return BondFunctions::yield(*this, price, dc, comp, freq, settlementDate(), accuracy, maxEvaluations, - guess, priceType); + guess); } Real Bond::cleanPrice(Rate y, @@ -244,13 +244,24 @@ namespace QuantLib { Size maxEvaluations, Real guess, Bond::Price::Type priceType) const { + return yield(Bond::Price(price, priceType), dc, comp, freq, settlement, accuracy, + maxEvaluations, guess); + } + Rate Bond::yield(Bond::Price price, + const DayCounter& dc, + Compounding comp, + Frequency freq, + Date settlement, + Real accuracy, + Size maxEvaluations, + Real guess) const { Real currentNotional = notional(settlement); if (currentNotional == 0.0) return 0.0; return BondFunctions::yield(*this, price, dc, comp, freq, settlement, accuracy, maxEvaluations, - guess, priceType); + guess); } Real Bond::accruedAmount(Date settlement) const { diff --git a/ql/instruments/bond.hpp b/ql/instruments/bond.hpp index f4401c75269..e638fda2763 100644 --- a/ql/instruments/bond.hpp +++ b/ql/instruments/bond.hpp @@ -196,6 +196,7 @@ namespace QuantLib { //! yield given a price and settlement date /*! The default bond settlement is used if no date is given. */ + [[deprecated("Use the version with the Bond::Price argument")]] Rate yield(Real price, const DayCounter& dc, Compounding comp, @@ -205,6 +206,16 @@ namespace QuantLib { Size maxEvaluations = 100, Real guess = 0.05, Bond::Price::Type priceType = Bond::Price::Clean) const; + //! yield given a price and settlement date + /*! The default bond settlement is used if no date is given. */ + Rate yield(Bond::Price price, + const DayCounter& dc, + Compounding comp, + Frequency freq, + Date settlementDate = Date(), + Real accuracy = 1.0e-8, + Size maxEvaluations = 100, + Real guess = 0.05) const; //! accrued amount at a given date /*! The default bond settlement is used if no date is given. */ diff --git a/ql/instruments/bonds/btp.cpp b/ql/instruments/bonds/btp.cpp index 67ca4db67d1..8681be91220 100644 --- a/ql/instruments/bonds/btp.cpp +++ b/ql/instruments/bonds/btp.cpp @@ -83,9 +83,9 @@ namespace QuantLib { Date settlementDate, Real accuracy, Size maxEvaluations) const { - return Bond::yield(cleanPrice, ActualActual(ActualActual::ISMA), - Compounded, Annual, - settlementDate, accuracy, maxEvaluations); + return Bond::yield(Bond::Price(cleanPrice, Bond::Price::Clean), + ActualActual(ActualActual::ISMA), Compounded, Annual, settlementDate, + accuracy, maxEvaluations); } @@ -160,9 +160,8 @@ namespace QuantLib { Date bondSettlementDate = btps[0]->settlementDate(); for (Size i=0; isize(); ++i) { yields_[i] = BondFunctions::yield( - *btps[i], quotes[i]->value(), - ActualActual(ActualActual::ISMA), Compounded, Annual, - bondSettlementDate, + *btps[i], Bond::Price(quotes[i]->value(), Bond::Price::Clean), + ActualActual(ActualActual::ISMA), Compounded, Annual, bondSettlementDate, // accuracy, maxIterations, guess 1.0e-10, 100, yields_[i]); durations_[i] = BondFunctions::duration( @@ -186,7 +185,7 @@ namespace QuantLib { Following, // paymentConvention 100.0); // redemption swapBondYields_[0] = BondFunctions::yield(swapBond, - 100.0, // floating leg NPV including end payment + Bond::Price(100.0, Bond::Price::Clean), // floating leg NPV including end payment ActualActual(ActualActual::ISMA), Compounded, Annual, bondSettlementDate, // accuracy, maxIterations, guess @@ -205,7 +204,7 @@ namespace QuantLib { Following, // paymentConvention 100.0); // redemption swapBondYields_[i] = BondFunctions::yield(swapBond, - 100.0, // floating leg NPV including end payment + Bond::Price(100.0, Bond::Price::Clean), // floating leg NPV including end payment ActualActual(ActualActual::ISMA), Compounded, Annual, bondSettlementDate, // accuracy, maxIterations, guess diff --git a/ql/pricingengines/bond/bondfunctions.cpp b/ql/pricingengines/bond/bondfunctions.cpp index d6240cf3533..10aa5aa7c38 100644 --- a/ql/pricingengines/bond/bondfunctions.cpp +++ b/ql/pricingengines/bond/bondfunctions.cpp @@ -367,11 +367,23 @@ namespace QuantLib { Size maxIterations, Rate guess, Bond::Price::Type priceType) { + return yield(bond, Bond::Price(price, priceType), dayCounter, compounding, frequency, + settlement, accuracy, maxIterations, guess); + } + Rate BondFunctions::yield(const Bond& bond, + Bond::Price price, + const DayCounter& dayCounter, + Compounding compounding, + Frequency frequency, + Date settlement, + Real accuracy, + Size maxIterations, + Rate guess) { NewtonSafe solver; solver.setMaxEvaluations(maxIterations); return yield(solver, bond, price, dayCounter, compounding, frequency, settlement, - accuracy, guess, priceType); + accuracy, guess); } Time BondFunctions::duration(const Bond& bond, diff --git a/ql/pricingengines/bond/bondfunctions.hpp b/ql/pricingengines/bond/bondfunctions.hpp index a699433b159..07e491db698 100644 --- a/ql/pricingengines/bond/bondfunctions.hpp +++ b/ql/pricingengines/bond/bondfunctions.hpp @@ -146,6 +146,7 @@ namespace QuantLib { Compounding compounding, Frequency frequency, Date settlementDate = Date()); + [[deprecated("Use the version with the Bond::Price argument")]] static Rate yield(const Bond& bond, Real price, const DayCounter& dayCounter, @@ -156,7 +157,17 @@ namespace QuantLib { Size maxIterations = 100, Rate guess = 0.05, Bond::Price::Type priceType = Bond::Price::Clean); + static Rate yield(const Bond& bond, + Bond::Price price, + const DayCounter& dayCounter, + Compounding compounding, + Frequency frequency, + Date settlementDate = Date(), + Real accuracy = 1.0e-10, + Size maxIterations = 100, + Rate guess = 0.05); template + [[deprecated("Use the version with the Bond::Price argument")]] static Rate yield(const Solver& solver, const Bond& bond, Real price, @@ -167,6 +178,19 @@ namespace QuantLib { Real accuracy = 1.0e-10, Rate guess = 0.05, Bond::Price::Type priceType = Bond::Price::Clean) { + return yield(solver, Bond::Price(price, priceType), dayCounter, compounding, frequency, + settlementDate, accuracy, guess); + } + template + static Rate yield(const Solver& solver, + const Bond& bond, + Bond::Price price, + const DayCounter& dayCounter, + Compounding compounding, + Frequency frequency, + Date settlementDate = Date(), + Real accuracy = 1.0e-10, + Rate guess = 0.05) { if (settlementDate == Date()) settlementDate = bond.settlementDate(); @@ -174,15 +198,15 @@ namespace QuantLib { "non tradable at " << settlementDate << " (maturity being " << bond.maturityDate() << ")"); - Real dirtyPrice = price; + Real amount = price.amount(); - if (priceType == Bond::Price::Clean) - dirtyPrice += bond.accruedAmount(settlementDate); + if (price.type() == Bond::Price::Clean) + amount += bond.accruedAmount(settlementDate); - dirtyPrice /= 100.0 / bond.notional(settlementDate); + amount /= 100.0 / bond.notional(settlementDate); - return CashFlows::yield(solver, bond.cashflows(), - dirtyPrice, dayCounter, compounding, + return CashFlows::yield(solver, bond.cashflows(), amount, dayCounter, + compounding, frequency, false, settlementDate, settlementDate, accuracy, guess); } diff --git a/ql/termstructures/yield/fittedbonddiscountcurve.cpp b/ql/termstructures/yield/fittedbonddiscountcurve.cpp index f4513700d44..21bd50e8e9d 100644 --- a/ql/termstructures/yield/fittedbonddiscountcurve.cpp +++ b/ql/termstructures/yield/fittedbonddiscountcurve.cpp @@ -149,10 +149,11 @@ namespace QuantLib { for (Size i=0; ibondHelpers_.size(); ++i) { ext::shared_ptr bond = curve_->bondHelpers_[i]->bond(); - Real cleanPrice = curve_->bondHelpers_[i]->quote()->value(); + Real amount = curve_->bondHelpers_[i]->quote()->value(); + Bond::Price price(amount, curve_->bondHelpers_[i]->priceType()); Date bondSettlement = bond->settlementDate(); - Rate ytm = BondFunctions::yield(*bond, cleanPrice, + Rate ytm = BondFunctions::yield(*bond, price, yieldDC, yieldComp, yieldFreq, bondSettlement); diff --git a/test-suite/bonds.cpp b/test-suite/bonds.cpp index 532037f508d..224eb925822 100644 --- a/test-suite/bonds.cpp +++ b/test-suite/bonds.cpp @@ -134,18 +134,20 @@ BOOST_AUTO_TEST_CASE(testYield) { for (Real m : yields) { - Real price = - BondFunctions::cleanPrice(bond, m, bondDayCount, n, frequency); + Bond::Price price( + BondFunctions::cleanPrice(bond, m, bondDayCount, n, frequency), + Bond::Price::Clean); Rate calculated = BondFunctions::yield( bond, price, bondDayCount, n, frequency, Date(), tolerance, - maxEvaluations, 0.05, Bond::Price::Clean); + maxEvaluations, 0.05); if (std::fabs(m - calculated) > tolerance) { // the difference might not matter Real price2 = BondFunctions::cleanPrice( bond, calculated, bondDayCount, n, frequency); - if (std::fabs(price - price2) / price > tolerance) { + if (std::fabs(price.amount() - price2) / price.amount() > + tolerance) { BOOST_ERROR("\nyield recalculation failed:" "\n issue: " << issue << "\n maturity: " << maturity << @@ -154,23 +156,25 @@ BOOST_AUTO_TEST_CASE(testYield) { "\n yield: " << io::rate(m) << (n == Compounded ? " compounded" : " continuous") << std::setprecision(7) << - "\n clean price: " << price << + "\n clean price: " << price.amount() << "\n yield': " << io::rate(calculated) << "\n clean price': " << price2); } } - price = BondFunctions::dirtyPrice(bond, m, bondDayCount, n, frequency); + price = Bond::Price( + BondFunctions::dirtyPrice(bond, m, bondDayCount, n, frequency), + Bond::Price::Dirty); calculated = BondFunctions::yield( bond, price, bondDayCount, n, frequency, Date(), tolerance, - maxEvaluations, 0.05, Bond::Price::Dirty); + maxEvaluations, 0.05); if (std::fabs(m - calculated) > tolerance) { // the difference might not matter Real price2 = BondFunctions::dirtyPrice( bond, calculated, bondDayCount, n, frequency); - if (std::fabs(price - price2) / price > tolerance) { + if (std::fabs(price.amount() - price2) / price.amount() > tolerance) { BOOST_ERROR("\nyield recalculation failed:" "\n issue: " << issue << "\n maturity: " << maturity << @@ -179,7 +183,7 @@ BOOST_AUTO_TEST_CASE(testYield) { "\n yield: " << io::rate(m) << (n == Compounded ? " compounded" : " continuous") << std::setprecision(7) << - "\n dirty price: " << price << + "\n dirty price: " << price.amount() << "\n yield': " << io::rate(calculated) << "\n dirty price': " << price2); } @@ -391,8 +395,8 @@ BOOST_AUTO_TEST_CASE(testTheoretical) { } Rate calculatedYield = BondFunctions::yield( - bond, calculatedPrice, bondDayCount, Continuous, frequency, - bond.settlementDate(), tolerance, maxEvaluations); + bond, Bond::Price(calculatedPrice, Bond::Price::Clean), bondDayCount, + Continuous, frequency, bond.settlementDate(), tolerance, maxEvaluations); if (std::fabs(m - calculatedYield) > tolerance) { BOOST_ERROR("yield calculation failed:" << "\n issue: " << issue << @@ -519,42 +523,31 @@ BOOST_AUTO_TEST_CASE(testCached) { tolerance, "failed to reproduce cached clean price with no schdule for bond 1:" ); - checkValue( - BondFunctions::yield(bond1, marketPrice1, bondDayCount1, Compounded, freq), - cachedYield1a, - tolerance, - "failed to reproduce cached compounded yield with schedule for bond 1:" - ); - checkValue( - BondFunctions::yield(bond1NoSchedule, marketPrice1, bondDayCount1NoSchedule, Compounded, freq), - cachedYield1a, - tolerance, - "failed to reproduce cached compounded yield with no schedule for bond 1:" - ); - checkValue( - BondFunctions::yield(bond1, marketPrice1, bondDayCount1, Continuous, freq), - cachedYield1b, - tolerance, - "failed to reproduce cached continuous yield with schedule for bond 1:" - ); - checkValue( - BondFunctions::yield(bond1NoSchedule, marketPrice1, bondDayCount1NoSchedule, Continuous, freq), - cachedYield1b, - tolerance, - "failed to reproduce cached continuous yield with no schedule for bond 1:" - ); - checkValue( - BondFunctions::yield(bond1, bond1.cleanPrice(), bondDayCount1, Continuous, freq, bond1.settlementDate()), - cachedYield1c, - tolerance, - "failed to reproduce cached continuous yield with schedule for bond 1:" - ); - checkValue( - BondFunctions::yield(bond1NoSchedule, bond1NoSchedule.cleanPrice(), bondDayCount1NoSchedule, Continuous, freq, bond1.settlementDate()), - cachedYield1c, - tolerance, - "failed to reproduce cached continuous yield with no schedule for bond 1:" - ); + checkValue(BondFunctions::yield(bond1, Bond::Price(marketPrice1, Bond::Price::Clean), + bondDayCount1, Compounded, freq), + cachedYield1a, tolerance, + "failed to reproduce cached compounded yield with schedule for bond 1:"); + checkValue(BondFunctions::yield(bond1NoSchedule, Bond::Price(marketPrice1, Bond::Price::Clean), + bondDayCount1NoSchedule, Compounded, freq), + cachedYield1a, tolerance, + "failed to reproduce cached compounded yield with no schedule for bond 1:"); + checkValue(BondFunctions::yield(bond1, Bond::Price(marketPrice1, Bond::Price::Clean), + bondDayCount1, Continuous, freq), + cachedYield1b, tolerance, + "failed to reproduce cached continuous yield with schedule for bond 1:"); + checkValue(BondFunctions::yield(bond1NoSchedule, Bond::Price(marketPrice1, Bond::Price::Clean), + bondDayCount1NoSchedule, Continuous, freq), + cachedYield1b, tolerance, + "failed to reproduce cached continuous yield with no schedule for bond 1:"); + checkValue(BondFunctions::yield(bond1, Bond::Price(bond1.cleanPrice(), Bond::Price::Clean), + bondDayCount1, Continuous, freq, bond1.settlementDate()), + cachedYield1c, tolerance, + "failed to reproduce cached continuous yield with schedule for bond 1:"); + checkValue(BondFunctions::yield( + bond1NoSchedule, Bond::Price(bond1NoSchedule.cleanPrice(), Bond::Price::Clean), + bondDayCount1NoSchedule, Continuous, freq, bond1.settlementDate()), + cachedYield1c, tolerance, + "failed to reproduce cached continuous yield with no schedule for bond 1:"); //Now bond 2 @@ -582,42 +575,31 @@ BOOST_AUTO_TEST_CASE(testCached) { tolerance, "failed to reproduce cached clean price with no schedule for bond 2:" ); - checkValue( - BondFunctions::yield(bond2, marketPrice2, bondDayCount2, Compounded, freq), - cachedYield2a, - tolerance, - "failed to reproduce cached compounded yield with schedule for bond 2:" - ); - checkValue( - BondFunctions::yield(bond2NoSchedule, marketPrice2, bondDayCount2NoSchedule, Compounded, freq), - cachedYield2a, - tolerance, - "failed to reproduce cached compounded yield with no schedule for bond 2:" - ); - checkValue( - BondFunctions::yield(bond2, marketPrice2, bondDayCount2, Continuous, freq), - cachedYield2b, - tolerance, - "failed to reproduce chached continuous yield with schedule for bond 2:" - ); - checkValue( - BondFunctions::yield(bond2NoSchedule, marketPrice2, bondDayCount2NoSchedule, Continuous, freq), - cachedYield2b, - tolerance, - "failed to reproduce cached continuous yield with schedule for bond 2:" - ); - checkValue( - BondFunctions::yield(bond2, bond2.cleanPrice(), bondDayCount2, Continuous, freq, bond2.settlementDate()), - cachedYield2c, - tolerance, - "failed to reproduce cached continuous yield for bond 2 with schedule:" - ); - checkValue( - BondFunctions::yield(bond2NoSchedule, bond2NoSchedule.cleanPrice(), bondDayCount2NoSchedule, Continuous, freq, bond2NoSchedule.settlementDate()), - cachedYield2c, - tolerance, - "failed to reproduce cached continuous yield for bond 2 with no schedule:" - ); + checkValue(BondFunctions::yield(bond2, Bond::Price(marketPrice2, Bond::Price::Clean), + bondDayCount2, Compounded, freq), + cachedYield2a, tolerance, + "failed to reproduce cached compounded yield with schedule for bond 2:"); + checkValue(BondFunctions::yield(bond2NoSchedule, Bond::Price(marketPrice2, Bond::Price::Clean), + bondDayCount2NoSchedule, Compounded, freq), + cachedYield2a, tolerance, + "failed to reproduce cached compounded yield with no schedule for bond 2:"); + checkValue(BondFunctions::yield(bond2, Bond::Price(marketPrice2, Bond::Price::Clean), + bondDayCount2, Continuous, freq), + cachedYield2b, tolerance, + "failed to reproduce chached continuous yield with schedule for bond 2:"); + checkValue(BondFunctions::yield(bond2NoSchedule, Bond::Price(marketPrice2, Bond::Price::Clean), + bondDayCount2NoSchedule, Continuous, freq), + cachedYield2b, tolerance, + "failed to reproduce cached continuous yield with schedule for bond 2:"); + checkValue(BondFunctions::yield(bond2, Bond::Price(bond2.cleanPrice(), Bond::Price::Clean), + bondDayCount2, Continuous, freq, bond2.settlementDate()), + cachedYield2c, tolerance, + "failed to reproduce cached continuous yield for bond 2 with schedule:"); + checkValue(BondFunctions::yield( + bond2NoSchedule, Bond::Price(bond2NoSchedule.cleanPrice(), Bond::Price::Clean), + bondDayCount2NoSchedule, Continuous, freq, bond2NoSchedule.settlementDate()), + cachedYield2c, tolerance, + "failed to reproduce cached continuous yield for bond 2 with no schedule:"); @@ -1672,7 +1654,7 @@ BOOST_AUTO_TEST_CASE(testThirty360BondWithSettlementOn31st){ Unadjusted, 100.0); - Real cleanPrice = 100.0; + Bond::Price cleanPrice(100.0, Bond::Price::Clean); Real yield = BondFunctions::yield(fixedRateBond, cleanPrice, dayCounter, compounding, Semiannual, settlement); ASSERT_CLOSE("yield", settlement, yield, 0.015, 1e-4); @@ -1720,7 +1702,7 @@ BOOST_AUTO_TEST_CASE(testBasisPointValue) { 100.0); Date defaultSettlement = fixedRateBond.settlementDate(); - Real cleanPrice = 102.890625; + Bond::Price cleanPrice(102.890625, Bond::Price::Clean); Real tolerance = 1e-6; diff --git a/test-suite/fittedbonddiscountcurve.cpp b/test-suite/fittedbonddiscountcurve.cpp index 03c9e11fafb..11207b29352 100644 --- a/test-suite/fittedbonddiscountcurve.cpp +++ b/test-suite/fittedbonddiscountcurve.cpp @@ -144,7 +144,7 @@ BOOST_AUTO_TEST_CASE(testFlatExtrapolation) { // extract the model prices using the two curves - std::vector modelPrices1, modelPrices2; + std::vector modelPrices1, modelPrices2; ext::shared_ptr engine1 = ext::make_shared(Handle(curve1)); @@ -153,9 +153,9 @@ BOOST_AUTO_TEST_CASE(testFlatExtrapolation) { for (auto& bond : bonds) { bond->setPricingEngine(engine1); - modelPrices1.push_back(bond->cleanPrice()); + modelPrices1.push_back(Bond::Price(bond->cleanPrice(), Bond::Price::Clean)); bond->setPricingEngine(engine2); - modelPrices2.push_back(bond->cleanPrice()); + modelPrices2.push_back(Bond::Price(bond->cleanPrice(), Bond::Price::Clean)); } BOOST_CHECK_EQUAL(curve1->fitResults().errorCode(), EndCriteria::MaxIterations); BOOST_CHECK_EQUAL(curve2->fitResults().errorCode(), EndCriteria::MaxIterations); From 2ee2bf7721fe57f9bffab87e08a36052bef22845 Mon Sep 17 00:00:00 2001 From: Francois Botha Date: Tue, 27 Feb 2024 10:46:14 +0200 Subject: [PATCH 04/11] Add Bond::Price constructor and equality/inequality operators with single arguemnt to enable Null support --- ql/instruments/bond.cpp | 8 ++++++++ ql/instruments/bond.hpp | 11 ++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/ql/instruments/bond.cpp b/ql/instruments/bond.cpp index 829a2ceefd6..1b22ed3c508 100644 --- a/ql/instruments/bond.cpp +++ b/ql/instruments/bond.cpp @@ -414,4 +414,12 @@ namespace QuantLib { QL_REQUIRE(cf, "null cash flow provided"); } + bool operator==(const Bond::Price& p1, const Bond::Price& p2) { + return (p1.amount() == p2.amount()) && ((p1.type() == p2.type())); + } + + bool operator!=(const Bond::Price& p1, const Bond::Price& p2) { + return (p1.amount() != p2.amount()) || ((p1.type() != p2.type())); + } + } diff --git a/ql/instruments/bond.hpp b/ql/instruments/bond.hpp index e638fda2763..c1510ff6055 100644 --- a/ql/instruments/bond.hpp +++ b/ql/instruments/bond.hpp @@ -62,7 +62,8 @@ namespace QuantLib { class Price { public: enum Type { Dirty, Clean }; - Price() : amount_(Null()) {} + Price() : amount_(Null()), type_(Bond::Price::Clean) {} + Price(Real amount) : amount_(amount), type_(Bond::Price::Clean) {} Price(Real amount, Type type) : amount_(amount), type_(type) {} Real amount() const { QL_REQUIRE(amount_ != Null(), "no amount given"); @@ -350,6 +351,14 @@ namespace QuantLib { return issueDate_; } +/*! Returns true iff the two bond prices belong to the same + derived class. + \relates Bond::Price +*/ + bool operator==(const Bond::Price&, const Bond::Price&); + + /*! \relates Bond::Price */ + bool operator!=(const Bond::Price&, const Bond::Price&); } #endif From 28c71b7318d9413016d314bdba860d65a0495cfe Mon Sep 17 00:00:00 2001 From: Francois Botha Date: Wed, 18 Nov 2020 14:42:41 +0200 Subject: [PATCH 05/11] More functions to support both clean and dirty prices as input parameter --- ql/pricingengines/bond/bondfunctions.cpp | 48 +++++++++++++---- ql/pricingengines/bond/bondfunctions.hpp | 16 ++++++ test-suite/bonds.cpp | 69 ++++++++++++++++++++---- 3 files changed, 114 insertions(+), 19 deletions(-) diff --git a/ql/pricingengines/bond/bondfunctions.cpp b/ql/pricingengines/bond/bondfunctions.cpp index 10aa5aa7c38..96f77aca3fe 100644 --- a/ql/pricingengines/bond/bondfunctions.cpp +++ b/ql/pricingengines/bond/bondfunctions.cpp @@ -272,6 +272,12 @@ namespace QuantLib { const YieldTermStructure& discountCurve, Date settlement, Real cleanPrice) { + return atmRate(bond, discountCurve, settlement, Bond::Price(cleanPrice, Bond::Price::Clean)); + } + Rate BondFunctions::atmRate(const Bond& bond, + const YieldTermStructure& discountCurve, + Date settlement, + const Bond::Price price) { if (settlement == Date()) settlement = bond.settlementDate(); @@ -279,15 +285,22 @@ namespace QuantLib { "non tradable at " << settlement << " (maturity being " << bond.maturityDate() << ")"); - Real dirtyPrice = cleanPrice==Null() ? Null() : - cleanPrice + bond.accruedAmount(settlement); - Real currentNotional = bond.notional(settlement); - Real npv = dirtyPrice==Null() ? Null() : - dirtyPrice/100.0 * currentNotional; + if (price == Null()) + return CashFlows::atmRate(bond.cashflows(), discountCurve, + false, settlement, settlement, + Null()); + else { + Real dirtyPrice = + price.amount() + + (price.type() == Bond::Price::Clean ? bond.accruedAmount(settlement) : 0); - return CashFlows::atmRate(bond.cashflows(), discountCurve, - false, settlement, settlement, - npv); + Real currentNotional = bond.notional(settlement); + Real npv = dirtyPrice / 100.0 * currentNotional; + + return CashFlows::atmRate(bond.cashflows(), discountCurve, + false, settlement, settlement, + npv); + } } Real BondFunctions::cleanPrice(const Bond& bond, @@ -516,6 +529,19 @@ namespace QuantLib { Real accuracy, Size maxIterations, Rate guess) { + return zSpread(bond, Bond::Price(cleanPrice, Bond::Price::Clean), d, dayCounter, + compounding, frequency, settlement, accuracy, maxIterations, guess); + } + Spread BondFunctions::zSpread(const Bond& bond, + Bond::Price price, + const ext::shared_ptr& d, + const DayCounter& dayCounter, + Compounding compounding, + Frequency frequency, + Date settlement, + Real accuracy, + Size maxIterations, + Rate guess) { if (settlement == Date()) settlement = bond.settlementDate(); @@ -523,7 +549,10 @@ namespace QuantLib { "non tradable at " << settlement << " (maturity being " << bond.maturityDate() << ")"); - Real dirtyPrice = cleanPrice + bond.accruedAmount(settlement); + Real dirtyPrice = + price.amount() + + (price.type() == Bond::Price::Clean ? bond.accruedAmount(settlement) : 0); + dirtyPrice /= 100.0 / bond.notional(settlement); return CashFlows::zSpread(bond.cashflows(), @@ -533,5 +562,4 @@ namespace QuantLib { false, settlement, settlement, accuracy, maxIterations, guess); } - } diff --git a/ql/pricingengines/bond/bondfunctions.hpp b/ql/pricingengines/bond/bondfunctions.hpp index 07e491db698..a2098ccf268 100644 --- a/ql/pricingengines/bond/bondfunctions.hpp +++ b/ql/pricingengines/bond/bondfunctions.hpp @@ -111,10 +111,15 @@ namespace QuantLib { static Real bps(const Bond& bond, const YieldTermStructure& discountCurve, Date settlementDate = Date()); + [[deprecated("Use the version with the Bond::Price argument")]] static Rate atmRate(const Bond& bond, const YieldTermStructure& discountCurve, Date settlementDate = Date(), Real cleanPrice = Null()); + static Rate atmRate(const Bond& bond, + const YieldTermStructure& discountCurve, + Date settlementDate = Date(), + Bond::Price price = Null()); //@} //! \name Yield (a.k.a. Internal Rate of Return, i.e. IRR) functions @@ -259,6 +264,7 @@ namespace QuantLib { Compounding compounding, Frequency frequency, Date settlementDate = Date()); + [[deprecated("Use the version with the Bond::Price argument")]] static Spread zSpread(const Bond& bond, Real cleanPrice, const ext::shared_ptr&, @@ -269,6 +275,16 @@ namespace QuantLib { Real accuracy = 1.0e-10, Size maxIterations = 100, Rate guess = 0.0); + static Spread zSpread(const Bond& bond, + Bond::Price price, + const ext::shared_ptr&, + const DayCounter& dayCounter, + Compounding compounding, + Frequency frequency, + Date settlementDate = Date(), + Real accuracy = 1.0e-10, + Size maxIterations = 100, + Rate guess = 0.0); //@} }; diff --git a/test-suite/bonds.cpp b/test-suite/bonds.cpp index 224eb925822..9245a80ece0 100644 --- a/test-suite/bonds.cpp +++ b/test-suite/bonds.cpp @@ -233,7 +233,7 @@ BOOST_AUTO_TEST_CASE(testAtmRate) { paymentConvention, redemption, issue); bond.setPricingEngine(bondEngine); - Real price = bond.cleanPrice(); + Bond::Price price(bond.cleanPrice(), Bond::Price::Clean); Rate calculated = BondFunctions::atmRate(bond, **disc, bond.settlementDate(), price); @@ -245,8 +245,23 @@ BOOST_AUTO_TEST_CASE(testAtmRate) { "\n maturity: " << maturity << "\n coupon: " << io::rate(coupon) << "\n frequency: " << frequency << - "\n clean price: " << price << - "\n dirty price: " << price + bond.accruedAmount() << + "\n clean price: " << price.amount() << + "\n atm rate: " << io::rate(calculated)); + } + + price = Bond::Price(bond.dirtyPrice(), Bond::Price::Dirty); + calculated = + BondFunctions::atmRate(bond, **disc, bond.settlementDate(), price); + + if (std::fabs(coupon - calculated) > tolerance) { + BOOST_ERROR("\natm rate recalculation failed:" + "\n today: " << vars.today << + "\n settlement date: " << bond.settlementDate() << + "\n issue: " << issue << + "\n maturity: " << maturity << + "\n coupon: " << io::rate(coupon) << + "\n frequency: " << frequency << + "\n dirty price: " << price.amount() << "\n atm rate: " << io::rate(calculated)); } } @@ -300,8 +315,10 @@ BOOST_AUTO_TEST_CASE(testZspread) { for (Real spread : spreads) { - Real price = BondFunctions::cleanPrice(bond, *discountCurve, spread, - bondDayCount, n, frequency); + Bond::Price price(BondFunctions::cleanPrice(bond, *discountCurve, + spread, bondDayCount, n, + frequency), + Bond::Price::Clean); Spread calculated = BondFunctions::zSpread( bond, price, *discountCurve, bondDayCount, n, frequency, Date(), tolerance, maxEvaluations); @@ -310,7 +327,7 @@ BOOST_AUTO_TEST_CASE(testZspread) { // the difference might not matter Real price2 = BondFunctions::cleanPrice( bond, *discountCurve, calculated, bondDayCount, n, frequency); - if (std::fabs(price - price2) / price > tolerance) { + if (std::fabs(price.amount() - price2) / price.amount() > tolerance) { BOOST_ERROR("\nZ-spread recalculation failed:" "\n issue: " << issue << "\n maturity: " << maturity << @@ -319,9 +336,9 @@ BOOST_AUTO_TEST_CASE(testZspread) { "\n Z-spread: " << io::rate(spread) << (n == Compounded ? " compounded" : " continuous") << std::setprecision(7) << - "\n price: " << price << + "\n clean price: " << price.amount() << "\n Z-spread': " << io::rate(calculated) << - "\n price': " << price2); + "\n clean price': " << price2); } } } @@ -377,6 +394,7 @@ BOOST_AUTO_TEST_CASE(testTheoretical) { rate->setValue(m); + // Test yield vs clean price Real price = BondFunctions::cleanPrice(bond, m, bondDayCount, Continuous, frequency); Real calculatedPrice = bond.cleanPrice(); @@ -405,9 +423,42 @@ BOOST_AUTO_TEST_CASE(testTheoretical) { "\n frequency: " << frequency << "\n yield: " << io::rate(m) << std::setprecision(7) << - "\n price: " << price << + "\n clean price: " << price << "\n yield': " << io::rate(calculatedYield)); } + + // Test yield vs dirty price + price = + BondFunctions::dirtyPrice(bond, m, bondDayCount, Continuous, frequency); + calculatedPrice = bond.dirtyPrice(); + + if (std::fabs(price - calculatedPrice) > tolerance) { + BOOST_ERROR("price calculation failed:" << + "\n issue: " << issue << + "\n maturity: " << maturity << + "\n coupon: " << io::rate(coupon) << + "\n frequency: " << frequency << + "\n yield: " << io::rate(m) << + std::setprecision(7) << + "\n expected: " << price << + "\n calculated': " << calculatedPrice << + "\n error': " << price - calculatedPrice); + } + + calculatedYield = BondFunctions::yield( + bond, Bond::Price(calculatedPrice, Bond::Price::Dirty), bondDayCount, + Continuous, frequency, bond.settlementDate(), tolerance, maxEvaluations, 0.05); + if (std::fabs(m - calculatedYield) > tolerance) { + BOOST_ERROR("yield calculation failed:" << + "\n issue: " << issue << + "\n maturity: " << maturity << + "\n coupon: " << io::rate(coupon) << + "\n frequency: " << frequency << + "\n yield: " << io::rate(m) << + std::setprecision(7) << + "\n dirty price: " << price << + "\n yield': " << io::rate(calculatedYield)); + } } } } From 95ac5fa2783a10cf626d5fa3dc6d1ec80c8c87d8 Mon Sep 17 00:00:00 2001 From: Francois Botha Date: Tue, 27 Feb 2024 11:02:14 +0200 Subject: [PATCH 06/11] Add BondFunctions::dirtyPrice() functions --- ql/pricingengines/bond/bondfunctions.cpp | 26 ++++++++++++++++++-- ql/pricingengines/bond/bondfunctions.hpp | 10 ++++++++ test-suite/bonds.cpp | 31 ++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/ql/pricingengines/bond/bondfunctions.cpp b/ql/pricingengines/bond/bondfunctions.cpp index 96f77aca3fe..c7a6c1385ac 100644 --- a/ql/pricingengines/bond/bondfunctions.cpp +++ b/ql/pricingengines/bond/bondfunctions.cpp @@ -242,6 +242,15 @@ namespace QuantLib { if (settlement == Date()) settlement = bond.settlementDate(); + return dirtyPrice(bond, discountCurve, settlement) - bond.accruedAmount(settlement); + } + + Real BondFunctions::dirtyPrice(const Bond& bond, + const YieldTermStructure& discountCurve, + Date settlement) { + if (settlement == Date()) + settlement = bond.settlementDate(); + QL_REQUIRE(BondFunctions::isTradable(bond, settlement), "non tradable at " << settlement << " settlement date (maturity being " << @@ -250,7 +259,7 @@ namespace QuantLib { Real dirtyPrice = CashFlows::npv(bond.cashflows(), discountCurve, false, settlement) * 100.0 / bond.notional(settlement); - return dirtyPrice - bond.accruedAmount(settlement); + return dirtyPrice; } Real BondFunctions::bps(const Bond& bond, @@ -508,6 +517,19 @@ namespace QuantLib { if (settlement == Date()) settlement = bond.settlementDate(); + return dirtyPrice(bond, d, zSpread, dc, comp, freq, settlement) - bond.accruedAmount(settlement); + } + + Real BondFunctions::dirtyPrice(const Bond& bond, + const ext::shared_ptr& d, + Spread zSpread, + const DayCounter& dc, + Compounding comp, + Frequency freq, + Date settlement) { + if (settlement == Date()) + settlement = bond.settlementDate(); + QL_REQUIRE(BondFunctions::isTradable(bond, settlement), "non tradable at " << settlement << " (maturity being " << bond.maturityDate() << ")"); @@ -516,7 +538,7 @@ namespace QuantLib { zSpread, dc, comp, freq, false, settlement) * 100.0 / bond.notional(settlement); - return dirtyPrice - bond.accruedAmount(settlement); + return dirtyPrice; } Spread BondFunctions::zSpread(const Bond& bond, diff --git a/ql/pricingengines/bond/bondfunctions.hpp b/ql/pricingengines/bond/bondfunctions.hpp index a2098ccf268..c34682a547f 100644 --- a/ql/pricingengines/bond/bondfunctions.hpp +++ b/ql/pricingengines/bond/bondfunctions.hpp @@ -108,6 +108,9 @@ namespace QuantLib { static Real cleanPrice(const Bond& bond, const YieldTermStructure& discountCurve, Date settlementDate = Date()); + static Real dirtyPrice(const Bond& bond, + const YieldTermStructure& discountCurve, + Date settlementDate = Date()); static Real bps(const Bond& bond, const YieldTermStructure& discountCurve, Date settlementDate = Date()); @@ -264,6 +267,13 @@ namespace QuantLib { Compounding compounding, Frequency frequency, Date settlementDate = Date()); + static Real dirtyPrice(const Bond& bond, + const ext::shared_ptr& discount, + Spread zSpread, + const DayCounter& dayCounter, + Compounding compounding, + Frequency frequency, + Date settlementDate = Date()); [[deprecated("Use the version with the Bond::Price argument")]] static Spread zSpread(const Bond& bond, Real cleanPrice, diff --git a/test-suite/bonds.cpp b/test-suite/bonds.cpp index 9245a80ece0..440b16ed92f 100644 --- a/test-suite/bonds.cpp +++ b/test-suite/bonds.cpp @@ -315,6 +315,7 @@ BOOST_AUTO_TEST_CASE(testZspread) { for (Real spread : spreads) { + // Clean price Bond::Price price(BondFunctions::cleanPrice(bond, *discountCurve, spread, bondDayCount, n, frequency), @@ -341,6 +342,36 @@ BOOST_AUTO_TEST_CASE(testZspread) { "\n clean price': " << price2); } } + + // Dirty price + price = + Bond::Price(BondFunctions::dirtyPrice(bond, *discountCurve, spread, + bondDayCount, n, frequency), + Bond::Price::Dirty); + + calculated = BondFunctions::zSpread( + bond, price, *discountCurve, bondDayCount, n, frequency, Date(), + tolerance, maxEvaluations); + + if (std::fabs(spread - calculated) > tolerance) { + // the difference might not matter + Real price2 = BondFunctions::dirtyPrice( + bond, *discountCurve, calculated, bondDayCount, n, frequency); + if (std::fabs(price.amount() - price2) / price.amount() > tolerance) { + BOOST_ERROR("\nZ-spread recalculation failed:" + "\n issue: " << issue << + "\n maturity: " << maturity << + "\n coupon: " << io::rate(coupon) << + "\n frequency: " << frequency << + "\n Z-spread: " << io::rate(spread) << + (compounding[n] == Compounded ? + " compounded" : " continuous") << + std::setprecision(7) << + "\n dirty price: " << price.amount() << + "\n Z-spread': " << io::rate(calculated) << + "\n dirty price': " << price2); + } + } } } } From cf1a1c5bf5fbccc88b8407c738fbc624b8f0b0fe Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Wed, 6 Mar 2024 16:51:28 +0100 Subject: [PATCH 07/11] Replace comparison to null with isValid method --- ql/instruments/bond.cpp | 8 -------- ql/instruments/bond.hpp | 9 +-------- ql/pricingengines/bond/bondfunctions.cpp | 16 +++++++--------- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/ql/instruments/bond.cpp b/ql/instruments/bond.cpp index 1b22ed3c508..829a2ceefd6 100644 --- a/ql/instruments/bond.cpp +++ b/ql/instruments/bond.cpp @@ -414,12 +414,4 @@ namespace QuantLib { QL_REQUIRE(cf, "null cash flow provided"); } - bool operator==(const Bond::Price& p1, const Bond::Price& p2) { - return (p1.amount() == p2.amount()) && ((p1.type() == p2.type())); - } - - bool operator!=(const Bond::Price& p1, const Bond::Price& p2) { - return (p1.amount() != p2.amount()) || ((p1.type() != p2.type())); - } - } diff --git a/ql/instruments/bond.hpp b/ql/instruments/bond.hpp index c1510ff6055..944b1d2786b 100644 --- a/ql/instruments/bond.hpp +++ b/ql/instruments/bond.hpp @@ -70,6 +70,7 @@ namespace QuantLib { return amount_; } Type type() const { return type_; } + bool isValid() const { return amount_ != Null(); } private: Real amount_; Type type_; @@ -351,14 +352,6 @@ namespace QuantLib { return issueDate_; } -/*! Returns true iff the two bond prices belong to the same - derived class. - \relates Bond::Price -*/ - bool operator==(const Bond::Price&, const Bond::Price&); - - /*! \relates Bond::Price */ - bool operator!=(const Bond::Price&, const Bond::Price&); } #endif diff --git a/ql/pricingengines/bond/bondfunctions.cpp b/ql/pricingengines/bond/bondfunctions.cpp index c7a6c1385ac..e8e289d8758 100644 --- a/ql/pricingengines/bond/bondfunctions.cpp +++ b/ql/pricingengines/bond/bondfunctions.cpp @@ -283,6 +283,7 @@ namespace QuantLib { Real cleanPrice) { return atmRate(bond, discountCurve, settlement, Bond::Price(cleanPrice, Bond::Price::Clean)); } + Rate BondFunctions::atmRate(const Bond& bond, const YieldTermStructure& discountCurve, Date settlement, @@ -294,22 +295,19 @@ namespace QuantLib { "non tradable at " << settlement << " (maturity being " << bond.maturityDate() << ")"); - if (price == Null()) - return CashFlows::atmRate(bond.cashflows(), discountCurve, - false, settlement, settlement, - Null()); - else { + Real npv = Null(); + if (price.isValid()) { Real dirtyPrice = price.amount() + (price.type() == Bond::Price::Clean ? bond.accruedAmount(settlement) : 0); Real currentNotional = bond.notional(settlement); - Real npv = dirtyPrice / 100.0 * currentNotional; + npv = dirtyPrice / 100.0 * currentNotional; - return CashFlows::atmRate(bond.cashflows(), discountCurve, - false, settlement, settlement, - npv); } + return CashFlows::atmRate(bond.cashflows(), discountCurve, + false, settlement, settlement, + npv); } Real BondFunctions::cleanPrice(const Bond& bond, From 0c9b9a801b8f54292665dc1d8e1a90d22684a360 Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Wed, 6 Mar 2024 16:53:58 +0100 Subject: [PATCH 08/11] Remove implicit conversion from Real --- ql/instruments/bond.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/ql/instruments/bond.hpp b/ql/instruments/bond.hpp index 944b1d2786b..730bb985d86 100644 --- a/ql/instruments/bond.hpp +++ b/ql/instruments/bond.hpp @@ -63,7 +63,6 @@ namespace QuantLib { public: enum Type { Dirty, Clean }; Price() : amount_(Null()), type_(Bond::Price::Clean) {} - Price(Real amount) : amount_(amount), type_(Bond::Price::Clean) {} Price(Real amount, Type type) : amount_(amount), type_(type) {} Real amount() const { QL_REQUIRE(amount_ != Null(), "no amount given"); From d944c52183c7338787ae66088acbaec1c402b6a6 Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Wed, 6 Mar 2024 17:15:33 +0100 Subject: [PATCH 09/11] Use aggregate initialization for brevity --- Examples/Bonds/Bonds.cpp | 8 +-- Examples/FittedBondCurve/FittedBondCurve.cpp | 2 +- ql/instruments/bond.cpp | 2 +- ql/instruments/bonds/btp.cpp | 8 +-- ql/pricingengines/bond/bondfunctions.cpp | 8 ++- test-suite/bonds.cpp | 61 +++++++++++--------- test-suite/fittedbonddiscountcurve.cpp | 4 +- 7 files changed, 48 insertions(+), 45 deletions(-) diff --git a/Examples/Bonds/Bonds.cpp b/Examples/Bonds/Bonds.cpp index 173c67da118..23a3ceda745 100644 --- a/Examples/Bonds/Bonds.cpp +++ b/Examples/Bonds/Bonds.cpp @@ -541,14 +541,10 @@ int main(int, char* []) { << floatingRateBond.cleanPrice(floatingRateBond.yield(Actual360(),Compounded,Annual),Actual360(),Compounded,Annual,settlementDate) << std::endl; std::cout << "Clean Price to Yield: " - << io::rate(floatingRateBond.yield( - Bond::Price(floatingRateBond.cleanPrice(), Bond::Price::Clean), - Actual360(), Compounded, Annual, settlementDate)) + << io::rate(floatingRateBond.yield({floatingRateBond.cleanPrice(), Bond::Price::Clean}, + Actual360(), Compounded, Annual, settlementDate)) << std::endl; - /* "Yield to Price" - "Price to Yield" */ - return 0; } catch (std::exception& e) { diff --git a/Examples/FittedBondCurve/FittedBondCurve.cpp b/Examples/FittedBondCurve/FittedBondCurve.cpp index a04b58d493a..4643728ac7c 100644 --- a/Examples/FittedBondCurve/FittedBondCurve.cpp +++ b/Examples/FittedBondCurve/FittedBondCurve.cpp @@ -592,7 +592,7 @@ int main(int, char* []) { Real P = instrumentsA[k]->quote()->value(); const Bond& b = *instrumentsA[k]->bond(); - Rate ytm = BondFunctions::yield(b, Bond::Price(P, Bond::Price::Clean), + Rate ytm = BondFunctions::yield(b, {P, Bond::Price::Clean}, dc, Compounded, frequency, today); Time dur = BondFunctions::duration(b, ytm, diff --git a/ql/instruments/bond.cpp b/ql/instruments/bond.cpp index 829a2ceefd6..a565f3775b2 100644 --- a/ql/instruments/bond.cpp +++ b/ql/instruments/bond.cpp @@ -244,7 +244,7 @@ namespace QuantLib { Size maxEvaluations, Real guess, Bond::Price::Type priceType) const { - return yield(Bond::Price(price, priceType), dc, comp, freq, settlement, accuracy, + return yield({price, priceType}, dc, comp, freq, settlement, accuracy, maxEvaluations, guess); } Rate Bond::yield(Bond::Price price, diff --git a/ql/instruments/bonds/btp.cpp b/ql/instruments/bonds/btp.cpp index 8681be91220..1f19b45a1fe 100644 --- a/ql/instruments/bonds/btp.cpp +++ b/ql/instruments/bonds/btp.cpp @@ -83,7 +83,7 @@ namespace QuantLib { Date settlementDate, Real accuracy, Size maxEvaluations) const { - return Bond::yield(Bond::Price(cleanPrice, Bond::Price::Clean), + return Bond::yield({cleanPrice, Bond::Price::Clean}, ActualActual(ActualActual::ISMA), Compounded, Annual, settlementDate, accuracy, maxEvaluations); } @@ -160,7 +160,7 @@ namespace QuantLib { Date bondSettlementDate = btps[0]->settlementDate(); for (Size i=0; isize(); ++i) { yields_[i] = BondFunctions::yield( - *btps[i], Bond::Price(quotes[i]->value(), Bond::Price::Clean), + *btps[i], {quotes[i]->value(), Bond::Price::Clean}, ActualActual(ActualActual::ISMA), Compounded, Annual, bondSettlementDate, // accuracy, maxIterations, guess 1.0e-10, 100, yields_[i]); @@ -185,7 +185,7 @@ namespace QuantLib { Following, // paymentConvention 100.0); // redemption swapBondYields_[0] = BondFunctions::yield(swapBond, - Bond::Price(100.0, Bond::Price::Clean), // floating leg NPV including end payment + {100.0, Bond::Price::Clean}, // floating leg NPV including end payment ActualActual(ActualActual::ISMA), Compounded, Annual, bondSettlementDate, // accuracy, maxIterations, guess @@ -204,7 +204,7 @@ namespace QuantLib { Following, // paymentConvention 100.0); // redemption swapBondYields_[i] = BondFunctions::yield(swapBond, - Bond::Price(100.0, Bond::Price::Clean), // floating leg NPV including end payment + {100.0, Bond::Price::Clean}, // floating leg NPV including end payment ActualActual(ActualActual::ISMA), Compounded, Annual, bondSettlementDate, // accuracy, maxIterations, guess diff --git a/ql/pricingengines/bond/bondfunctions.cpp b/ql/pricingengines/bond/bondfunctions.cpp index e8e289d8758..3a11bc1597f 100644 --- a/ql/pricingengines/bond/bondfunctions.cpp +++ b/ql/pricingengines/bond/bondfunctions.cpp @@ -281,7 +281,7 @@ namespace QuantLib { const YieldTermStructure& discountCurve, Date settlement, Real cleanPrice) { - return atmRate(bond, discountCurve, settlement, Bond::Price(cleanPrice, Bond::Price::Clean)); + return atmRate(bond, discountCurve, settlement, {cleanPrice, Bond::Price::Clean}); } Rate BondFunctions::atmRate(const Bond& bond, @@ -387,9 +387,10 @@ namespace QuantLib { Size maxIterations, Rate guess, Bond::Price::Type priceType) { - return yield(bond, Bond::Price(price, priceType), dayCounter, compounding, frequency, + return yield(bond, {price, priceType}, dayCounter, compounding, frequency, settlement, accuracy, maxIterations, guess); } + Rate BondFunctions::yield(const Bond& bond, Bond::Price price, const DayCounter& dayCounter, @@ -549,9 +550,10 @@ namespace QuantLib { Real accuracy, Size maxIterations, Rate guess) { - return zSpread(bond, Bond::Price(cleanPrice, Bond::Price::Clean), d, dayCounter, + return zSpread(bond, {cleanPrice, Bond::Price::Clean}, d, dayCounter, compounding, frequency, settlement, accuracy, maxIterations, guess); } + Spread BondFunctions::zSpread(const Bond& bond, Bond::Price price, const ext::shared_ptr& d, diff --git a/test-suite/bonds.cpp b/test-suite/bonds.cpp index 440b16ed92f..92c6d0f18c1 100644 --- a/test-suite/bonds.cpp +++ b/test-suite/bonds.cpp @@ -134,9 +134,10 @@ BOOST_AUTO_TEST_CASE(testYield) { for (Real m : yields) { - Bond::Price price( + Bond::Price price = { BondFunctions::cleanPrice(bond, m, bondDayCount, n, frequency), - Bond::Price::Clean); + Bond::Price::Clean + }; Rate calculated = BondFunctions::yield( bond, price, bondDayCount, n, frequency, Date(), tolerance, @@ -162,9 +163,10 @@ BOOST_AUTO_TEST_CASE(testYield) { } } - price = Bond::Price( + price = { BondFunctions::dirtyPrice(bond, m, bondDayCount, n, frequency), - Bond::Price::Dirty); + Bond::Price::Dirty + }; calculated = BondFunctions::yield( bond, price, bondDayCount, n, frequency, Date(), tolerance, @@ -233,7 +235,7 @@ BOOST_AUTO_TEST_CASE(testAtmRate) { paymentConvention, redemption, issue); bond.setPricingEngine(bondEngine); - Bond::Price price(bond.cleanPrice(), Bond::Price::Clean); + Bond::Price price = {bond.cleanPrice(), Bond::Price::Clean}; Rate calculated = BondFunctions::atmRate(bond, **disc, bond.settlementDate(), price); @@ -249,7 +251,7 @@ BOOST_AUTO_TEST_CASE(testAtmRate) { "\n atm rate: " << io::rate(calculated)); } - price = Bond::Price(bond.dirtyPrice(), Bond::Price::Dirty); + price = {bond.dirtyPrice(), Bond::Price::Dirty}; calculated = BondFunctions::atmRate(bond, **disc, bond.settlementDate(), price); @@ -316,10 +318,12 @@ BOOST_AUTO_TEST_CASE(testZspread) { for (Real spread : spreads) { // Clean price - Bond::Price price(BondFunctions::cleanPrice(bond, *discountCurve, - spread, bondDayCount, n, - frequency), - Bond::Price::Clean); + Bond::Price price = { + BondFunctions::cleanPrice(bond, *discountCurve, + spread, bondDayCount, n, + frequency), + Bond::Price::Clean + }; Spread calculated = BondFunctions::zSpread( bond, price, *discountCurve, bondDayCount, n, frequency, Date(), tolerance, maxEvaluations); @@ -344,10 +348,11 @@ BOOST_AUTO_TEST_CASE(testZspread) { } // Dirty price - price = - Bond::Price(BondFunctions::dirtyPrice(bond, *discountCurve, spread, - bondDayCount, n, frequency), - Bond::Price::Dirty); + price = { + BondFunctions::dirtyPrice(bond, *discountCurve, spread, + bondDayCount, n, frequency), + Bond::Price::Dirty + }; calculated = BondFunctions::zSpread( bond, price, *discountCurve, bondDayCount, n, frequency, Date(), @@ -444,7 +449,7 @@ BOOST_AUTO_TEST_CASE(testTheoretical) { } Rate calculatedYield = BondFunctions::yield( - bond, Bond::Price(calculatedPrice, Bond::Price::Clean), bondDayCount, + bond, {calculatedPrice, Bond::Price::Clean}, bondDayCount, Continuous, frequency, bond.settlementDate(), tolerance, maxEvaluations); if (std::fabs(m - calculatedYield) > tolerance) { BOOST_ERROR("yield calculation failed:" << @@ -477,7 +482,7 @@ BOOST_AUTO_TEST_CASE(testTheoretical) { } calculatedYield = BondFunctions::yield( - bond, Bond::Price(calculatedPrice, Bond::Price::Dirty), bondDayCount, + bond, {calculatedPrice, Bond::Price::Dirty}, bondDayCount, Continuous, frequency, bond.settlementDate(), tolerance, maxEvaluations, 0.05); if (std::fabs(m - calculatedYield) > tolerance) { BOOST_ERROR("yield calculation failed:" << @@ -605,28 +610,28 @@ BOOST_AUTO_TEST_CASE(testCached) { tolerance, "failed to reproduce cached clean price with no schdule for bond 1:" ); - checkValue(BondFunctions::yield(bond1, Bond::Price(marketPrice1, Bond::Price::Clean), + checkValue(BondFunctions::yield(bond1, {marketPrice1, Bond::Price::Clean}, bondDayCount1, Compounded, freq), cachedYield1a, tolerance, "failed to reproduce cached compounded yield with schedule for bond 1:"); - checkValue(BondFunctions::yield(bond1NoSchedule, Bond::Price(marketPrice1, Bond::Price::Clean), + checkValue(BondFunctions::yield(bond1NoSchedule, {marketPrice1, Bond::Price::Clean}, bondDayCount1NoSchedule, Compounded, freq), cachedYield1a, tolerance, "failed to reproduce cached compounded yield with no schedule for bond 1:"); - checkValue(BondFunctions::yield(bond1, Bond::Price(marketPrice1, Bond::Price::Clean), + checkValue(BondFunctions::yield(bond1, {marketPrice1, Bond::Price::Clean}, bondDayCount1, Continuous, freq), cachedYield1b, tolerance, "failed to reproduce cached continuous yield with schedule for bond 1:"); - checkValue(BondFunctions::yield(bond1NoSchedule, Bond::Price(marketPrice1, Bond::Price::Clean), + checkValue(BondFunctions::yield(bond1NoSchedule, {marketPrice1, Bond::Price::Clean}, bondDayCount1NoSchedule, Continuous, freq), cachedYield1b, tolerance, "failed to reproduce cached continuous yield with no schedule for bond 1:"); - checkValue(BondFunctions::yield(bond1, Bond::Price(bond1.cleanPrice(), Bond::Price::Clean), + checkValue(BondFunctions::yield(bond1, {bond1.cleanPrice(), Bond::Price::Clean}, bondDayCount1, Continuous, freq, bond1.settlementDate()), cachedYield1c, tolerance, "failed to reproduce cached continuous yield with schedule for bond 1:"); checkValue(BondFunctions::yield( - bond1NoSchedule, Bond::Price(bond1NoSchedule.cleanPrice(), Bond::Price::Clean), + bond1NoSchedule, {bond1NoSchedule.cleanPrice(), Bond::Price::Clean}, bondDayCount1NoSchedule, Continuous, freq, bond1.settlementDate()), cachedYield1c, tolerance, "failed to reproduce cached continuous yield with no schedule for bond 1:"); @@ -657,28 +662,28 @@ BOOST_AUTO_TEST_CASE(testCached) { tolerance, "failed to reproduce cached clean price with no schedule for bond 2:" ); - checkValue(BondFunctions::yield(bond2, Bond::Price(marketPrice2, Bond::Price::Clean), + checkValue(BondFunctions::yield(bond2, {marketPrice2, Bond::Price::Clean}, bondDayCount2, Compounded, freq), cachedYield2a, tolerance, "failed to reproduce cached compounded yield with schedule for bond 2:"); - checkValue(BondFunctions::yield(bond2NoSchedule, Bond::Price(marketPrice2, Bond::Price::Clean), + checkValue(BondFunctions::yield(bond2NoSchedule, {marketPrice2, Bond::Price::Clean}, bondDayCount2NoSchedule, Compounded, freq), cachedYield2a, tolerance, "failed to reproduce cached compounded yield with no schedule for bond 2:"); - checkValue(BondFunctions::yield(bond2, Bond::Price(marketPrice2, Bond::Price::Clean), + checkValue(BondFunctions::yield(bond2, {marketPrice2, Bond::Price::Clean}, bondDayCount2, Continuous, freq), cachedYield2b, tolerance, "failed to reproduce chached continuous yield with schedule for bond 2:"); - checkValue(BondFunctions::yield(bond2NoSchedule, Bond::Price(marketPrice2, Bond::Price::Clean), + checkValue(BondFunctions::yield(bond2NoSchedule, {marketPrice2, Bond::Price::Clean}, bondDayCount2NoSchedule, Continuous, freq), cachedYield2b, tolerance, "failed to reproduce cached continuous yield with schedule for bond 2:"); - checkValue(BondFunctions::yield(bond2, Bond::Price(bond2.cleanPrice(), Bond::Price::Clean), + checkValue(BondFunctions::yield(bond2, {bond2.cleanPrice(), Bond::Price::Clean}, bondDayCount2, Continuous, freq, bond2.settlementDate()), cachedYield2c, tolerance, "failed to reproduce cached continuous yield for bond 2 with schedule:"); checkValue(BondFunctions::yield( - bond2NoSchedule, Bond::Price(bond2NoSchedule.cleanPrice(), Bond::Price::Clean), + bond2NoSchedule, {bond2NoSchedule.cleanPrice(), Bond::Price::Clean}, bondDayCount2NoSchedule, Continuous, freq, bond2NoSchedule.settlementDate()), cachedYield2c, tolerance, "failed to reproduce cached continuous yield for bond 2 with no schedule:"); diff --git a/test-suite/fittedbonddiscountcurve.cpp b/test-suite/fittedbonddiscountcurve.cpp index 11207b29352..0cd87cd43f1 100644 --- a/test-suite/fittedbonddiscountcurve.cpp +++ b/test-suite/fittedbonddiscountcurve.cpp @@ -153,9 +153,9 @@ BOOST_AUTO_TEST_CASE(testFlatExtrapolation) { for (auto& bond : bonds) { bond->setPricingEngine(engine1); - modelPrices1.push_back(Bond::Price(bond->cleanPrice(), Bond::Price::Clean)); + modelPrices1.emplace_back(bond->cleanPrice(), Bond::Price::Clean); bond->setPricingEngine(engine2); - modelPrices2.push_back(Bond::Price(bond->cleanPrice(), Bond::Price::Clean)); + modelPrices2.emplace_back(bond->cleanPrice(), Bond::Price::Clean); } BOOST_CHECK_EQUAL(curve1->fitResults().errorCode(), EndCriteria::MaxIterations); BOOST_CHECK_EQUAL(curve2->fitResults().errorCode(), EndCriteria::MaxIterations); From 072a71e36e5bbd86bfc2fa746d85b48d1897ab2e Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Wed, 6 Mar 2024 17:24:28 +0100 Subject: [PATCH 10/11] Document deprecations in Doxygen --- ql/instruments/bond.hpp | 8 +++++--- ql/pricingengines/bond/bondfunctions.hpp | 21 +++++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/ql/instruments/bond.hpp b/ql/instruments/bond.hpp index 730bb985d86..c4d7faec281 100644 --- a/ql/instruments/bond.hpp +++ b/ql/instruments/bond.hpp @@ -195,9 +195,10 @@ namespace QuantLib { /*! The default bond settlement date is used for calculation. */ Real settlementValue(Real cleanPrice) const; - //! yield given a price and settlement date - /*! The default bond settlement is used if no date is given. */ - [[deprecated("Use the version with the Bond::Price argument")]] + /*! \deprecated Use the overload taking a Bond::Price argument instead. + Deprecated in version 1.34. + */ + [[deprecated("Use the overload taking a Bond::Price argument instead")]] Rate yield(Real price, const DayCounter& dc, Compounding comp, @@ -207,6 +208,7 @@ namespace QuantLib { Size maxEvaluations = 100, Real guess = 0.05, Bond::Price::Type priceType = Bond::Price::Clean) const; + //! yield given a price and settlement date /*! The default bond settlement is used if no date is given. */ Rate yield(Bond::Price price, diff --git a/ql/pricingengines/bond/bondfunctions.hpp b/ql/pricingengines/bond/bondfunctions.hpp index c34682a547f..4871fce7724 100644 --- a/ql/pricingengines/bond/bondfunctions.hpp +++ b/ql/pricingengines/bond/bondfunctions.hpp @@ -114,7 +114,11 @@ namespace QuantLib { static Real bps(const Bond& bond, const YieldTermStructure& discountCurve, Date settlementDate = Date()); - [[deprecated("Use the version with the Bond::Price argument")]] + + /*! \deprecated Use the overload taking a Bond::Price argument instead. + Deprecated in version 1.34. + */ + [[deprecated("Use the overload taking a Bond::Price argument instead")]] static Rate atmRate(const Bond& bond, const YieldTermStructure& discountCurve, Date settlementDate = Date(), @@ -154,7 +158,10 @@ namespace QuantLib { Compounding compounding, Frequency frequency, Date settlementDate = Date()); - [[deprecated("Use the version with the Bond::Price argument")]] + /*! \deprecated Use the overload taking a Bond::Price argument instead. + Deprecated in version 1.34. + */ + [[deprecated("Use the overload taking a Bond::Price argument instead")]] static Rate yield(const Bond& bond, Real price, const DayCounter& dayCounter, @@ -174,8 +181,11 @@ namespace QuantLib { Real accuracy = 1.0e-10, Size maxIterations = 100, Rate guess = 0.05); + /*! \deprecated Use the overload taking a Bond::Price argument instead. + Deprecated in version 1.34. + */ template - [[deprecated("Use the version with the Bond::Price argument")]] + [[deprecated("Use the overload taking a Bond::Price argument instead")]] static Rate yield(const Solver& solver, const Bond& bond, Real price, @@ -274,7 +284,10 @@ namespace QuantLib { Compounding compounding, Frequency frequency, Date settlementDate = Date()); - [[deprecated("Use the version with the Bond::Price argument")]] + /*! \deprecated Use the overload taking a Bond::Price argument instead. + Deprecated in version 1.34. + */ + [[deprecated("Use the overload taking a Bond::Price argument instead")]] static Spread zSpread(const Bond& bond, Real cleanPrice, const ext::shared_ptr&, From f1ed88696825015763807e78ab4687bd010705d2 Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Wed, 6 Mar 2024 17:50:51 +0100 Subject: [PATCH 11/11] More concise default --- ql/pricingengines/bond/bondfunctions.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ql/pricingengines/bond/bondfunctions.hpp b/ql/pricingengines/bond/bondfunctions.hpp index 4871fce7724..ccf65ca4ec5 100644 --- a/ql/pricingengines/bond/bondfunctions.hpp +++ b/ql/pricingengines/bond/bondfunctions.hpp @@ -126,7 +126,7 @@ namespace QuantLib { static Rate atmRate(const Bond& bond, const YieldTermStructure& discountCurve, Date settlementDate = Date(), - Bond::Price price = Null()); + Bond::Price price = {}); //@} //! \name Yield (a.k.a. Internal Rate of Return, i.e. IRR) functions