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
35namespace 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

source code of quantlib/ql/instruments/bond.cpp

Morty Proxy This is a proxified and sanitized view of the page, visit original site.