Coverage Report

Created: 2026-06-23 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/cashflows/multipleresetscoupon.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2008 Toyin Akin
5
 Copyright (C) 2021 Marcin Rybacki
6
7
 This file is part of QuantLib, a free-software/open-source library
8
 for financial quantitative analysts and developers - http://quantlib.org/
9
10
 QuantLib is free software: you can redistribute it and/or modify it
11
 under the terms of the QuantLib license.  You should have received a
12
 copy of the license along with this program; if not, please email
13
 <quantlib-dev@lists.sf.net>. The license is also available online at
14
 <https://www.quantlib.org/license.shtml>.
15
16
 This program is distributed in the hope that it will be useful, but WITHOUT
17
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 FOR A PARTICULAR PURPOSE.  See the license for more details.
19
*/
20
21
#include <ql/cashflows/multipleresetscoupon.hpp>
22
#include <ql/cashflows/cashflowvectors.hpp>
23
#include <ql/time/schedule.hpp>
24
#include <ql/indexes/iborindex.hpp>
25
#include <ql/termstructures/yieldtermstructure.hpp>
26
#include <cmath>
27
28
namespace QuantLib {
29
30
    MultipleResetsCoupon::MultipleResetsCoupon(const Date& paymentDate,
31
                                               Real nominal,
32
                                               const Schedule& resetSchedule,
33
                                               Natural fixingDays,
34
                                               const ext::shared_ptr<IborIndex>& index,
35
                                               Real gearing,
36
                                               Rate couponSpread,
37
                                               Rate rateSpread,
38
                                               const Date& refPeriodStart,
39
                                               const Date& refPeriodEnd,
40
                                               const DayCounter& dayCounter,
41
                                               const Date& exCouponDate)
42
0
    : FloatingRateCoupon(paymentDate, nominal,
43
0
                         resetSchedule.front(), resetSchedule.back(),
44
0
                         fixingDays, index, gearing, couponSpread,
45
0
                         refPeriodStart, refPeriodEnd, dayCounter,
46
0
                         false, exCouponDate),
47
0
      rateSpread_(rateSpread) {
48
0
        valueDates_ = resetSchedule.dates();
49
50
        // fixing dates
51
0
        n_ = valueDates_.size() - 1;
52
0
        if (fixingDays_ == 0) {
53
0
            fixingDates_ = std::vector<Date>(valueDates_.begin(), valueDates_.end() - 1);
54
0
        } else {
55
0
            fixingDates_.resize(n_);
56
0
            for (Size i = 0; i < n_; ++i)
57
0
                fixingDates_[i] = fixingDate(valueDates_[i]);
58
0
        }
59
60
        // accrual times of sub-periods
61
0
        dt_.resize(n_);
62
0
        const DayCounter& dc = index->dayCounter();
63
0
        for (Size i = 0; i < n_; ++i)
64
0
            dt_[i] = dc.yearFraction(valueDates_[i], valueDates_[i + 1]);
65
0
    }
Unexecuted instantiation: QuantLib::MultipleResetsCoupon::MultipleResetsCoupon(QuantLib::Date const&, double, QuantLib::Schedule const&, unsigned int, boost::shared_ptr<QuantLib::IborIndex> const&, double, double, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::DayCounter const&, QuantLib::Date const&)
Unexecuted instantiation: QuantLib::MultipleResetsCoupon::MultipleResetsCoupon(QuantLib::Date const&, double, QuantLib::Schedule const&, unsigned int, boost::shared_ptr<QuantLib::IborIndex> const&, double, double, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::DayCounter const&, QuantLib::Date const&)
66
67
0
    void MultipleResetsCoupon::accept(AcyclicVisitor& v) {
68
0
        auto* v1 = dynamic_cast<Visitor<MultipleResetsCoupon>*>(&v);
69
0
        if (v1 != nullptr)
70
0
            v1->visit(*this);
71
0
        else
72
0
            FloatingRateCoupon::accept(v);
73
0
    }
74
75
0
    Date MultipleResetsCoupon::fixingDate(const Date& valueDate) const {
76
0
        Date fixingDate =
77
0
            index_->fixingCalendar().advance(valueDate, -static_cast<Integer>(fixingDays_), Days);
78
0
        return fixingDate;
79
0
    }
80
81
0
    void MultipleResetsPricer::initialize(const FloatingRateCoupon& coupon) {
82
0
        coupon_ = dynamic_cast<const MultipleResetsCoupon*>(&coupon);
83
0
        QL_REQUIRE(coupon_, "sub-periods coupon required");
84
85
0
        ext::shared_ptr<IborIndex> index = ext::dynamic_pointer_cast<IborIndex>(coupon_->index());
86
0
        if (!index) {
87
            // coupon was right, index is not
88
0
            QL_FAIL("IborIndex required");
89
0
        }
90
91
0
        QL_REQUIRE(coupon_->accrualPeriod() != 0.0, "null accrual period");
92
93
0
        const std::vector<Date>& fixingDates = coupon_->fixingDates();
94
0
        Size n = fixingDates.size();
95
0
        subPeriodFixings_.resize(n);
96
97
0
        for (Size i = 0; i < n; i++) {
98
0
            subPeriodFixings_[i] = index->fixing(fixingDates[i]) + coupon_->rateSpread();
99
0
        }
100
0
    }
101
102
0
    Real MultipleResetsPricer::swapletPrice() const {
103
0
        QL_FAIL("MultipleResetsPricer::swapletPrice not implemented");
104
0
    }
105
106
0
    Real MultipleResetsPricer::capletPrice(Rate) const {
107
0
        QL_FAIL("MultipleResetsPricer::capletPrice not implemented");
108
0
    }
109
110
0
    Rate MultipleResetsPricer::capletRate(Rate) const {
111
0
        QL_FAIL("MultipleResetsPricer::capletRate not implemented");
112
0
    }
113
114
0
    Real MultipleResetsPricer::floorletPrice(Rate) const {
115
0
        QL_FAIL("MultipleResetsPricer::floorletPrice not implemented");
116
0
    }
117
118
0
    Rate MultipleResetsPricer::floorletRate(Rate) const {
119
0
        QL_FAIL("MultipleResetsPricer::floorletRate not implemented");
120
0
    }
121
122
0
    Real AveragingMultipleResetsPricer::swapletRate() const {
123
        // past or future fixing is managed in InterestRateIndex::fixing()
124
125
0
        Size nCount = subPeriodFixings_.size();
126
0
        const std::vector<Time>& subPeriodFractions = coupon_->dt();
127
0
        Real aggregateFactor = 0.0;
128
0
        for (Size i = 0; i < nCount; i++) {
129
0
            aggregateFactor += subPeriodFixings_[i] * subPeriodFractions[i];
130
0
        }
131
132
0
        Real rate = aggregateFactor / coupon_->accrualPeriod();
133
0
        return coupon_->gearing() * rate + coupon_->spread();
134
0
    }
135
136
0
    Real CompoundingMultipleResetsPricer::swapletRate() const {
137
        // past or future fixing is managed in InterestRateIndex::fixing()
138
139
0
        Real compoundFactor = 1.0;
140
0
        const std::vector<Time>& subPeriodFractions = coupon_->dt();
141
0
        Size nCount = subPeriodFixings_.size();
142
0
        for (Size i = 0; i < nCount; i++) {
143
0
            compoundFactor *= (1.0 + subPeriodFixings_[i] * subPeriodFractions[i]);
144
0
        }
145
146
0
        Real rate = (compoundFactor - 1.0) / coupon_->accrualPeriod();
147
0
        return coupon_->gearing() * rate + coupon_->spread();
148
0
    }
149
150
151
152
    MultipleResetsLeg::MultipleResetsLeg(Schedule schedule,
153
                                         ext::shared_ptr<IborIndex> index,
154
                                         Size resetsPerCoupon)
155
0
    : schedule_(std::move(schedule)), index_(std::move(index)), resetsPerCoupon_(resetsPerCoupon),
156
0
      paymentCalendar_(schedule_.calendar()) {
157
0
        QL_REQUIRE(index_, "no index provided");
158
0
        QL_REQUIRE(!schedule_.empty(), "empty schedule provided");
159
0
        QL_REQUIRE((schedule_.size() - 1) % resetsPerCoupon_ == 0,
160
0
                   "number of resets per coupon does not divide exactly number of periods in schedule");
161
0
    }
162
163
0
    MultipleResetsLeg& MultipleResetsLeg::withNotionals(Real notional) {
164
0
        notionals_ = std::vector<Real>(1, notional);
165
0
        return *this;
166
0
    }
167
168
0
    MultipleResetsLeg& MultipleResetsLeg::withNotionals(const std::vector<Real>& notionals) {
169
0
        notionals_ = notionals;
170
0
        return *this;
171
0
    }
172
173
0
    MultipleResetsLeg& MultipleResetsLeg::withPaymentDayCounter(const DayCounter& dc) {
174
0
        paymentDayCounter_ = dc;
175
0
        return *this;
176
0
    }
177
178
0
    MultipleResetsLeg& MultipleResetsLeg::withPaymentAdjustment(BusinessDayConvention convention) {
179
0
        paymentAdjustment_ = convention;
180
0
        return *this;
181
0
    }
182
183
0
    MultipleResetsLeg& MultipleResetsLeg::withPaymentCalendar(const Calendar& cal) {
184
0
        paymentCalendar_ = cal;
185
0
        return *this;
186
0
    }
187
188
0
    MultipleResetsLeg& MultipleResetsLeg::withPaymentLag(Integer lag) {
189
0
        paymentLag_ = lag;
190
0
        return *this;
191
0
    }
192
193
0
    MultipleResetsLeg& MultipleResetsLeg::withFixingDays(Natural fixingDays) {
194
0
        fixingDays_ = std::vector<Natural>(1, fixingDays);
195
0
        return *this;
196
0
    }
197
198
0
    MultipleResetsLeg& MultipleResetsLeg::withFixingDays(const std::vector<Natural>& fixingDays) {
199
0
        fixingDays_ = fixingDays;
200
0
        return *this;
201
0
    }
202
203
0
    MultipleResetsLeg& MultipleResetsLeg::withGearings(Real gearing) {
204
0
        gearings_ = std::vector<Real>(1, gearing);
205
0
        return *this;
206
0
    }
207
208
0
    MultipleResetsLeg& MultipleResetsLeg::withGearings(const std::vector<Real>& gearings) {
209
0
        gearings_ = gearings;
210
0
        return *this;
211
0
    }
212
213
0
    MultipleResetsLeg& MultipleResetsLeg::withCouponSpreads(Spread spread) {
214
0
        couponSpreads_ = std::vector<Spread>(1, spread);
215
0
        return *this;
216
0
    }
217
218
0
    MultipleResetsLeg& MultipleResetsLeg::withCouponSpreads(const std::vector<Spread>& spreads) {
219
0
        couponSpreads_ = spreads;
220
0
        return *this;
221
0
    }
222
223
0
    MultipleResetsLeg& MultipleResetsLeg::withRateSpreads(Spread spread) {
224
0
        rateSpreads_ = std::vector<Spread>(1, spread);
225
0
        return *this;
226
0
    }
227
228
0
    MultipleResetsLeg& MultipleResetsLeg::withRateSpreads(const std::vector<Spread>& spreads) {
229
0
        rateSpreads_ = spreads;
230
0
        return *this;
231
0
    }
232
233
0
    MultipleResetsLeg& MultipleResetsLeg::withAveragingMethod(RateAveraging::Type averagingMethod) {
234
0
        averagingMethod_ = averagingMethod;
235
0
        return *this;
236
0
    }
237
238
    MultipleResetsLeg& MultipleResetsLeg::withExCouponPeriod(const Period& period,
239
                                                     const Calendar& cal,
240
                                                     BusinessDayConvention convention,
241
0
                                                     bool endOfMonth) {
242
0
        exCouponPeriod_ = period;
243
0
        exCouponCalendar_ = cal;
244
0
        exCouponAdjustment_ = convention;
245
0
        exCouponEndOfMonth_ = endOfMonth;
246
0
        return *this;
247
0
    }
248
249
0
    MultipleResetsLeg::operator Leg() const {
250
0
        Leg cashflows;
251
0
        Calendar calendar = schedule_.calendar();
252
253
0
        Size n = (schedule_.size() - 1) / resetsPerCoupon_;
254
0
        QL_REQUIRE(!notionals_.empty(), "no notional given");
255
0
        QL_REQUIRE(notionals_.size() <= n,
256
0
                   "too many nominals (" << notionals_.size() << "), only " << n << " required");
257
0
        QL_REQUIRE(gearings_.size() <= n,
258
0
                   "too many gearings (" << gearings_.size() << "), only " << n << " required");
259
0
        QL_REQUIRE(couponSpreads_.size() <= n,
260
0
                   "too many coupon spreads (" << couponSpreads_.size() << "), only " << n << " required");
261
0
        QL_REQUIRE(rateSpreads_.size() <= n,
262
0
                   "too many rate spreads (" << rateSpreads_.size() << "), only " << n << " required");
263
0
        QL_REQUIRE(fixingDays_.size() <= n,
264
0
                   "too many fixing days (" << fixingDays_.size() << "), only " << n << " required");
265
266
0
        for (Size i = 0; i < n; ++i) {
267
0
            Date start = schedule_.date(i * resetsPerCoupon_);
268
0
            Date end = schedule_.date((i + 1) * resetsPerCoupon_);
269
0
            auto subSchedule = schedule_.after(start).until(end);
270
0
            Date paymentDate = paymentCalendar_.advance(end, paymentLag_, Days, paymentAdjustment_);
271
0
            Date exCouponDate;
272
0
            if (exCouponPeriod_ != Period()) {
273
0
                if (exCouponCalendar_.empty()) {
274
0
                    exCouponDate = calendar.advance(paymentDate, -exCouponPeriod_,
275
0
                                                    exCouponAdjustment_, exCouponEndOfMonth_);
276
0
                } else {
277
0
                    exCouponDate = exCouponCalendar_.advance(
278
0
                        paymentDate, -exCouponPeriod_, exCouponAdjustment_, exCouponEndOfMonth_);
279
0
                }
280
0
            }
281
282
0
            cashflows.push_back(ext::make_shared<MultipleResetsCoupon>(
283
0
                paymentDate, detail::get(notionals_, i, notionals_.back()), subSchedule,
284
0
                detail::get(fixingDays_, i, index_->fixingDays()), index_,
285
0
                detail::get(gearings_, i, 1.0), detail::get(couponSpreads_, i, 0.0),
286
0
                detail::get(rateSpreads_, i, 0.0), start, end, paymentDayCounter_,
287
0
                exCouponDate));
288
0
        }
289
290
0
        switch (averagingMethod_) {
291
0
          case RateAveraging::Simple:
292
0
            setCouponPricer(cashflows, ext::make_shared<AveragingMultipleResetsPricer>());
293
0
            break;
294
0
          case RateAveraging::Compound:
295
0
            setCouponPricer(cashflows, ext::make_shared<CompoundingMultipleResetsPricer>());
296
0
            break;
297
0
          default:
298
0
            QL_FAIL("unknown compounding convention (" << Integer(averagingMethod_) << ")");
299
0
        }
300
0
        return cashflows;
301
0
    }
302
303
}