/src/quantlib/ql/instruments/bond.cpp
Line | Count | Source |
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 | | <https://www.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 <ql/shared_ptr.hpp> |
34 | | #include <utility> |
35 | | |
36 | | namespace QuantLib { |
37 | | |
38 | | Bond::Bond(Natural settlementDays, Calendar calendar, const Date& issueDate, const Leg& coupons) |
39 | 76.6k | : settlementDays_(settlementDays), calendar_(std::move(calendar)), cashflows_(coupons), |
40 | 76.6k | issueDate_(issueDate) { |
41 | | |
42 | 76.6k | if (!coupons.empty()) { |
43 | 0 | std::sort(cashflows_.begin(), cashflows_.end(), |
44 | 0 | earlier_than<ext::shared_ptr<CashFlow> >()); |
45 | |
|
46 | 0 | if (issueDate_ != Date()) { |
47 | 0 | QL_REQUIRE(issueDate_<cashflows_[0]->date(), |
48 | 0 | "issue date (" << issueDate_ << |
49 | 0 | ") must be earlier than first payment date (" << |
50 | 0 | cashflows_[0]->date() << ")"); |
51 | 0 | } |
52 | | |
53 | 0 | maturityDate_ = coupons.back()->date(); |
54 | |
|
55 | 0 | addRedemptionsToCashflows(); |
56 | 0 | } |
57 | | |
58 | 76.6k | registerWith(Settings::instance().evaluationDate()); |
59 | 76.6k | for (const auto& cashflow : cashflows_) |
60 | 0 | registerWith(cashflow); |
61 | 76.6k | } QuantLib::Bond::Bond(unsigned int, QuantLib::Calendar, QuantLib::Date const&, std::__1::vector<boost::shared_ptr<QuantLib::CashFlow>, std::__1::allocator<boost::shared_ptr<QuantLib::CashFlow> > > const&) Line | Count | Source | 39 | 76.6k | : settlementDays_(settlementDays), calendar_(std::move(calendar)), cashflows_(coupons), | 40 | 76.6k | issueDate_(issueDate) { | 41 | | | 42 | 76.6k | if (!coupons.empty()) { | 43 | 0 | std::sort(cashflows_.begin(), cashflows_.end(), | 44 | 0 | earlier_than<ext::shared_ptr<CashFlow> >()); | 45 | |
| 46 | 0 | if (issueDate_ != Date()) { | 47 | 0 | QL_REQUIRE(issueDate_<cashflows_[0]->date(), | 48 | 0 | "issue date (" << issueDate_ << | 49 | 0 | ") must be earlier than first payment date (" << | 50 | 0 | cashflows_[0]->date() << ")"); | 51 | 0 | } | 52 | | | 53 | 0 | maturityDate_ = coupons.back()->date(); | 54 | |
| 55 | 0 | addRedemptionsToCashflows(); | 56 | 0 | } | 57 | | | 58 | 76.6k | registerWith(Settings::instance().evaluationDate()); | 59 | 76.6k | for (const auto& cashflow : cashflows_) | 60 | 0 | registerWith(cashflow); | 61 | 76.6k | } |
Unexecuted instantiation: QuantLib::Bond::Bond(unsigned int, QuantLib::Calendar, QuantLib::Date const&, std::__1::vector<boost::shared_ptr<QuantLib::CashFlow>, std::__1::allocator<boost::shared_ptr<QuantLib::CashFlow> > > const&) |
62 | | |
63 | | Bond::Bond(Natural settlementDays, |
64 | | Calendar calendar, |
65 | | Real faceAmount, |
66 | | const Date& maturityDate, |
67 | | const Date& issueDate, |
68 | | const Leg& cashflows) |
69 | 0 | : settlementDays_(settlementDays), calendar_(std::move(calendar)), cashflows_(cashflows), |
70 | 0 | maturityDate_(maturityDate), issueDate_(issueDate) { |
71 | |
|
72 | 0 | if (!cashflows.empty()) { |
73 | |
|
74 | 0 | std::sort(cashflows_.begin(), cashflows_.end()-1, |
75 | 0 | earlier_than<ext::shared_ptr<CashFlow> >()); |
76 | |
|
77 | 0 | if (maturityDate_ == Date()) |
78 | 0 | maturityDate_ = CashFlows::maturityDate(cashflows); |
79 | |
|
80 | 0 | if (issueDate_ != Date()) { |
81 | 0 | QL_REQUIRE(issueDate_<cashflows_[0]->date(), |
82 | 0 | "issue date (" << issueDate_ << |
83 | 0 | ") must be earlier than first payment date (" << |
84 | 0 | cashflows_[0]->date() << ")"); |
85 | 0 | } |
86 | | |
87 | 0 | notionals_.resize(2); |
88 | 0 | notionalSchedule_.resize(2); |
89 | |
|
90 | 0 | notionalSchedule_[0] = Date(); |
91 | 0 | notionals_[0] = faceAmount; |
92 | |
|
93 | 0 | notionalSchedule_[1] = maturityDate_; |
94 | 0 | notionals_[1] = 0.0; |
95 | |
|
96 | 0 | redemptions_.push_back(cashflows.back()); |
97 | 0 | } |
98 | | |
99 | 0 | registerWith(Settings::instance().evaluationDate()); |
100 | 0 | for (const auto& cashflow : cashflows_) |
101 | 0 | registerWith(cashflow); |
102 | 0 | } Unexecuted instantiation: QuantLib::Bond::Bond(unsigned int, QuantLib::Calendar, double, QuantLib::Date const&, QuantLib::Date const&, std::__1::vector<boost::shared_ptr<QuantLib::CashFlow>, std::__1::allocator<boost::shared_ptr<QuantLib::CashFlow> > > const&) Unexecuted instantiation: QuantLib::Bond::Bond(unsigned int, QuantLib::Calendar, double, QuantLib::Date const&, QuantLib::Date const&, std::__1::vector<boost::shared_ptr<QuantLib::CashFlow>, std::__1::allocator<boost::shared_ptr<QuantLib::CashFlow> > > const&) |
103 | | |
104 | 0 | bool Bond::isExpired() const { |
105 | | // this is the Instrument interface, so it doesn't use |
106 | | // BondFunctions. We pass nullopt as includeSettlementDateFlows |
107 | | // so that CashFlows::isExpired uses the default setting. |
108 | 0 | return CashFlows::isExpired(cashflows_, |
109 | 0 | ext::nullopt, |
110 | 0 | Settings::instance().evaluationDate()); |
111 | 0 | } |
112 | | |
113 | 0 | Real Bond::notional(Date d) const { |
114 | 0 | if (d == Date()) |
115 | 0 | d = settlementDate(); |
116 | |
|
117 | 0 | if (d > notionalSchedule_.back()) { |
118 | | // after maturity |
119 | 0 | return 0.0; |
120 | 0 | } |
121 | | |
122 | | // After the check above, d is between the schedule |
123 | | // boundaries. We search starting from the second notional |
124 | | // date, since the first is null. After the call to |
125 | | // lower_bound, *i is the earliest date which is greater or |
126 | | // equal than d. Its index is greater or equal to 1. |
127 | 0 | auto i = std::lower_bound(notionalSchedule_.begin() + 1, notionalSchedule_.end(), d); |
128 | 0 | Size index = std::distance(notionalSchedule_.begin(), i); |
129 | |
|
130 | 0 | if (d < notionalSchedule_[index]) { |
131 | | // no doubt about what to return |
132 | 0 | return notionals_[index-1]; |
133 | 0 | } else { |
134 | | // d is equal to a redemption date. |
135 | | // As per bond conventions, the payment has occurred; |
136 | | // the bond already changed notional. |
137 | 0 | return notionals_[index]; |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | 0 | const ext::shared_ptr<CashFlow>& Bond::redemption() const { |
142 | 0 | QL_REQUIRE(redemptions_.size() == 1, |
143 | 0 | "multiple redemption cash flows given"); |
144 | 0 | return redemptions_.back(); |
145 | 0 | } |
146 | | |
147 | 0 | Date Bond::startDate() const { |
148 | 0 | return BondFunctions::startDate(*this); |
149 | 0 | } |
150 | | |
151 | 0 | Date Bond::maturityDate() const { |
152 | 0 | if (maturityDate_ != Date()) |
153 | 0 | return maturityDate_; |
154 | 0 | else |
155 | 0 | return BondFunctions::maturityDate(*this); |
156 | 0 | } |
157 | | |
158 | 0 | bool Bond::isTradable(Date d) const { |
159 | 0 | return BondFunctions::isTradable(*this, d); |
160 | 0 | } |
161 | | |
162 | 0 | Date Bond::settlementDate(Date d) const { |
163 | 0 | if (d==Date()) |
164 | 0 | d = Settings::instance().evaluationDate(); |
165 | | |
166 | | // usually, the settlement is at T+n... |
167 | 0 | Date settlement = calendar_.advance(d, settlementDays_, Days); |
168 | | // ...but the bond won't be traded until the issue date (if given.) |
169 | 0 | if (issueDate_ == Date()) |
170 | 0 | return settlement; |
171 | 0 | else |
172 | 0 | return std::max(settlement, issueDate_); |
173 | 0 | } |
174 | | |
175 | 0 | Real Bond::cleanPrice() const { |
176 | 0 | return dirtyPrice() - accruedAmount(settlementDate()); |
177 | 0 | } |
178 | | |
179 | 0 | Real Bond::dirtyPrice() const { |
180 | 0 | Real currentNotional = notional(settlementDate()); |
181 | 0 | if (currentNotional == 0.0) |
182 | 0 | return 0.0; |
183 | 0 | else |
184 | 0 | return settlementValue()*100.0/currentNotional; |
185 | 0 | } |
186 | | |
187 | 0 | Real Bond::settlementValue() const { |
188 | 0 | calculate(); |
189 | 0 | QL_REQUIRE(settlementValue_ != Null<Real>(), |
190 | 0 | "settlement value not provided"); |
191 | 0 | return settlementValue_; |
192 | 0 | } |
193 | | |
194 | 0 | Real Bond::settlementValue(Real cleanPrice) const { |
195 | 0 | Real dirtyPrice = cleanPrice + accruedAmount(settlementDate()); |
196 | 0 | return dirtyPrice / 100.0 * notional(settlementDate()); |
197 | 0 | } |
198 | | |
199 | | Rate Bond::yield(const DayCounter& dc, |
200 | | Compounding comp, |
201 | | Frequency freq, |
202 | | Real accuracy, |
203 | | Size maxEvaluations, |
204 | | Real guess, |
205 | 0 | Bond::Price::Type priceType) const { |
206 | 0 | Real currentNotional = notional(settlementDate()); |
207 | 0 | if (currentNotional == 0.0) |
208 | 0 | return 0.0; |
209 | | |
210 | 0 | Bond::Price price(priceType == Bond::Price::Clean ? cleanPrice() : dirtyPrice(), priceType); |
211 | |
|
212 | 0 | return BondFunctions::yield(*this, price, dc, comp, freq, |
213 | 0 | settlementDate(), |
214 | 0 | accuracy, maxEvaluations, |
215 | 0 | guess); |
216 | 0 | } |
217 | | |
218 | | Real Bond::cleanPrice(Rate y, |
219 | | const DayCounter& dc, |
220 | | Compounding comp, |
221 | | Frequency freq, |
222 | 0 | Date settlement) const { |
223 | 0 | return BondFunctions::cleanPrice(*this, y, dc, comp, freq, settlement); |
224 | 0 | } |
225 | | |
226 | | Real Bond::dirtyPrice(Rate y, |
227 | | const DayCounter& dc, |
228 | | Compounding comp, |
229 | | Frequency freq, |
230 | 0 | Date settlement) const { |
231 | 0 | Real currentNotional = notional(settlement); |
232 | 0 | if (currentNotional == 0.0) |
233 | 0 | return 0.0; |
234 | | |
235 | 0 | return BondFunctions::cleanPrice(*this, y, dc, comp, freq, settlement) |
236 | 0 | + accruedAmount(settlement); |
237 | 0 | } |
238 | | |
239 | | Rate Bond::yield(Bond::Price price, |
240 | | const DayCounter& dc, |
241 | | Compounding comp, |
242 | | Frequency freq, |
243 | | Date settlement, |
244 | | Real accuracy, |
245 | | Size maxEvaluations, |
246 | 0 | Real guess) const { |
247 | 0 | Real currentNotional = notional(settlement); |
248 | 0 | if (currentNotional == 0.0) |
249 | 0 | return 0.0; |
250 | | |
251 | 0 | return BondFunctions::yield(*this, price, dc, comp, freq, |
252 | 0 | settlement, accuracy, maxEvaluations, |
253 | 0 | guess); |
254 | 0 | } |
255 | | |
256 | 0 | Real Bond::accruedAmount(Date settlement) const { |
257 | 0 | Real currentNotional = notional(settlement); |
258 | 0 | if (currentNotional == 0.0) |
259 | 0 | return 0.0; |
260 | | |
261 | 0 | return BondFunctions::accruedAmount(*this, settlement); |
262 | 0 | } |
263 | | |
264 | 0 | Rate Bond::nextCouponRate(Date settlement) const { |
265 | 0 | return BondFunctions::nextCouponRate(*this, settlement); |
266 | 0 | } |
267 | | |
268 | 0 | Rate Bond::previousCouponRate(Date settlement) const { |
269 | 0 | return BondFunctions::previousCouponRate(*this, settlement); |
270 | 0 | } |
271 | | |
272 | 0 | Date Bond::nextCashFlowDate(Date settlement) const { |
273 | 0 | return BondFunctions::nextCashFlowDate(*this, settlement); |
274 | 0 | } |
275 | | |
276 | 0 | Date Bond::previousCashFlowDate(Date settlement) const { |
277 | 0 | return BondFunctions::previousCashFlowDate(*this, settlement); |
278 | 0 | } |
279 | | |
280 | 0 | void Bond::setupExpired() const { |
281 | 0 | Instrument::setupExpired(); |
282 | 0 | settlementValue_ = 0.0; |
283 | 0 | } |
284 | | |
285 | 0 | void Bond::setupArguments(PricingEngine::arguments* args) const { |
286 | 0 | auto* arguments = dynamic_cast<Bond::arguments*>(args); |
287 | 0 | QL_REQUIRE(arguments != nullptr, "wrong argument type"); |
288 | | |
289 | 0 | arguments->settlementDate = settlementDate(); |
290 | 0 | arguments->cashflows = cashflows_; |
291 | 0 | arguments->calendar = calendar_; |
292 | 0 | } |
293 | | |
294 | 0 | void Bond::fetchResults(const PricingEngine::results* r) const { |
295 | |
|
296 | 0 | Instrument::fetchResults(r); |
297 | |
|
298 | 0 | const auto* results = dynamic_cast<const Bond::results*>(r); |
299 | 0 | QL_ENSURE(results != nullptr, "wrong result type"); |
300 | | |
301 | 0 | settlementValue_ = results->settlementValue; |
302 | 0 | } |
303 | | |
304 | 76.6k | void Bond::addRedemptionsToCashflows(const std::vector<Real>& redemptions) { |
305 | | // First, we gather the notional information from the cashflows |
306 | 76.6k | 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 | 76.6k | redemptions_.clear(); |
311 | 27.6M | for (Size i=1; i<notionalSchedule_.size(); ++i) { |
312 | 27.6M | Real R = i < redemptions.size() ? redemptions[i] : |
313 | 27.6M | !redemptions.empty() ? redemptions.back() : |
314 | 27.6M | 100.0; |
315 | 27.6M | Real amount = (R/100.0)*(notionals_[i-1]-notionals_[i]); |
316 | 27.6M | ext::shared_ptr<CashFlow> payment; |
317 | 27.6M | if (i < notionalSchedule_.size()-1) |
318 | 27.5M | payment = ext::make_shared<AmortizingPayment>(amount, |
319 | 27.5M | notionalSchedule_[i]); |
320 | 76.6k | else |
321 | 76.6k | payment = ext::make_shared<Redemption>(amount, notionalSchedule_[i]); |
322 | 27.6M | cashflows_.push_back(payment); |
323 | 27.6M | redemptions_.push_back(payment); |
324 | 27.6M | } |
325 | | // stable_sort now moves the redemptions to the right places |
326 | | // while ensuring that they follow coupons with the same date. |
327 | 76.6k | std::stable_sort(cashflows_.begin(), cashflows_.end(), |
328 | 76.6k | earlier_than<ext::shared_ptr<CashFlow> >()); |
329 | 76.6k | } |
330 | | |
331 | | void Bond::setSingleRedemption(Real notional, |
332 | | Real redemption, |
333 | 0 | const Date& date) { |
334 | |
|
335 | 0 | ext::shared_ptr<CashFlow> redemptionCashflow( |
336 | 0 | new Redemption(notional*redemption/100.0, date)); |
337 | 0 | setSingleRedemption(notional, redemptionCashflow); |
338 | 0 | } |
339 | | |
340 | | void Bond::setSingleRedemption(Real notional, |
341 | 0 | const ext::shared_ptr<CashFlow>& redemption) { |
342 | 0 | notionals_.resize(2); |
343 | 0 | notionalSchedule_.resize(2); |
344 | 0 | redemptions_.clear(); |
345 | |
|
346 | 0 | notionalSchedule_[0] = Date(); |
347 | 0 | notionals_[0] = notional; |
348 | |
|
349 | 0 | notionalSchedule_[1] = redemption->date(); |
350 | 0 | notionals_[1] = 0.0; |
351 | |
|
352 | 0 | cashflows_.push_back(redemption); |
353 | 0 | redemptions_.push_back(redemption); |
354 | 0 | } |
355 | | |
356 | 0 | void Bond::deepUpdate() { |
357 | 0 | for (auto& cashflow : cashflows_) { |
358 | 0 | cashflow->deepUpdate(); |
359 | 0 | } |
360 | 0 | update(); |
361 | 0 | } |
362 | | |
363 | 76.6k | void Bond::calculateNotionalsFromCashflows() { |
364 | 76.6k | notionalSchedule_.clear(); |
365 | 76.6k | notionals_.clear(); |
366 | | |
367 | 76.6k | Date lastPaymentDate = Date(); |
368 | 76.6k | notionalSchedule_.emplace_back(); |
369 | 27.6M | for (auto& cashflow : cashflows_) { |
370 | 27.6M | ext::shared_ptr<Coupon> coupon = ext::dynamic_pointer_cast<Coupon>(cashflow); |
371 | 27.6M | if (!coupon) |
372 | 0 | continue; |
373 | | |
374 | 27.6M | Real notional = coupon->nominal(); |
375 | | // we add the notional only if it is the first one... |
376 | 27.6M | if (notionals_.empty()) { |
377 | 76.6k | notionals_.push_back(coupon->nominal()); |
378 | 76.6k | lastPaymentDate = coupon->date(); |
379 | 27.5M | } else if (!close(notional, notionals_.back())) { |
380 | | // ...or if it has changed. |
381 | 27.5M | notionals_.push_back(coupon->nominal()); |
382 | | // in this case, we also add the last valid date for |
383 | | // the previous one... |
384 | 27.5M | notionalSchedule_.push_back(lastPaymentDate); |
385 | | // ...and store the candidate for this one. |
386 | 27.5M | lastPaymentDate = coupon->date(); |
387 | 27.5M | } else { |
388 | | // otherwise, we just extend the valid range of dates |
389 | | // for the current notional. |
390 | 0 | lastPaymentDate = coupon->date(); |
391 | 0 | } |
392 | 27.6M | } |
393 | 76.6k | QL_REQUIRE(!notionals_.empty(), "no coupons provided"); |
394 | 76.6k | notionals_.push_back(0.0); |
395 | 76.6k | notionalSchedule_.push_back(lastPaymentDate); |
396 | 76.6k | } |
397 | | |
398 | | |
399 | 0 | void Bond::arguments::validate() const { |
400 | 0 | QL_REQUIRE(settlementDate != Date(), "no settlement date provided"); |
401 | 0 | QL_REQUIRE(!cashflows.empty(), "no cash flow provided"); |
402 | 0 | for (const auto & cf: cashflows) |
403 | | QL_REQUIRE(cf, "null cash flow provided"); |
404 | 0 | } |
405 | | |
406 | | } |