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

lballabio / QuantLib / 8467932009

28 Mar 2024 01:18PM UTC coverage: 72.497% (+0.07%) from 72.426%
8467932009

Pull #1593

github

web-flow
Merge 9b4efa33c into d6f6c13a5
Pull Request #1593: allow swaptions to take OvernightIndexedSwap

103 of 127 new or added lines in 13 files covered. (81.1%)

373 existing lines in 21 files now uncovered.

54966 of 75818 relevant lines covered (72.5%)

8708317.57 hits per line

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

84.49
/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 <utility>
34

35
namespace QuantLib {
36

37
    Bond::Bond(Natural settlementDays, Calendar calendar, const Date& issueDate, const Leg& coupons)
1,707✔
38
    : settlementDays_(settlementDays), calendar_(std::move(calendar)), cashflows_(coupons),
1,707✔
39
      issueDate_(issueDate) {
1,707✔
40

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

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

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

54
            addRedemptionsToCashflows();
16✔
55
        }
56

57
        registerWith(Settings::instance().evaluationDate());
3,414✔
58
        for (const auto& cashflow : cashflows_)
1,910✔
59
            registerWith(cashflow);
406✔
60
    }
1,707✔
61

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

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

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

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

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

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

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

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

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

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

103
    bool Bond::isExpired() const {
850,509✔
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(cashflows_,
850,509✔
108
                                    true,
109
                                    Settings::instance().evaluationDate());
850,509✔
110
    }
111

112
    Real Bond::notional(Date d) const {
3,486,216✔
113
        if (d == Date())
3,486,216✔
114
            d = settlementDate();
2,897✔
115

116
        if (d > notionalSchedule_.back()) {
3,486,216✔
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(notionalSchedule_.begin() + 1, notionalSchedule_.end(), d);
3,486,216✔
127
        Size index = std::distance(notionalSchedule_.begin(), i);
128

129
        if (d < notionalSchedule_[index]) {
3,486,216✔
130
            // no doubt about what to return
131
            return notionals_[index-1];
3,486,216✔
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 {
108✔
141
        QL_REQUIRE(redemptions_.size() == 1,
108✔
142
                   "multiple redemption cash flows given");
143
        return redemptions_.back();
108✔
144
    }
145

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

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

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

161
    Date Bond::settlementDate(Date d) const {
2,581,578✔
162
        if (d==Date())
2,581,578✔
163
            d = Settings::instance().evaluationDate();
2,581,578✔
164

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

174
    Real Bond::cleanPrice() const {
850,480✔
175
        return dirtyPrice() - accruedAmount(settlementDate());
850,480✔
176
    }
177

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

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

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

198
    Rate Bond::yield(const DayCounter& dc,
15✔
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(settlementDate());
15✔
206
        if (currentNotional == 0.0)
15✔
207
            return 0.0;
208

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

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

217
    Real Bond::cleanPrice(Rate y,
1✔
218
                          const DayCounter& dc,
219
                          Compounding comp,
220
                          Frequency freq,
221
                          Date settlement) const {
222
        return BondFunctions::cleanPrice(*this, y, dc, comp, freq, settlement);
1✔
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(settlement);
×
231
        if (currentNotional == 0.0)
×
232
            return 0.0;
233

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

UNCOV
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 {
UNCOV
247
        return yield({price, priceType}, dc, comp, freq, settlement, accuracy,
×
UNCOV
248
                     maxEvaluations, guess);
×
249
    }
250
    Rate Bond::yield(Bond::Price price,
6✔
251
                     const DayCounter& dc,
252
                     Compounding comp,
253
                     Frequency freq,
254
                     Date settlement,
255
                     Real accuracy,
256
                     Size maxEvaluations,
257
                     Real guess) const {
258
        Real currentNotional = notional(settlement);
6✔
259
        if (currentNotional == 0.0)
6✔
260
            return 0.0;
261

262
        return BondFunctions::yield(*this, price, dc, comp, freq,
6✔
263
                                    settlement, accuracy, maxEvaluations,
264
                                    guess);
6✔
265
    }
266

267
    Real Bond::accruedAmount(Date settlement) const {
862,551✔
268
        Real currentNotional = notional(settlement);
862,551✔
269
        if (currentNotional == 0.0)
862,551✔
270
            return 0.0;
271

272
        return BondFunctions::accruedAmount(*this, settlement);
862,551✔
273
    }
274

275
    Rate Bond::nextCouponRate(Date settlement) const {
4✔
276
        return BondFunctions::nextCouponRate(*this, settlement);
4✔
277
    }
278

279
    Rate Bond::previousCouponRate(Date settlement) const {
2✔
280
        return BondFunctions::previousCouponRate(*this, settlement);
2✔
281
    }
282

283
    Date Bond::nextCashFlowDate(Date settlement) const {
115✔
284
        return BondFunctions::nextCashFlowDate(*this, settlement);
115✔
285
    }
286

UNCOV
287
    Date Bond::previousCashFlowDate(Date settlement) const {
×
UNCOV
288
        return BondFunctions::previousCashFlowDate(*this, settlement);
×
289
    }
290

UNCOV
291
    void Bond::setupExpired() const {
×
UNCOV
292
        Instrument::setupExpired();
×
UNCOV
293
        settlementValue_ = 0.0;
×
UNCOV
294
    }
×
295

296
    void Bond::setupArguments(PricingEngine::arguments* args) const {
850,501✔
297
        auto* arguments = dynamic_cast<Bond::arguments*>(args);
850,501✔
298
        QL_REQUIRE(arguments != nullptr, "wrong argument type");
850,501✔
299

300
        arguments->settlementDate = settlementDate();
850,501✔
301
        arguments->cashflows = cashflows_;
850,501✔
302
        arguments->calendar = calendar_;
303
    }
850,501✔
304

305
    void Bond::fetchResults(const PricingEngine::results* r) const {
850,506✔
306

307
        Instrument::fetchResults(r);
850,506✔
308

309
        const auto* results = dynamic_cast<const Bond::results*>(r);
850,506✔
310
        QL_ENSURE(results != nullptr, "wrong result type");
850,506✔
311

312
        settlementValue_ = results->settlementValue;
850,506✔
313
    }
850,506✔
314

315
    void Bond::addRedemptionsToCashflows(const std::vector<Real>& redemptions) {
1,677✔
316
        // First, we gather the notional information from the cashflows
317
        calculateNotionalsFromCashflows();
1,677✔
318
        // Then, we create the redemptions based on the notional
319
        // information and we add them to the cashflows vector after
320
        // the coupons.
321
        redemptions_.clear();
1,677✔
322
        for (Size i=1; i<notionalSchedule_.size(); ++i) {
8,086✔
323
            Real R = i < redemptions.size() ? redemptions[i] :
6,409✔
324
                     !redemptions.empty()   ? redemptions.back() :
6,409✔
325
                                              100.0;
326
            Real amount = (R/100.0)*(notionals_[i-1]-notionals_[i]);
6,409✔
327
            ext::shared_ptr<CashFlow> payment;
6,409✔
328
            if (i < notionalSchedule_.size()-1)
6,409✔
329
                payment.reset(new AmortizingPayment(amount,
4,732✔
330
                                                    notionalSchedule_[i]));
4,732✔
331
            else
332
                payment.reset(new Redemption(amount, notionalSchedule_[i]));
1,677✔
333
            cashflows_.push_back(payment);
6,409✔
334
            redemptions_.push_back(payment);
6,409✔
335
        }
336
        // stable_sort now moves the redemptions to the right places
337
        // while ensuring that they follow coupons with the same date.
338
        std::stable_sort(cashflows_.begin(), cashflows_.end(),
1,677✔
339
                         earlier_than<ext::shared_ptr<CashFlow> >());
340
    }
1,677✔
341

342
    void Bond::setSingleRedemption(Real notional,
28✔
343
                                   Real redemption,
344
                                   const Date& date) {
345

346
        ext::shared_ptr<CashFlow> redemptionCashflow(
347
                         new Redemption(notional*redemption/100.0, date));
28✔
348
        setSingleRedemption(notional, redemptionCashflow);
28✔
349
    }
28✔
350

351
    void Bond::setSingleRedemption(Real notional,
28✔
352
                                   const ext::shared_ptr<CashFlow>& redemption) {
353
        notionals_.resize(2);
28✔
354
        notionalSchedule_.resize(2);
28✔
355
        redemptions_.clear();
28✔
356

357
        notionalSchedule_[0] = Date();
28✔
358
        notionals_[0] = notional;
28✔
359

360
        notionalSchedule_[1] = redemption->date();
28✔
361
        notionals_[1] = 0.0;
28✔
362

363
        cashflows_.push_back(redemption);
28✔
364
        redemptions_.push_back(redemption);
28✔
365
    }
28✔
366

UNCOV
367
    void Bond::deepUpdate() {
×
UNCOV
368
        for (auto& cashflow : cashflows_) {
×
UNCOV
369
            cashflow->deepUpdate();
×
370
        }
UNCOV
371
        update();
×
UNCOV
372
    }
×
373

374
    void Bond::calculateNotionalsFromCashflows() {
1,679✔
375
        notionalSchedule_.clear();
1,679✔
376
        notionals_.clear();
1,679✔
377

378
        Date lastPaymentDate = Date();
1,679✔
379
        notionalSchedule_.emplace_back();
1,679✔
380
        for (auto& cashflow : cashflows_) {
33,264✔
381
            ext::shared_ptr<Coupon> coupon = ext::dynamic_pointer_cast<Coupon>(cashflow);
31,585✔
382
            if (!coupon)
31,585✔
383
                continue;
384

385
            Real notional = coupon->nominal();
31,583✔
386
            // we add the notional only if it is the first one...
387
            if (notionals_.empty()) {
31,583✔
388
                notionals_.push_back(coupon->nominal());
1,679✔
389
                lastPaymentDate = coupon->date();
1,679✔
390
            } else if (!close(notional, notionals_.back())) {
29,904✔
391
                // ...or if it has changed.
392
                notionals_.push_back(coupon->nominal());
4,732✔
393
                // in this case, we also add the last valid date for
394
                // the previous one...
395
                notionalSchedule_.push_back(lastPaymentDate);
4,732✔
396
                // ...and store the candidate for this one.
397
                lastPaymentDate = coupon->date();
4,732✔
398
            } else {
399
                // otherwise, we just extend the valid range of dates
400
                // for the current notional.
401
                lastPaymentDate = coupon->date();
25,172✔
402
            }
403
        }
404
        QL_REQUIRE(!notionals_.empty(), "no coupons provided");
1,679✔
405
        notionals_.push_back(0.0);
1,679✔
406
        notionalSchedule_.push_back(lastPaymentDate);
1,679✔
407
    }
1,679✔
408

409

410
    void Bond::arguments::validate() const {
850,447✔
411
        QL_REQUIRE(settlementDate != Date(), "no settlement date provided");
850,447✔
412
        QL_REQUIRE(!cashflows.empty(), "no cash flow provided");
850,447✔
413
        for (const auto & cf: cashflows)
14,814,575✔
414
            QL_REQUIRE(cf, "null cash flow provided");
13,964,128✔
415
    }
850,447✔
416

417
}
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