diff --git a/Examples/Bonds/Bonds.cpp b/Examples/Bonds/Bonds.cpp index 099b27d9021..23a3ceda745 100644 --- a/Examples/Bonds/Bonds.cpp +++ b/Examples/Bonds/Bonds.cpp @@ -541,10 +541,9 @@ 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; - - /* "Yield to Price" - "Price to Yield" */ + << io::rate(floatingRateBond.yield({floatingRateBond.cleanPrice(), Bond::Price::Clean}, + Actual360(), Compounded, Annual, settlementDate)) + << std::endl; return 0; diff --git a/Examples/FittedBondCurve/FittedBondCurve.cpp b/Examples/FittedBondCurve/FittedBondCurve.cpp index c0d1f6c61ff..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, P, + Rate ytm = BondFunctions::yield(b, {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..a565f3775b2 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({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..c4d7faec281 100644 --- a/ql/instruments/bond.hpp +++ b/ql/instruments/bond.hpp @@ -62,13 +62,14 @@ namespace QuantLib { class Price { public: enum Type { Dirty, Clean }; - Price() : amount_(Null()) {} + Price() : amount_(Null()), type_(Bond::Price::Clean) {} Price(Real amount, Type type) : amount_(amount), type_(type) {} Real amount() const { QL_REQUIRE(amount_ != Null(), "no amount given"); return amount_; } Type type() const { return type_; } + bool isValid() const { return amount_ != Null(); } private: Real amount_; Type type_; @@ -194,8 +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 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, @@ -206,6 +209,17 @@ namespace QuantLib { 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. */ virtual Real accruedAmount(Date d = Date()) const; diff --git a/ql/instruments/bonds/btp.cpp b/ql/instruments/bonds/btp.cpp index 67ca4db67d1..1f19b45a1fe 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({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], {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 + {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 + {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..3a11bc1597f 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, @@ -272,6 +281,13 @@ namespace QuantLib { const YieldTermStructure& discountCurve, Date settlement, Real cleanPrice) { + return atmRate(bond, discountCurve, settlement, {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,12 +295,16 @@ 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; + Real npv = Null(); + if (price.isValid()) { + Real dirtyPrice = + price.amount() + + (price.type() == Bond::Price::Clean ? bond.accruedAmount(settlement) : 0); + + Real currentNotional = bond.notional(settlement); + npv = dirtyPrice / 100.0 * currentNotional; + } return CashFlows::atmRate(bond.cashflows(), discountCurve, false, settlement, settlement, npv); @@ -367,11 +387,24 @@ namespace QuantLib { Size maxIterations, Rate guess, Bond::Price::Type priceType) { + return yield(bond, {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, @@ -483,6 +516,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() << ")"); @@ -491,7 +537,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, @@ -504,6 +550,20 @@ namespace QuantLib { Real accuracy, Size maxIterations, Rate guess) { + 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, + const DayCounter& dayCounter, + Compounding compounding, + Frequency frequency, + Date settlement, + Real accuracy, + Size maxIterations, + Rate guess) { if (settlement == Date()) settlement = bond.settlementDate(); @@ -511,7 +571,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(), @@ -521,5 +584,4 @@ namespace QuantLib { false, settlement, settlement, accuracy, maxIterations, guess); } - } diff --git a/ql/pricingengines/bond/bondfunctions.hpp b/ql/pricingengines/bond/bondfunctions.hpp index a699433b159..ccf65ca4ec5 100644 --- a/ql/pricingengines/bond/bondfunctions.hpp +++ b/ql/pricingengines/bond/bondfunctions.hpp @@ -108,13 +108,25 @@ 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()); + + /*! \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(), Real cleanPrice = Null()); + static Rate atmRate(const Bond& bond, + const YieldTermStructure& discountCurve, + Date settlementDate = Date(), + Bond::Price price = {}); //@} //! \name Yield (a.k.a. Internal Rate of Return, i.e. IRR) functions @@ -146,6 +158,10 @@ namespace QuantLib { Compounding compounding, Frequency frequency, Date settlementDate = Date()); + /*! \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, @@ -156,7 +172,20 @@ 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); + /*! \deprecated Use the overload taking a Bond::Price argument instead. + Deprecated in version 1.34. + */ template + [[deprecated("Use the overload taking a Bond::Price argument instead")]] static Rate yield(const Solver& solver, const Bond& bond, Real price, @@ -167,6 +196,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 +216,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); } @@ -235,6 +277,17 @@ 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 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&, @@ -245,6 +298,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/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 f94196413c4..92c6d0f18c1 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); @@ -134,52 +134,60 @@ BOOST_AUTO_TEST_CASE(testYield) { for (Real m : yields) { - Real price = - BondFunctions::cleanPrice(bond, m, bondDayCount, n, frequencie); + Bond::Price price = { + BondFunctions::cleanPrice(bond, m, bondDayCount, n, frequency), + Bond::Price::Clean + }; Rate calculated = BondFunctions::yield( - bond, price, bondDayCount, n, frequencie, Date(), tolerance, - maxEvaluations, 0.05, Bond::Price::Clean); + bond, price, bondDayCount, n, frequency, Date(), tolerance, + maxEvaluations, 0.05); if (std::fabs(m - calculated) > tolerance) { // the difference might not matter Real price2 = BondFunctions::cleanPrice( - bond, calculated, bondDayCount, n, frequencie); - if (std::fabs(price - price2) / price > tolerance) { + bond, calculated, bondDayCount, n, frequency); + if (std::fabs(price.amount() - price2) / price.amount() > + 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: " << frequency << + "\n yield: " << io::rate(m) << + (n == Compounded ? " compounded" : " continuous") << + std::setprecision(7) << + "\n clean price: " << price.amount() << + "\n yield': " << io::rate(calculated) << + "\n clean price': " << price2); } } - price = BondFunctions::dirtyPrice(bond, m, bondDayCount, n, frequencie); + price = { + BondFunctions::dirtyPrice(bond, m, bondDayCount, n, frequency), + Bond::Price::Dirty + }; calculated = BondFunctions::yield( - bond, price, bondDayCount, n, frequencie, Date(), tolerance, - maxEvaluations, 0.05, Bond::Price::Dirty); + bond, price, bondDayCount, n, frequency, Date(), tolerance, + maxEvaluations, 0.05); if (std::fabs(m - calculated) > tolerance) { // the difference might not matter Real price2 = BondFunctions::dirtyPrice( - bond, calculated, bondDayCount, n, frequencie); - if (std::fabs(price - price2) / price > tolerance) { + bond, calculated, bondDayCount, n, frequency); + if (std::fabs(price.amount() - price2) / price.amount() > 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: " << frequency << + "\n yield: " << io::rate(m) << + (n == Compounded ? " compounded" : " continuous") << + std::setprecision(7) << + "\n dirty price: " << price.amount() << + "\n yield': " << io::rate(calculated) << + "\n dirty price': " << price2); } } } @@ -213,12 +221,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); @@ -227,20 +235,36 @@ 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); 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: " << frequency << + "\n clean price: " << price.amount() << + "\n atm rate: " << io::rate(calculated)); + } + + 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)); } } } @@ -276,14 +300,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); @@ -293,28 +317,64 @@ BOOST_AUTO_TEST_CASE(testZspread) { for (Real spread : spreads) { - Real price = BondFunctions::cleanPrice(bond, *discountCurve, spread, - bondDayCount, n, frequencie); + // Clean price + Bond::Price price = { + BondFunctions::cleanPrice(bond, *discountCurve, + spread, bondDayCount, n, + frequency), + Bond::Price::Clean + }; 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); - if (std::fabs(price - price2) / price > tolerance) { + 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) << + (n == Compounded ? " compounded" : " continuous") << + std::setprecision(7) << + "\n clean price: " << price.amount() << + "\n Z-spread': " << io::rate(calculated) << + "\n clean price': " << price2); + } + } + + // Dirty price + 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: " << 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: " << 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); } } } @@ -347,7 +407,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; @@ -356,7 +416,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, @@ -370,32 +430,70 @@ BOOST_AUTO_TEST_CASE(testTheoretical) { rate->setValue(m); + // Test yield vs clean price 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) { - 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: " << frequency << + "\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); + bond, {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 << + "\n maturity: " << maturity << + "\n coupon: " << io::rate(coupon) << + "\n frequency: " << frequency << + "\n yield: " << io::rate(m) << + std::setprecision(7) << + "\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, {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: " << 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: " << frequency << + "\n yield: " << io::rate(m) << + std::setprecision(7) << + "\n dirty price: " << price << + "\n yield': " << io::rate(calculatedYield)); } } } @@ -512,42 +610,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, {marketPrice1, Bond::Price::Clean}, + bondDayCount1, Compounded, freq), + cachedYield1a, tolerance, + "failed to reproduce cached compounded yield with schedule for bond 1:"); + 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, {marketPrice1, Bond::Price::Clean}, + bondDayCount1, Continuous, freq), + cachedYield1b, tolerance, + "failed to reproduce cached continuous yield with schedule for bond 1:"); + 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, {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, {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 @@ -575,42 +662,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, {marketPrice2, Bond::Price::Clean}, + bondDayCount2, Compounded, freq), + cachedYield2a, tolerance, + "failed to reproduce cached compounded yield with schedule for bond 2:"); + 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, {marketPrice2, Bond::Price::Clean}, + bondDayCount2, Continuous, freq), + cachedYield2b, tolerance, + "failed to reproduce chached continuous yield with schedule for bond 2:"); + 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, {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, {bond2NoSchedule.cleanPrice(), Bond::Price::Clean}, + bondDayCount2NoSchedule, Continuous, freq, bond2NoSchedule.settlementDate()), + cachedYield2c, tolerance, + "failed to reproduce cached continuous yield for bond 2 with no schedule:"); @@ -1665,7 +1741,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); @@ -1713,7 +1789,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..0cd87cd43f1 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.emplace_back(bond->cleanPrice(), Bond::Price::Clean); bond->setPricingEngine(engine2); - modelPrices2.push_back(bond->cleanPrice()); + modelPrices2.emplace_back(bond->cleanPrice(), Bond::Price::Clean); } BOOST_CHECK_EQUAL(curve1->fitResults().errorCode(), EndCriteria::MaxIterations); BOOST_CHECK_EQUAL(curve2->fitResults().errorCode(), EndCriteria::MaxIterations);