• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lballabio / QuantLib / 14737517078

29 Apr 2025 05:26PM UTC coverage: 73.314% (+0.01%) from 73.3%
14737517078

Pull #2195

github

web-flow
Merge 438444191 into 29147dea4
Pull Request #2195: Added `Handle<Quote>` for spread in `OISRateHelper`

31 of 32 new or added lines in 2 files covered. (96.88%)

122 existing lines in 15 files now uncovered.

56262 of 76741 relevant lines covered (73.31%)

8684256.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.87
/ql/instruments/bond.cpp
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 <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)
1,739✔
39
    : settlementDays_(settlementDays), calendar_(std::move(calendar)), cashflows_(coupons),
1,739✔
40
      issueDate_(issueDate) {
1,739✔
41

42
        if (!coupons.empty()) {
1,739✔
43
            std::sort(cashflows_.begin(), cashflows_.end(),
8✔
44
                      earlier_than<ext::shared_ptr<CashFlow> >());
45

46
            if (issueDate_ != Date()) {
8✔
47
                QL_REQUIRE(issueDate_<cashflows_[0]->date(),
8✔
48
                           "issue date (" << issueDate_ <<
49
                           ") must be earlier than first payment date (" <<
50
                           cashflows_[0]->date() << ")");
51
            }
52

53
            maturityDate_ = coupons.back()->date();
8✔
54

55
            addRedemptionsToCashflows();
16✔
56
        }
57

58
        registerWith(Settings::instance().evaluationDate());
3,478✔
59
        for (const auto& cashflow : cashflows_)
1,942✔
60
            registerWith(cashflow);
406✔
61
    }
1,739✔
62

63
    Bond::Bond(Natural settlementDays,
40✔
64
               Calendar calendar,
65
               Real faceAmount,
66
               const Date& maturityDate,
67
               const Date& issueDate,
68
               const Leg& cashflows)
40✔
69
    : settlementDays_(settlementDays), calendar_(std::move(calendar)), cashflows_(cashflows),
40✔
70
      maturityDate_(maturityDate), issueDate_(issueDate) {
40✔
71

72
        if (!cashflows.empty()) {
40✔
73

74
            std::sort(cashflows_.begin(), cashflows_.end()-1,
40✔
75
                      earlier_than<ext::shared_ptr<CashFlow> >());
76

77
            if (maturityDate_ == Date())
40✔
78
                maturityDate_ = CashFlows::maturityDate(cashflows);
×
79

80
            if (issueDate_ != Date()) {
40✔
81
                QL_REQUIRE(issueDate_<cashflows_[0]->date(),
40✔
82
                           "issue date (" << issueDate_ <<
83
                           ") must be earlier than first payment date (" <<
84
                           cashflows_[0]->date() << ")");
85
            }
86

87
            notionals_.resize(2);
40✔
88
            notionalSchedule_.resize(2);
40✔
89

90
            notionalSchedule_[0] = Date();
40✔
91
            notionals_[0] = faceAmount;
40✔
92

93
            notionalSchedule_[1] = maturityDate_;
40✔
94
            notionals_[1] = 0.0;
40✔
95

96
            redemptions_.push_back(cashflows.back());
40✔
97
        }
98

99
        registerWith(Settings::instance().evaluationDate());
80✔
100
        for (const auto& cashflow : cashflows_)
675✔
101
            registerWith(cashflow);
1,270✔
102
    }
40✔
103

104
    bool Bond::isExpired() const {
829,186✔
105
        // this is the Instrument interface, so it doesn't use
106
        // BondFunctions, and includeSettlementDateFlows is true
107
        // (unless QL_TODAY_PAYMENTS will set it to false later on)
108
        return CashFlows::isExpired(cashflows_,
829,186✔
109
                                    true,
110
                                    Settings::instance().evaluationDate());
829,186✔
111
    }
112

113
    Real Bond::notional(Date d) const {
3,401,139✔
114
        if (d == Date())
3,401,139✔
115
            d = settlementDate();
2,901✔
116

117
        if (d > notionalSchedule_.back()) {
3,401,139✔
118
            // after maturity
119
            return 0.0;
120
        }
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
        auto i = std::lower_bound(notionalSchedule_.begin() + 1, notionalSchedule_.end(), d);
3,401,139✔
128
        Size index = std::distance(notionalSchedule_.begin(), i);
129

130
        if (d < notionalSchedule_[index]) {
3,401,139✔
131
            // no doubt about what to return
132
            return notionals_[index-1];
3,401,139✔
133
        } 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
            return notionals_[index];
×
138
        }
139
    }
140

141
    const ext::shared_ptr<CashFlow>& Bond::redemption() const {
118✔
142
        QL_REQUIRE(redemptions_.size() == 1,
118✔
143
                   "multiple redemption cash flows given");
144
        return redemptions_.back();
118✔
145
    }
146

147
    Date Bond::startDate() const {
×
148
        return BondFunctions::startDate(*this);
×
149
    }
150

151
    Date Bond::maturityDate() const {
155✔
152
        if (maturityDate_ != Date())
155✔
153
            return maturityDate_;
155✔
154
        else
155
            return BondFunctions::maturityDate(*this);
×
156
    }
157

158
    bool Bond::isTradable(Date d) const {
×
159
        return BondFunctions::isTradable(*this, d);
×
160
    }
161

162
    Date Bond::settlementDate(Date d) const {
2,517,688✔
163
        if (d==Date())
2,517,688✔
164
            d = Settings::instance().evaluationDate();
2,517,688✔
165

166
        // usually, the settlement is at T+n...
167
        Date settlement = calendar_.advance(d, settlementDays_, Days);
2,517,688✔
168
        // ...but the bond won't be traded until the issue date (if given.)
169
        if (issueDate_ == Date())
2,517,688✔
170
            return settlement;
2,484,551✔
171
        else
172
            return std::max(settlement, issueDate_);
45,986✔
173
    }
174

175
    Real Bond::cleanPrice() const {
829,157✔
176
        return dirtyPrice() - accruedAmount(settlementDate());
829,157✔
177
    }
178

179
    Real Bond::dirtyPrice() const {
829,589✔
180
        Real currentNotional = notional(settlementDate());
829,589✔
181
        if (currentNotional == 0.0)
829,589✔
182
            return 0.0;
183
        else
184
            return settlementValue()*100.0/currentNotional;
829,589✔
185
    }
186

187
    Real Bond::settlementValue() const {
829,599✔
188
        calculate();
829,599✔
189
        QL_REQUIRE(settlementValue_ != Null<Real>(),
829,599✔
190
                   "settlement value not provided");
191
        return settlementValue_;
829,599✔
192
    }
193

194
    Real Bond::settlementValue(Real cleanPrice) const {
×
195
        Real dirtyPrice = cleanPrice + accruedAmount(settlementDate());
×
196
        return dirtyPrice / 100.0 * notional(settlementDate());
×
197
    }
198

199
    Rate Bond::yield(const DayCounter& dc,
15✔
200
                     Compounding comp,
201
                     Frequency freq,
202
                     Real accuracy,
203
                     Size maxEvaluations,
204
                     Real guess,
205
                     Bond::Price::Type priceType) const {
206
        Real currentNotional = notional(settlementDate());
15✔
207
        if (currentNotional == 0.0)
15✔
208
            return 0.0;
209

210
        Bond::Price price(priceType == Bond::Price::Clean ? cleanPrice() : dirtyPrice(), priceType);
15✔
211

212
        return BondFunctions::yield(*this, price, dc, comp, freq,
15✔
213
                                    settlementDate(),
214
                                    accuracy, maxEvaluations,
215
                                    guess);
216
    }
217

218
    Real Bond::cleanPrice(Rate y,
1✔
219
                          const DayCounter& dc,
220
                          Compounding comp,
221
                          Frequency freq,
222
                          Date settlement) const {
223
        return BondFunctions::cleanPrice(*this, y, dc, comp, freq, settlement);
1✔
224
    }
225

226
    Real Bond::dirtyPrice(Rate y,
×
227
                          const DayCounter& dc,
228
                          Compounding comp,
229
                          Frequency freq,
230
                          Date settlement) const {
231
        Real currentNotional = notional(settlement);
×
232
        if (currentNotional == 0.0)
×
233
            return 0.0;
234

235
        return BondFunctions::cleanPrice(*this, y, dc, comp, freq, settlement)
×
236
            + accruedAmount(settlement);
×
237
    }
238

239
    Rate Bond::yield(Bond::Price price,
10✔
240
                     const DayCounter& dc,
241
                     Compounding comp,
242
                     Frequency freq,
243
                     Date settlement,
244
                     Real accuracy,
245
                     Size maxEvaluations,
246
                     Real guess) const {
247
        Real currentNotional = notional(settlement);
10✔
248
        if (currentNotional == 0.0)
10✔
249
            return 0.0;
250

251
        return BondFunctions::yield(*this, price, dc, comp, freq,
10✔
252
                                    settlement, accuracy, maxEvaluations,
253
                                    guess);
10✔
254
    }
255

256
    Real Bond::accruedAmount(Date settlement) const {
841,260✔
257
        Real currentNotional = notional(settlement);
841,260✔
258
        if (currentNotional == 0.0)
841,260✔
259
            return 0.0;
260

261
        return BondFunctions::accruedAmount(*this, settlement);
841,260✔
262
    }
263

264
    Rate Bond::nextCouponRate(Date settlement) const {
4✔
265
        return BondFunctions::nextCouponRate(*this, settlement);
4✔
266
    }
267

268
    Rate Bond::previousCouponRate(Date settlement) const {
2✔
269
        return BondFunctions::previousCouponRate(*this, settlement);
2✔
270
    }
271

272
    Date Bond::nextCashFlowDate(Date settlement) const {
144✔
273
        return BondFunctions::nextCashFlowDate(*this, settlement);
144✔
274
    }
275

UNCOV
276
    Date Bond::previousCashFlowDate(Date settlement) const {
×
UNCOV
277
        return BondFunctions::previousCashFlowDate(*this, settlement);
×
278
    }
279

UNCOV
280
    void Bond::setupExpired() const {
×
UNCOV
281
        Instrument::setupExpired();
×
UNCOV
282
        settlementValue_ = 0.0;
×
UNCOV
283
    }
×
284

285
    void Bond::setupArguments(PricingEngine::arguments* args) const {
829,182✔
286
        auto* arguments = dynamic_cast<Bond::arguments*>(args);
829,182✔
287
        QL_REQUIRE(arguments != nullptr, "wrong argument type");
829,182✔
288

289
        arguments->settlementDate = settlementDate();
829,182✔
290
        arguments->cashflows = cashflows_;
829,182✔
291
        arguments->calendar = calendar_;
292
    }
829,182✔
293

294
    void Bond::fetchResults(const PricingEngine::results* r) const {
829,183✔
295

296
        Instrument::fetchResults(r);
829,183✔
297

298
        const auto* results = dynamic_cast<const Bond::results*>(r);
829,183✔
299
        QL_ENSURE(results != nullptr, "wrong result type");
829,183✔
300

301
        settlementValue_ = results->settlementValue;
829,183✔
302
    }
829,183✔
303

304
    void Bond::addRedemptionsToCashflows(const std::vector<Real>& redemptions) {
1,700✔
305
        // First, we gather the notional information from the cashflows
306
        calculateNotionalsFromCashflows();
1,700✔
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();
1,700✔
311
        for (Size i=1; i<notionalSchedule_.size(); ++i) {
8,132✔
312
            Real R = i < redemptions.size() ? redemptions[i] :
6,432✔
313
                     !redemptions.empty()   ? redemptions.back() :
6,432✔
314
                                              100.0;
315
            Real amount = (R/100.0)*(notionals_[i-1]-notionals_[i]);
6,432✔
316
            ext::shared_ptr<CashFlow> payment;
6,432✔
317
            if (i < notionalSchedule_.size()-1)
6,432✔
318
                payment = ext::make_shared<AmortizingPayment>(amount,
9,464✔
319
                                                    notionalSchedule_[i]);
4,732✔
320
            else
321
                payment = ext::make_shared<Redemption>(amount, notionalSchedule_[i]);
3,400✔
322
            cashflows_.push_back(payment);
6,432✔
323
            redemptions_.push_back(payment);
6,432✔
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(cashflows_.begin(), cashflows_.end(),
1,700✔
328
                         earlier_than<ext::shared_ptr<CashFlow> >());
329
    }
1,700✔
330

331
    void Bond::setSingleRedemption(Real notional,
37✔
332
                                   Real redemption,
333
                                   const Date& date) {
334

335
        ext::shared_ptr<CashFlow> redemptionCashflow(
336
                         new Redemption(notional*redemption/100.0, date));
37✔
337
        setSingleRedemption(notional, redemptionCashflow);
37✔
338
    }
37✔
339

340
    void Bond::setSingleRedemption(Real notional,
37✔
341
                                   const ext::shared_ptr<CashFlow>& redemption) {
342
        notionals_.resize(2);
37✔
343
        notionalSchedule_.resize(2);
37✔
344
        redemptions_.clear();
37✔
345

346
        notionalSchedule_[0] = Date();
37✔
347
        notionals_[0] = notional;
37✔
348

349
        notionalSchedule_[1] = redemption->date();
37✔
350
        notionals_[1] = 0.0;
37✔
351

352
        cashflows_.push_back(redemption);
37✔
353
        redemptions_.push_back(redemption);
37✔
354
    }
37✔
355

UNCOV
356
    void Bond::deepUpdate() {
×
UNCOV
357
        for (auto& cashflow : cashflows_) {
×
UNCOV
358
            cashflow->deepUpdate();
×
359
        }
UNCOV
360
        update();
×
UNCOV
361
    }
×
362

363
    void Bond::calculateNotionalsFromCashflows() {
1,702✔
364
        notionalSchedule_.clear();
1,702✔
365
        notionals_.clear();
1,702✔
366

367
        Date lastPaymentDate = Date();
1,702✔
368
        notionalSchedule_.emplace_back();
1,702✔
369
        for (auto& cashflow : cashflows_) {
33,634✔
370
            ext::shared_ptr<Coupon> coupon = ext::dynamic_pointer_cast<Coupon>(cashflow);
31,932✔
371
            if (!coupon)
31,932✔
372
                continue;
373

374
            Real notional = coupon->nominal();
31,930✔
375
            // we add the notional only if it is the first one...
376
            if (notionals_.empty()) {
31,930✔
377
                notionals_.push_back(coupon->nominal());
1,702✔
378
                lastPaymentDate = coupon->date();
1,702✔
379
            } else if (!close(notional, notionals_.back())) {
30,228✔
380
                // ...or if it has changed.
381
                notionals_.push_back(coupon->nominal());
4,732✔
382
                // in this case, we also add the last valid date for
383
                // the previous one...
384
                notionalSchedule_.push_back(lastPaymentDate);
4,732✔
385
                // ...and store the candidate for this one.
386
                lastPaymentDate = coupon->date();
4,732✔
387
            } else {
388
                // otherwise, we just extend the valid range of dates
389
                // for the current notional.
390
                lastPaymentDate = coupon->date();
25,496✔
391
            }
392
        }
393
        QL_REQUIRE(!notionals_.empty(), "no coupons provided");
1,702✔
394
        notionals_.push_back(0.0);
1,702✔
395
        notionalSchedule_.push_back(lastPaymentDate);
1,702✔
396
    }
1,702✔
397

398

399
    void Bond::arguments::validate() const {
829,123✔
400
        QL_REQUIRE(settlementDate != Date(), "no settlement date provided");
829,123✔
401
        QL_REQUIRE(!cashflows.empty(), "no cash flow provided");
829,123✔
402
        for (const auto & cf: cashflows)
14,509,809✔
403
            QL_REQUIRE(cf, "null cash flow provided");
13,680,686✔
404
    }
829,123✔
405

406
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc