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 551a1c5
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 28 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
80 changes: 70 additions & 10 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 @@ -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));
}
}
}
}
Expand Down Expand Up @@ -301,28 +318,71 @@ 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

0 comments on commit 551a1c5

Please sign in to comment.