From 551a1c55d361c9d9851b8086740383cf13de74e5 Mon Sep 17 00:00:00 2001 From: Francois Botha Date: Wed, 18 Nov 2020 14:42:41 +0200 Subject: [PATCH] More functions to support both clean and dirty prices as input parameter --- ql/pricingengines/bond/bondfunctions.cpp | 63 ++++++++++++++----- ql/pricingengines/bond/bondfunctions.hpp | 20 +++++- test-suite/bonds.cpp | 80 +++++++++++++++++++++--- 3 files changed, 135 insertions(+), 28 deletions(-) diff --git a/ql/pricingengines/bond/bondfunctions.cpp b/ql/pricingengines/bond/bondfunctions.cpp index 3be13cb9da8..f6ce6bb1c5f 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, @@ -271,7 +280,8 @@ namespace QuantLib { Rate BondFunctions::atmRate(const Bond& bond, const YieldTermStructure& discountCurve, Date settlement, - Real cleanPrice) { + Real price, + const Bond::Price::Type priceType) { if (settlement == Date()) settlement = bond.settlementDate(); @@ -279,15 +289,23 @@ 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; - return CashFlows::atmRate(bond.cashflows(), discountCurve, - false, settlement, settlement, - npv); + if (priceType == Bond::Price::Clean) + dirtyPrice += bond.accruedAmount(settlement); + + 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, @@ -485,6 +503,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() << ")"); @@ -493,11 +524,11 @@ 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, - Real cleanPrice, + Real price, const ext::shared_ptr& d, const DayCounter& dayCounter, Compounding compounding, @@ -505,7 +536,8 @@ namespace QuantLib { Date settlement, Real accuracy, Size maxIterations, - Rate guess) { + Rate guess, + const Bond::Price::Type priceType) { if (settlement == Date()) settlement = bond.settlementDate(); @@ -513,7 +545,9 @@ namespace QuantLib { "non tradable at " << settlement << " (maturity being " << bond.maturityDate() << ")"); - Real dirtyPrice = cleanPrice + bond.accruedAmount(settlement); + Real dirtyPrice = price + + (priceType == Bond::Price::Clean ? bond.accruedAmount(settlement) : 0); + dirtyPrice /= 100.0 / bond.notional(settlement); return CashFlows::zSpread(bond.cashflows(), @@ -523,5 +557,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..93170c8809e 100644 --- a/ql/pricingengines/bond/bondfunctions.hpp +++ b/ql/pricingengines/bond/bondfunctions.hpp @@ -108,13 +108,17 @@ 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()); static Rate atmRate(const Bond& bond, const YieldTermStructure& discountCurve, Date settlementDate = Date(), - Real cleanPrice = Null()); + Real price = Null(), + Bond::Price::Type priceType = Bond::Price::Clean); //@} //! \name Yield (a.k.a. Internal Rate of Return, i.e. IRR) functions @@ -235,8 +239,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()); + static Spread zSpread(const Bond& bond, - Real cleanPrice, + Real price, const ext::shared_ptr&, const DayCounter& dayCounter, Compounding compounding, @@ -244,7 +257,8 @@ namespace QuantLib { Date settlementDate = Date(), Real accuracy = 1.0e-10, Size maxIterations = 100, - Rate guess = 0.0); + Rate guess = 0.0, + Bond::Price::Type priceType = Bond::Price::Clean); //@} }; diff --git a/test-suite/bonds.cpp b/test-suite/bonds.cpp index a2534fe4d45..6069f32d9b8 100644 --- a/test-suite/bonds.cpp +++ b/test-suite/bonds.cpp @@ -234,7 +234,7 @@ void BondTest::testAtmRate() { bond.setPricingEngine(bondEngine); Real price = bond.cleanPrice(); Rate calculated = - BondFunctions::atmRate(bond, **disc, bond.settlementDate(), price); + BondFunctions::atmRate(bond, **disc, bond.settlementDate(), price, Bond::Price::Clean); if (std::fabs(coupon - calculated) > tolerance) { BOOST_ERROR("\natm rate recalculation failed:" @@ -248,6 +248,23 @@ void BondTest::testAtmRate() { "\n dirty price: " << price + bond.accruedAmount() << "\n atm rate: " << io::rate(calculated)); } + + price = bond.dirtyPrice(); + calculated = + BondFunctions::atmRate(bond, **disc, bond.settlementDate(), price, Bond::Price::Dirty); + + 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 clean price: " << price - bond.accruedAmount() << + "\n dirty price: " << price << + "\n atm rate: " << io::rate(calculated)); + } } } } @@ -301,11 +318,12 @@ void BondTest::testZspread() { for (Real spread : spreads) { + // Clean price Real price = BondFunctions::cleanPrice(bond, *discountCurve, spread, bondDayCount, n, frequency); Spread calculated = BondFunctions::zSpread( bond, price, *discountCurve, bondDayCount, n, frequency, Date(), - tolerance, maxEvaluations); + tolerance, maxEvaluations, 0.0, Bond::Price::Clean); if (std::fabs(spread - calculated) > tolerance) { // the difference might not matter @@ -313,16 +331,58 @@ void BondTest::testZspread() { 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: " << frequency << - "\n Z-spread: " << io::rate(spread) << + "\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 price: " << price << - "\n Z-spread': " << io::rate(calculated) << - "\n price': " << price2); + "\n clean price: " << price << + "\n Z-spread': " << io::rate(calculated) << + "\n clean price': " << price2); + } + } + + // Dirty price + price = BondFunctions::dirtyPrice(bond, *discountCurve, + spread, + bondDayCount, + n, + frequency); + + calculated = BondFunctions::zSpread(bond, price, + *discountCurve, + bondDayCount, + n, + frequency, + Date(), + tolerance, + maxEvaluations, + 0.0, + Bond::Price::Dirty); + + 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 - price2) / price > 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 << + "\n Z-spread': " << io::rate(calculated) << + "\n dirty price': " << price2); } } }