Coverage Report

Created: 2026-02-03 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}