| 1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| 2 | |
| 3 | /* |
| 4 | Copyright (C) 2004 Jeff Yu |
| 5 | Copyright (C) 2004 M-Dimension Consulting Inc. |
| 6 | Copyright (C) 2005, 2006, 2007, 2008, 2010 StatPro Italia srl |
| 7 | Copyright (C) 2007, 2008, 2009 Ferdinando Ametrano |
| 8 | Copyright (C) 2007 Chiara Fornarola |
| 9 | Copyright (C) 2008 Simon Ibbotson |
| 10 | Copyright (C) 2022 Oleg Kulkov |
| 11 | |
| 12 | This file is part of QuantLib, a free-software/open-source library |
| 13 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 14 | |
| 15 | QuantLib is free software: you can redistribute it and/or modify it |
| 16 | under the terms of the QuantLib license. You should have received a |
| 17 | copy of the license along with this program; if not, please email |
| 18 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 19 | <http://quantlib.org/license.shtml>. |
| 20 | |
| 21 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 22 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 23 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 24 | */ |
| 25 | |
| 26 | #include <ql/cashflows/cashflows.hpp> |
| 27 | #include <ql/cashflows/floatingratecoupon.hpp> |
| 28 | #include <ql/cashflows/simplecashflow.hpp> |
| 29 | #include <ql/instruments/bond.hpp> |
| 30 | #include <ql/math/solvers1d/brent.hpp> |
| 31 | #include <ql/pricingengines/bond/bondfunctions.hpp> |
| 32 | #include <ql/pricingengines/bond/discountingbondengine.hpp> |
| 33 | #include <utility> |
| 34 | |
| 35 | namespace QuantLib { |
| 36 | |
| 37 | Bond::Bond(Natural settlementDays, Calendar calendar, const Date& issueDate, const Leg& coupons) |
| 38 | : settlementDays_(settlementDays), calendar_(std::move(calendar)), cashflows_(coupons), |
| 39 | issueDate_(issueDate) { |
| 40 | |
| 41 | if (!coupons.empty()) { |
| 42 | std::sort(first: cashflows_.begin(), last: cashflows_.end(), |
| 43 | comp: earlier_than<ext::shared_ptr<CashFlow> >()); |
| 44 | |
| 45 | if (issueDate_ != Date()) { |
| 46 | QL_REQUIRE(issueDate_<cashflows_[0]->date(), |
| 47 | "issue date (" << issueDate_ << |
| 48 | ") must be earlier than first payment date (" << |
| 49 | cashflows_[0]->date() << ")" ); |
| 50 | } |
| 51 | |
| 52 | maturityDate_ = coupons.back()->date(); |
| 53 | |
| 54 | addRedemptionsToCashflows(); |
| 55 | } |
| 56 | |
| 57 | registerWith(h: Settings::instance().evaluationDate()); |
| 58 | for (const auto& cashflow : cashflows_) |
| 59 | registerWith(h: cashflow); |
| 60 | } |
| 61 | |
| 62 | Bond::Bond(Natural settlementDays, |
| 63 | Calendar calendar, |
| 64 | Real faceAmount, |
| 65 | const Date& maturityDate, |
| 66 | const Date& issueDate, |
| 67 | const Leg& cashflows) |
| 68 | : settlementDays_(settlementDays), calendar_(std::move(calendar)), cashflows_(cashflows), |
| 69 | maturityDate_(maturityDate), issueDate_(issueDate) { |
| 70 | |
| 71 | if (!cashflows.empty()) { |
| 72 | |
| 73 | std::sort(first: cashflows_.begin(), last: cashflows_.end()-1, |
| 74 | comp: earlier_than<ext::shared_ptr<CashFlow> >()); |
| 75 | |
| 76 | if (maturityDate_ == Date()) |
| 77 | maturityDate_ = CashFlows::maturityDate(leg: cashflows); |
| 78 | |
| 79 | if (issueDate_ != Date()) { |
| 80 | QL_REQUIRE(issueDate_<cashflows_[0]->date(), |
| 81 | "issue date (" << issueDate_ << |
| 82 | ") must be earlier than first payment date (" << |
| 83 | cashflows_[0]->date() << ")" ); |
| 84 | } |
| 85 | |
| 86 | notionals_.resize(new_size: 2); |
| 87 | notionalSchedule_.resize(new_size: 2); |
| 88 | |
| 89 | notionalSchedule_[0] = Date(); |
| 90 | notionals_[0] = faceAmount; |
| 91 | |
| 92 | notionalSchedule_[1] = maturityDate_; |
| 93 | notionals_[1] = 0.0; |
| 94 | |
| 95 | redemptions_.push_back(x: cashflows.back()); |
| 96 | } |
| 97 | |
| 98 | registerWith(h: Settings::instance().evaluationDate()); |
| 99 | for (const auto& cashflow : cashflows_) |
| 100 | registerWith(h: cashflow); |
| 101 | } |
| 102 | |
| 103 | bool Bond::isExpired() const { |
| 104 | // this is the Instrument interface, so it doesn't use |
| 105 | // BondFunctions, and includeSettlementDateFlows is true |
| 106 | // (unless QL_TODAY_PAYMENTS will set it to false later on) |
| 107 | return CashFlows::isExpired(leg: cashflows_, |
| 108 | includeSettlementDateFlows: true, |
| 109 | settlementDate: Settings::instance().evaluationDate()); |
| 110 | } |
| 111 | |
| 112 | Real Bond::notional(Date d) const { |
| 113 | if (d == Date()) |
| 114 | d = settlementDate(); |
| 115 | |
| 116 | if (d > notionalSchedule_.back()) { |
| 117 | // after maturity |
| 118 | return 0.0; |
| 119 | } |
| 120 | |
| 121 | // After the check above, d is between the schedule |
| 122 | // boundaries. We search starting from the second notional |
| 123 | // date, since the first is null. After the call to |
| 124 | // lower_bound, *i is the earliest date which is greater or |
| 125 | // equal than d. Its index is greater or equal to 1. |
| 126 | auto i = std::lower_bound(first: notionalSchedule_.begin() + 1, last: notionalSchedule_.end(), val: d); |
| 127 | Size index = std::distance(first: notionalSchedule_.begin(), last: i); |
| 128 | |
| 129 | if (d < notionalSchedule_[index]) { |
| 130 | // no doubt about what to return |
| 131 | return notionals_[index-1]; |
| 132 | } else { |
| 133 | // d is equal to a redemption date. |
| 134 | // As per bond conventions, the payment has occurred; |
| 135 | // the bond already changed notional. |
| 136 | return notionals_[index]; |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | const ext::shared_ptr<CashFlow>& Bond::redemption() const { |
| 141 | QL_REQUIRE(redemptions_.size() == 1, |
| 142 | "multiple redemption cash flows given" ); |
| 143 | return redemptions_.back(); |
| 144 | } |
| 145 | |
| 146 | Date Bond::startDate() const { |
| 147 | return BondFunctions::startDate(bond: *this); |
| 148 | } |
| 149 | |
| 150 | Date Bond::maturityDate() const { |
| 151 | if (maturityDate_!=Null<Date>()) |
| 152 | return maturityDate_; |
| 153 | else |
| 154 | return BondFunctions::maturityDate(bond: *this); |
| 155 | } |
| 156 | |
| 157 | bool Bond::isTradable(Date d) const { |
| 158 | return BondFunctions::isTradable(bond: *this, settlementDate: d); |
| 159 | } |
| 160 | |
| 161 | Date Bond::settlementDate(Date d) const { |
| 162 | if (d==Date()) |
| 163 | d = Settings::instance().evaluationDate(); |
| 164 | |
| 165 | // usually, the settlement is at T+n... |
| 166 | Date settlement = calendar_.advance(d, n: settlementDays_, unit: Days); |
| 167 | // ...but the bond won't be traded until the issue date (if given.) |
| 168 | if (issueDate_ == Date()) |
| 169 | return settlement; |
| 170 | else |
| 171 | return std::max(a: settlement, b: issueDate_); |
| 172 | } |
| 173 | |
| 174 | Real Bond::cleanPrice() const { |
| 175 | return dirtyPrice() - accruedAmount(d: settlementDate()); |
| 176 | } |
| 177 | |
| 178 | Real Bond::dirtyPrice() const { |
| 179 | Real currentNotional = notional(d: settlementDate()); |
| 180 | if (currentNotional == 0.0) |
| 181 | return 0.0; |
| 182 | else |
| 183 | return settlementValue()*100.0/currentNotional; |
| 184 | } |
| 185 | |
| 186 | Real Bond::settlementValue() const { |
| 187 | calculate(); |
| 188 | QL_REQUIRE(settlementValue_ != Null<Real>(), |
| 189 | "settlement value not provided" ); |
| 190 | return settlementValue_; |
| 191 | } |
| 192 | |
| 193 | Real Bond::settlementValue(Real cleanPrice) const { |
| 194 | Real dirtyPrice = cleanPrice + accruedAmount(d: settlementDate()); |
| 195 | return dirtyPrice / 100.0 * notional(d: settlementDate()); |
| 196 | } |
| 197 | |
| 198 | Rate Bond::yield(const DayCounter& dc, |
| 199 | Compounding comp, |
| 200 | Frequency freq, |
| 201 | Real accuracy, |
| 202 | Size maxEvaluations, |
| 203 | Real guess, |
| 204 | Bond::Price::Type priceType) const { |
| 205 | Real currentNotional = notional(d: settlementDate()); |
| 206 | if (currentNotional == 0.0) |
| 207 | return 0.0; |
| 208 | |
| 209 | Real price = priceType == Bond::Price::Clean ? cleanPrice() : dirtyPrice(); |
| 210 | |
| 211 | return BondFunctions::yield(bond: *this, price, dayCounter: dc, compounding: comp, frequency: freq, |
| 212 | settlementDate: settlementDate(), |
| 213 | accuracy, maxIterations: maxEvaluations, |
| 214 | guess, priceType); |
| 215 | } |
| 216 | |
| 217 | Real Bond::cleanPrice(Rate y, |
| 218 | const DayCounter& dc, |
| 219 | Compounding comp, |
| 220 | Frequency freq, |
| 221 | Date settlement) const { |
| 222 | return BondFunctions::cleanPrice(bond: *this, yield: y, dayCounter: dc, compounding: comp, frequency: freq, settlementDate: settlement); |
| 223 | } |
| 224 | |
| 225 | Real Bond::dirtyPrice(Rate y, |
| 226 | const DayCounter& dc, |
| 227 | Compounding comp, |
| 228 | Frequency freq, |
| 229 | Date settlement) const { |
| 230 | Real currentNotional = notional(d: settlement); |
| 231 | if (currentNotional == 0.0) |
| 232 | return 0.0; |
| 233 | |
| 234 | return BondFunctions::cleanPrice(bond: *this, yield: y, dayCounter: dc, compounding: comp, frequency: freq, settlementDate: settlement) |
| 235 | + accruedAmount(d: settlement); |
| 236 | } |
| 237 | |
| 238 | Rate Bond::yield(Real price, |
| 239 | const DayCounter& dc, |
| 240 | Compounding comp, |
| 241 | Frequency freq, |
| 242 | Date settlement, |
| 243 | Real accuracy, |
| 244 | Size maxEvaluations, |
| 245 | Real guess, |
| 246 | Bond::Price::Type priceType) const { |
| 247 | Real currentNotional = notional(d: settlement); |
| 248 | if (currentNotional == 0.0) |
| 249 | return 0.0; |
| 250 | |
| 251 | return BondFunctions::yield(bond: *this, price, dayCounter: dc, compounding: comp, frequency: freq, |
| 252 | settlementDate: settlement, accuracy, maxIterations: maxEvaluations, |
| 253 | guess, priceType); |
| 254 | } |
| 255 | |
| 256 | Real Bond::accruedAmount(Date settlement) const { |
| 257 | Real currentNotional = notional(d: settlement); |
| 258 | if (currentNotional == 0.0) |
| 259 | return 0.0; |
| 260 | |
| 261 | return BondFunctions::accruedAmount(bond: *this, settlementDate: settlement); |
| 262 | } |
| 263 | |
| 264 | Rate Bond::nextCouponRate(Date settlement) const { |
| 265 | return BondFunctions::nextCouponRate(bond: *this, settlementDate: settlement); |
| 266 | } |
| 267 | |
| 268 | Rate Bond::previousCouponRate(Date settlement) const { |
| 269 | return BondFunctions::previousCouponRate(bond: *this, settlementDate: settlement); |
| 270 | } |
| 271 | |
| 272 | Date Bond::nextCashFlowDate(Date settlement) const { |
| 273 | return BondFunctions::nextCashFlowDate(bond: *this, refDate: settlement); |
| 274 | } |
| 275 | |
| 276 | Date Bond::previousCashFlowDate(Date settlement) const { |
| 277 | return BondFunctions::previousCashFlowDate(bond: *this, refDate: settlement); |
| 278 | } |
| 279 | |
| 280 | void Bond::setupExpired() const { |
| 281 | Instrument::setupExpired(); |
| 282 | settlementValue_ = 0.0; |
| 283 | } |
| 284 | |
| 285 | void Bond::setupArguments(PricingEngine::arguments* args) const { |
| 286 | auto* arguments = dynamic_cast<Bond::arguments*>(args); |
| 287 | QL_REQUIRE(arguments != nullptr, "wrong argument type" ); |
| 288 | |
| 289 | arguments->settlementDate = settlementDate(); |
| 290 | arguments->cashflows = cashflows_; |
| 291 | arguments->calendar = calendar_; |
| 292 | } |
| 293 | |
| 294 | void Bond::fetchResults(const PricingEngine::results* r) const { |
| 295 | |
| 296 | Instrument::fetchResults(r); |
| 297 | |
| 298 | const auto* results = dynamic_cast<const Bond::results*>(r); |
| 299 | QL_ENSURE(results != nullptr, "wrong result type" ); |
| 300 | |
| 301 | settlementValue_ = results->settlementValue; |
| 302 | } |
| 303 | |
| 304 | void Bond::addRedemptionsToCashflows(const std::vector<Real>& redemptions) { |
| 305 | // First, we gather the notional information from the cashflows |
| 306 | calculateNotionalsFromCashflows(); |
| 307 | // Then, we create the redemptions based on the notional |
| 308 | // information and we add them to the cashflows vector after |
| 309 | // the coupons. |
| 310 | redemptions_.clear(); |
| 311 | for (Size i=1; i<notionalSchedule_.size(); ++i) { |
| 312 | Real R = i < redemptions.size() ? redemptions[i] : |
| 313 | !redemptions.empty() ? redemptions.back() : |
| 314 | 100.0; |
| 315 | Real amount = (R/100.0)*(notionals_[i-1]-notionals_[i]); |
| 316 | ext::shared_ptr<CashFlow> payment; |
| 317 | if (i < notionalSchedule_.size()-1) |
| 318 | payment.reset(p: new AmortizingPayment(amount, |
| 319 | notionalSchedule_[i])); |
| 320 | else |
| 321 | payment.reset(p: new Redemption(amount, notionalSchedule_[i])); |
| 322 | cashflows_.push_back(x: payment); |
| 323 | redemptions_.push_back(x: payment); |
| 324 | } |
| 325 | // stable_sort now moves the redemptions to the right places |
| 326 | // while ensuring that they follow coupons with the same date. |
| 327 | std::stable_sort(first: cashflows_.begin(), last: cashflows_.end(), |
| 328 | comp: earlier_than<ext::shared_ptr<CashFlow> >()); |
| 329 | } |
| 330 | |
| 331 | void Bond::setSingleRedemption(Real notional, |
| 332 | Real redemption, |
| 333 | const Date& date) { |
| 334 | |
| 335 | ext::shared_ptr<CashFlow> redemptionCashflow( |
| 336 | new Redemption(notional*redemption/100.0, date)); |
| 337 | setSingleRedemption(notional, redemption: redemptionCashflow); |
| 338 | } |
| 339 | |
| 340 | void Bond::setSingleRedemption(Real notional, |
| 341 | const ext::shared_ptr<CashFlow>& redemption) { |
| 342 | notionals_.resize(new_size: 2); |
| 343 | notionalSchedule_.resize(new_size: 2); |
| 344 | redemptions_.clear(); |
| 345 | |
| 346 | notionalSchedule_[0] = Date(); |
| 347 | notionals_[0] = notional; |
| 348 | |
| 349 | notionalSchedule_[1] = redemption->date(); |
| 350 | notionals_[1] = 0.0; |
| 351 | |
| 352 | cashflows_.push_back(x: redemption); |
| 353 | redemptions_.push_back(x: redemption); |
| 354 | } |
| 355 | |
| 356 | void Bond::deepUpdate() { |
| 357 | for (auto& cashflow : cashflows_) { |
| 358 | cashflow->deepUpdate(); |
| 359 | } |
| 360 | update(); |
| 361 | } |
| 362 | |
| 363 | void Bond::calculateNotionalsFromCashflows() { |
| 364 | notionalSchedule_.clear(); |
| 365 | notionals_.clear(); |
| 366 | |
| 367 | Date lastPaymentDate = Date(); |
| 368 | notionalSchedule_.emplace_back(); |
| 369 | for (auto& cashflow : cashflows_) { |
| 370 | ext::shared_ptr<Coupon> coupon = ext::dynamic_pointer_cast<Coupon>(r: cashflow); |
| 371 | if (!coupon) |
| 372 | continue; |
| 373 | |
| 374 | Real notional = coupon->nominal(); |
| 375 | // we add the notional only if it is the first one... |
| 376 | if (notionals_.empty()) { |
| 377 | notionals_.push_back(x: coupon->nominal()); |
| 378 | lastPaymentDate = coupon->date(); |
| 379 | } else if (!close(x: notional, y: notionals_.back())) { |
| 380 | // ...or if it has changed. |
| 381 | notionals_.push_back(x: coupon->nominal()); |
| 382 | // in this case, we also add the last valid date for |
| 383 | // the previous one... |
| 384 | notionalSchedule_.push_back(x: lastPaymentDate); |
| 385 | // ...and store the candidate for this one. |
| 386 | lastPaymentDate = coupon->date(); |
| 387 | } else { |
| 388 | // otherwise, we just extend the valid range of dates |
| 389 | // for the current notional. |
| 390 | lastPaymentDate = coupon->date(); |
| 391 | } |
| 392 | } |
| 393 | QL_REQUIRE(!notionals_.empty(), "no coupons provided" ); |
| 394 | notionals_.push_back(x: 0.0); |
| 395 | notionalSchedule_.push_back(x: lastPaymentDate); |
| 396 | } |
| 397 | |
| 398 | |
| 399 | void Bond::arguments::validate() const { |
| 400 | QL_REQUIRE(settlementDate != Date(), "no settlement date provided" ); |
| 401 | QL_REQUIRE(!cashflows.empty(), "no cash flow provided" ); |
| 402 | for (const auto & cf: cashflows) |
| 403 | QL_REQUIRE(cf, "null cash flow provided" ); |
| 404 | } |
| 405 | |
| 406 | } |
| 407 | |