Skip to content

Commit

Permalink
More functions to support both clean and dirty prices as input parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
igitur committed Oct 18, 2023
1 parent 69ca728 commit 231b351
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 37 deletions.
63 changes: 48 additions & 15 deletions ql/pricingengines/bond/bondfunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 " <<
Expand All @@ -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,
Expand All @@ -271,23 +280,32 @@ 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();

QL_REQUIRE(BondFunctions::isTradable(bond, settlement),
"non tradable at " << settlement <<
" (maturity being " << bond.maturityDate() << ")");

Real dirtyPrice = cleanPrice==Null<Real>() ? Null<Real>() :
cleanPrice + bond.accruedAmount(settlement);
Real currentNotional = bond.notional(settlement);
Real npv = dirtyPrice==Null<Real>() ? Null<Real>() :
dirtyPrice/100.0 * currentNotional;
if (price == Null<Real>())
return CashFlows::atmRate(bond.cashflows(), discountCurve,
false, settlement, settlement,
Null<Real>());
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,
Expand Down Expand Up @@ -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<YieldTermStructure>& 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() << ")");
Expand All @@ -493,27 +524,30 @@ 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<YieldTermStructure>& d,
const DayCounter& dayCounter,
Compounding compounding,
Frequency frequency,
Date settlement,
Real accuracy,
Size maxIterations,
Rate guess) {
Rate guess,
const Bond::Price::Type priceType) {
if (settlement == Date())
settlement = bond.settlementDate();

QL_REQUIRE(BondFunctions::isTradable(bond, settlement),
"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(),
Expand All @@ -523,5 +557,4 @@ namespace QuantLib {
false, settlement, settlement,
accuracy, maxIterations, guess);
}

}
20 changes: 17 additions & 3 deletions ql/pricingengines/bond/bondfunctions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>());
Real price = Null<Real>(),
Bond::Price::Type priceType = Bond::Price::Clean);
//@}

//! \name Yield (a.k.a. Internal Rate of Return, i.e. IRR) functions
Expand Down Expand Up @@ -235,16 +239,26 @@ namespace QuantLib {
Compounding compounding,
Frequency frequency,
Date settlementDate = Date());

static Real dirtyPrice(const Bond& bond,
const ext::shared_ptr<YieldTermStructure>& 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<YieldTermStructure>&,
const DayCounter& dayCounter,
Compounding compounding,
Frequency frequency,
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);
//@}

};
Expand Down
115 changes: 96 additions & 19 deletions test-suite/bonds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:"
Expand All @@ -245,7 +245,22 @@ void BondTest::testAtmRate() {
"\n coupon: " << io::rate(coupon) <<
"\n frequency: " << frequency <<
"\n clean price: " << price <<
"\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 dirty price: " << price <<
"\n atm rate: " << io::rate(calculated));
}
}
Expand Down Expand Up @@ -301,28 +316,56 @@ 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
Real price2 = BondFunctions::cleanPrice(
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);
}
}
}
Expand Down Expand Up @@ -382,6 +425,7 @@ void BondTest::testTheoretical() {

rate->setValue(m);

// Test yield vs clean price
Real price =
BondFunctions::cleanPrice(bond, m, bondDayCount, Continuous, frequency);
Real calculatedPrice = bond.cleanPrice();
Expand All @@ -401,17 +445,50 @@ void BondTest::testTheoretical() {

Rate calculatedYield = BondFunctions::yield(
bond, calculatedPrice, bondDayCount, Continuous, frequency,
bond.settlementDate(), tolerance, maxEvaluations);
bond.settlementDate(), tolerance, maxEvaluations, 0.05, Bond::Price::Clean);
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) <<
"\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, bondDayCount, Continuous, frequency,
bond.settlementDate(), tolerance, maxEvaluations, 0.05, Bond::Price::Dirty);
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 price: " << price <<
"\n yield': " << io::rate(calculatedYield));
"\n dirty price: " << price <<
"\n yield': " << io::rate(calculatedYield));
}
}
}
Expand Down

0 comments on commit 231b351

Please sign in to comment.