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

lballabio / QuantLib / 29168448220

11 Jul 2026 09:14PM UTC coverage: 74.922% (+0.04%) from 74.885%
29168448220

Pull #2655

github

web-flow
Merge dfb8e8baa into 4567efac4
Pull Request #2655: RFR swaption support in Gsr

129 of 144 new or added lines in 10 files covered. (89.58%)

2 existing lines in 2 files now uncovered.

59622 of 79579 relevant lines covered (74.92%)

8597070.72 hits per line

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

60.98
/ql/instruments/floatfloatswap.cpp
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2

3
/*
4
 Copyright (C) 2013 Peter Caspers
5

6
 This file is part of QuantLib, a free-software/open-source library
7
 for financial quantitative analysts and developers - http://quantlib.org/
8

9
 QuantLib is free software: you can redistribute it and/or modify it
10
 under the terms of the QuantLib license.  You should have received a
11
 copy of the license along with this program; if not, please email
12
 <quantlib-dev@lists.sf.net>. The license is also available online at
13
 <https://www.quantlib.org/license.shtml>.
14

15
 This program is distributed in the hope that it will be useful, but WITHOUT
16
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 FOR A PARTICULAR PURPOSE.  See the license for more details.
18
*/
19

20
#include <ql/cashflows/capflooredcoupon.hpp>
21
#include <ql/cashflows/cashflows.hpp>
22
#include <ql/cashflows/cashflowvectors.hpp>
23
#include <ql/cashflows/cmscoupon.hpp>
24
#include <ql/cashflows/couponpricer.hpp>
25
#include <ql/cashflows/iborcoupon.hpp>
26
#include <ql/cashflows/simplecashflow.hpp>
27
#include <ql/experimental/coupons/cmsspreadcoupon.hpp> // internal
28
#include <ql/indexes/iborindex.hpp>
29
#include <ql/indexes/swapindex.hpp>
30
#include <ql/instruments/floatfloatswap.hpp>
31
#include <ql/cashflows/overnightindexedcoupon.hpp>
32
#include <ql/termstructures/yieldtermstructure.hpp>
33
#include <ql/optional.hpp>
34
#include <utility>
35

36
namespace QuantLib {
37

38
    FloatFloatSwap::FloatFloatSwap(const Swap::Type type,
42✔
39
                                   const Real nominal1,
40
                                   const Real nominal2,
41
                                   Schedule schedule1,
42
                                   ext::shared_ptr<InterestRateIndex> index1,
43
                                   DayCounter dayCount1,
44
                                   Schedule schedule2,
45
                                   ext::shared_ptr<InterestRateIndex> index2,
46
                                   DayCounter dayCount2,
47
                                   const bool intermediateCapitalExchange,
48
                                   const bool finalCapitalExchange,
49
                                   const Real gearing1,
50
                                   const Real spread1,
51
                                   const Real cappedRate1,
52
                                   const Real flooredRate1,
53
                                   const Real gearing2,
54
                                   const Real spread2,
55
                                   const Real cappedRate2,
56
                                   const Real flooredRate2,
57
                                   const ext::optional<BusinessDayConvention>& paymentConvention1,
58
                                   const ext::optional<BusinessDayConvention>& paymentConvention2)
42✔
59
    : Swap(2), type_(type), nominal1_(std::vector<Real>(schedule1.size() - 1, nominal1)),
84✔
60
      nominal2_(std::vector<Real>(schedule2.size() - 1, nominal2)),
42✔
61
      schedule1_(std::move(schedule1)), schedule2_(std::move(schedule2)),
42✔
62
      index1_(std::move(index1)), index2_(std::move(index2)),
63
      gearing1_(std::vector<Real>(schedule1_.size() - 1, gearing1)),
42✔
64
      gearing2_(std::vector<Real>(schedule2_.size() - 1, gearing2)),
42✔
65
      spread1_(std::vector<Real>(schedule1_.size() - 1, spread1)),
42✔
66
      spread2_(std::vector<Real>(schedule2_.size() - 1, spread2)),
42✔
67
      cappedRate1_(std::vector<Real>(schedule1_.size() - 1, cappedRate1)),
42✔
68
      flooredRate1_(std::vector<Real>(schedule1_.size() - 1, flooredRate1)),
42✔
69
      cappedRate2_(std::vector<Real>(schedule2_.size() - 1, cappedRate2)),
42✔
70
      flooredRate2_(std::vector<Real>(schedule2_.size() - 1, flooredRate2)),
42✔
71
      dayCount1_(std::move(dayCount1)), dayCount2_(std::move(dayCount2)),
72
      intermediateCapitalExchange_(intermediateCapitalExchange),
42✔
73
      finalCapitalExchange_(finalCapitalExchange) {
42✔
74

75
        init(paymentConvention1, paymentConvention2);
42✔
76
    }
42✔
77

78
    FloatFloatSwap::FloatFloatSwap(const Swap::Type type,
×
79
                                   std::vector<Real> nominal1,
80
                                   std::vector<Real> nominal2,
81
                                   Schedule schedule1,
82
                                   ext::shared_ptr<InterestRateIndex> index1,
83
                                   DayCounter dayCount1,
84
                                   Schedule schedule2,
85
                                   ext::shared_ptr<InterestRateIndex> index2,
86
                                   DayCounter dayCount2,
87
                                   const bool intermediateCapitalExchange,
88
                                   const bool finalCapitalExchange,
89
                                   std::vector<Real> gearing1,
90
                                   std::vector<Real> spread1,
91
                                   std::vector<Real> cappedRate1,
92
                                   std::vector<Real> flooredRate1,
93
                                   std::vector<Real> gearing2,
94
                                   std::vector<Real> spread2,
95
                                   std::vector<Real> cappedRate2,
96
                                   std::vector<Real> flooredRate2,
97
                                   const ext::optional<BusinessDayConvention>& paymentConvention1,
98
                                   const ext::optional<BusinessDayConvention>& paymentConvention2)
×
99
    : Swap(2), type_(type), nominal1_(std::move(nominal1)), nominal2_(std::move(nominal2)),
×
100
      schedule1_(std::move(schedule1)), schedule2_(std::move(schedule2)),
×
101
      index1_(std::move(index1)), index2_(std::move(index2)), gearing1_(std::move(gearing1)),
102
      gearing2_(std::move(gearing2)), spread1_(std::move(spread1)), spread2_(std::move(spread2)),
103
      cappedRate1_(std::move(cappedRate1)), flooredRate1_(std::move(flooredRate1)),
104
      cappedRate2_(std::move(cappedRate2)), flooredRate2_(std::move(flooredRate2)),
105
      dayCount1_(std::move(dayCount1)), dayCount2_(std::move(dayCount2)),
106
      intermediateCapitalExchange_(intermediateCapitalExchange),
×
107
      finalCapitalExchange_(finalCapitalExchange) {
×
108

109
        init(paymentConvention1, paymentConvention2);
×
110
    }
×
111

112
    void FloatFloatSwap::init(
42✔
113
        ext::optional<BusinessDayConvention> paymentConvention1,
114
        ext::optional<BusinessDayConvention> paymentConvention2) {
115

116
        QL_REQUIRE(nominal1_.size() == schedule1_.size() - 1,
42✔
117
                   "nominal1 size (" << nominal1_.size()
118
                                     << ") does not match schedule1 size ("
119
                                     << schedule1_.size() << ")");
120
        QL_REQUIRE(nominal2_.size() == schedule2_.size() - 1,
42✔
121
                   "nominal2 size (" << nominal2_.size()
122
                                     << ") does not match schedule2 size ("
123
                                     << nominal2_.size() << ")");
124
        QL_REQUIRE(gearing1_.empty() || gearing1_.size() == nominal1_.size(),
42✔
125
                   "nominal1 size (" << nominal1_.size() << ") does not match gearing1 size ("
126
                                     << gearing1_.size() << ")");
127
        QL_REQUIRE(gearing2_.empty() || gearing2_.size() == nominal2_.size(),
42✔
128
                   "nominal2 size (" << nominal2_.size() << ") does not match gearing2 size ("
129
                                     << gearing2_.size() << ")");
130
        QL_REQUIRE(cappedRate1_.empty() || cappedRate1_.size() == nominal1_.size(),
42✔
131
                   "nominal1 size (" << nominal1_.size() << ") does not match cappedRate1 size ("
132
                                     << cappedRate1_.size() << ")");
133
        QL_REQUIRE(cappedRate2_.empty() || cappedRate2_.size() == nominal2_.size(),
42✔
134
                   "nominal2 size (" << nominal2_.size() << ") does not match cappedRate2 size ("
135
                                     << cappedRate2_.size() << ")");
136
        QL_REQUIRE(flooredRate1_.empty() || flooredRate1_.size() == nominal1_.size(),
42✔
137
                   "nominal1 size (" << nominal1_.size() << ") does not match flooredRate1 size ("
138
                                     << flooredRate1_.size() << ")");
139
        QL_REQUIRE(flooredRate2_.empty() || flooredRate2_.size() == nominal2_.size(),
42✔
140
                   "nominal2 size (" << nominal2_.size() << ") does not match flooredRate2 size ("
141
                                     << flooredRate2_.size() << ")");
142

143
        if (paymentConvention1) // NOLINT(readability-implicit-bool-conversion)
42✔
144
            paymentConvention1_ = *paymentConvention1;
×
145
        else
146
            paymentConvention1_ = schedule1_.businessDayConvention();
42✔
147

148
        if (paymentConvention2) // NOLINT(readability-implicit-bool-conversion)
42✔
149
            paymentConvention2_ = *paymentConvention2;
×
150
        else
151
            paymentConvention2_ = schedule2_.businessDayConvention();
42✔
152

153
        if (gearing1_.empty())
42✔
154
            gearing1_ = std::vector<Real>(nominal1_.size(), 1.0);
×
155
        if (gearing2_.empty())
42✔
156
            gearing2_ = std::vector<Real>(nominal2_.size(), 1.0);
×
157
        if (spread1_.empty())
42✔
158
            spread1_ = std::vector<Real>(nominal1_.size(), 0.0);
×
159
        if (spread2_.empty())
42✔
160
            spread2_ = std::vector<Real>(nominal2_.size(), 0.0);
×
161
        if (cappedRate1_.empty())
42✔
162
            cappedRate1_ = std::vector<Real>(nominal1_.size(), Null<Real>());
×
163
        if (cappedRate2_.empty())
42✔
164
            cappedRate2_ = std::vector<Real>(nominal2_.size(), Null<Real>());
×
165
        if (flooredRate1_.empty())
42✔
166
            flooredRate1_ = std::vector<Real>(nominal1_.size(), Null<Real>());
×
167
        if (flooredRate2_.empty())
42✔
168
            flooredRate2_ = std::vector<Real>(nominal2_.size(), Null<Real>());
×
169

170
        bool isNull;
171
        isNull = cappedRate1_[0] == Null<Real>();
42✔
172
        for (Size i = 0; i < cappedRate1_.size(); i++) {
1,593✔
173
            if (isNull)
1,551✔
174
                QL_REQUIRE(cappedRate1_[i] == Null<Real>(),
1,551✔
175
                           "cappedRate1 must be null for all or none entry ("
176
                               << (i + 1) << "th is " << cappedRate1_[i]
177
                               << ")");
178
            else
179
                QL_REQUIRE(cappedRate1_[i] != Null<Real>(),
×
180
                           "cappedRate 1 must be null for all or none entry ("
181
                               << "1st is " << cappedRate1_[0] << ")");
182
        }
183
        isNull = cappedRate2_[0] == Null<Real>();
42✔
184
        for (Size i = 0; i < cappedRate2_.size(); i++) {
843✔
185
            if (isNull)
801✔
186
                QL_REQUIRE(cappedRate2_[i] == Null<Real>(),
801✔
187
                           "cappedRate2 must be null for all or none entry ("
188
                               << (i + 1) << "th is " << cappedRate2_[i]
189
                               << ")");
190
            else
191
                QL_REQUIRE(cappedRate2_[i] != Null<Real>(),
×
192
                           "cappedRate2 must be null for all or none entry ("
193
                               << "1st is " << cappedRate2_[0] << ")");
194
        }
195
        isNull = flooredRate1_[0] == Null<Real>();
42✔
196
        for (Size i = 0; i < flooredRate1_.size(); i++) {
1,593✔
197
            if (isNull)
1,551✔
198
                QL_REQUIRE(flooredRate1_[i] == Null<Real>(),
1,551✔
199
                           "flooredRate1 must be null for all or none entry ("
200
                               << (i + 1) << "th is " << flooredRate1_[i]
201
                               << ")");
202
            else
203
                QL_REQUIRE(flooredRate1_[i] != Null<Real>(),
×
204
                           "flooredRate 1 must be null for all or none entry ("
205
                               << "1st is " << flooredRate1_[0] << ")");
206
        }
207
        isNull = flooredRate2_[0] == Null<Real>();
42✔
208
        for (Size i = 0; i < flooredRate2_.size(); i++) {
843✔
209
            if (isNull)
801✔
210
                QL_REQUIRE(flooredRate2_[i] == Null<Real>(),
801✔
211
                           "flooredRate2 must be null for all or none entry ("
212
                               << (i + 1) << "th is " << flooredRate2_[i]
213
                               << ")");
214
            else
215
                QL_REQUIRE(flooredRate2_[i] != Null<Real>(),
×
216
                           "flooredRate2 must be null for all or none entry ("
217
                               << "1st is " << flooredRate2_[0] << ")");
218
        }
219

220
        // if the gearing is zero then the ibor / cms leg will be set up with
221
        // fixed coupons which makes trouble here in this context. We therefore
222
        // use a dirty trick and enforce the gearing to be non zero.
223
        for (Real& i : gearing1_)
1,593✔
224
            if (close(i, 0.0))
1,551✔
225
                i = QL_EPSILON;
×
226
        for (Real& i : gearing2_)
843✔
227
            if (close(i, 0.0))
801✔
228
                i = QL_EPSILON;
×
229

230
        ext::shared_ptr<IborIndex> ibor1 =
231
            ext::dynamic_pointer_cast<IborIndex>(index1_);
42✔
232
        ext::shared_ptr<IborIndex> ibor2 =
233
            ext::dynamic_pointer_cast<IborIndex>(index2_);
42✔
234
        ext::shared_ptr<SwapIndex> cms1 =
235
            ext::dynamic_pointer_cast<SwapIndex>(index1_);
42✔
236
        ext::shared_ptr<SwapIndex> cms2 =
237
            ext::dynamic_pointer_cast<SwapIndex>(index2_);
42✔
238
        ext::shared_ptr<SwapSpreadIndex> cmsspread1 =
239
            ext::dynamic_pointer_cast<SwapSpreadIndex>(index1_);
42✔
240
        ext::shared_ptr<SwapSpreadIndex> cmsspread2 =
241
            ext::dynamic_pointer_cast<SwapSpreadIndex>(index2_);
42✔
242

243
        QL_REQUIRE(ibor1 != nullptr || cms1 != nullptr || cmsspread1 != nullptr,
42✔
244
                   "index1 must be ibor or cms or cms spread");
245
        QL_REQUIRE(ibor2 != nullptr || cms2 != nullptr || cmsspread2 != nullptr,
42✔
246
                   "index2 must be ibor or cms");
247

248
        auto overnight1 = ext::dynamic_pointer_cast<OvernightIndex>(ibor1);
42✔
249
        if (overnight1 != nullptr) {
42✔
250
            // compounded-in-arrears overnight (RFR) leg; capped / floored
251
            // compounded coupons are not wired through the pricing arguments
252
            // (CappedFlooredOvernightIndexedCoupon does not derive from
253
            // CappedFlooredCoupon), so they are rejected rather than dropped
254
            QL_REQUIRE(cappedRate1_[0] == Null<Real>() &&
3✔
255
                           flooredRate1_[0] == Null<Real>(),
256
                       "capped/floored compounded overnight legs are not supported");
257
            legs_[0] = OvernightLeg(schedule1_, overnight1)
6✔
258
                           .withNotionals(nominal1_)
3✔
259
                           .withPaymentDayCounter(dayCount1_)
3✔
260
                           .withPaymentAdjustment(paymentConvention1_)
3✔
261
                           .withSpreads(spread1_)
3✔
262
                           .withGearings(gearing1_);
6✔
263
        } else if (ibor1 != nullptr) {
39✔
264
            IborLeg leg(schedule1_, ibor1);
76✔
265
            leg = leg.withNotionals(nominal1_)
38✔
266
                      .withPaymentDayCounter(dayCount1_)
38✔
267
                      .withPaymentAdjustment(paymentConvention1_)
38✔
268
                      .withSpreads(spread1_)
38✔
269
                      .withGearings(gearing1_);
38✔
270
            if (cappedRate1_[0] != Null<Real>())
38✔
271
                leg = leg.withCaps(cappedRate1_);
×
272
            if (flooredRate1_[0] != Null<Real>())
38✔
273
                leg = leg.withFloors(flooredRate1_);
×
274
            legs_[0] = leg;
76✔
275
        }
38✔
276

277
        auto overnight2 = ext::dynamic_pointer_cast<OvernightIndex>(ibor2);
42✔
278
        if (overnight2 != nullptr) {
42✔
NEW
279
            QL_REQUIRE(cappedRate2_[0] == Null<Real>() &&
×
280
                           flooredRate2_[0] == Null<Real>(),
281
                       "capped/floored compounded overnight legs are not supported");
NEW
282
            legs_[1] = OvernightLeg(schedule2_, overnight2)
×
NEW
283
                           .withNotionals(nominal2_)
×
NEW
284
                           .withPaymentDayCounter(dayCount2_)
×
NEW
285
                           .withPaymentAdjustment(paymentConvention2_)
×
NEW
286
                           .withSpreads(spread2_)
×
NEW
287
                           .withGearings(gearing2_);
×
288
        } else if (ibor2 != nullptr) {
42✔
289
            IborLeg leg(schedule2_, ibor2);
84✔
290
            leg = leg.withNotionals(nominal2_)
42✔
291
                      .withPaymentDayCounter(dayCount2_)
42✔
292
                      .withPaymentAdjustment(paymentConvention2_)
42✔
293
                      .withSpreads(spread2_)
42✔
294
                      .withGearings(gearing2_);
42✔
295
            if (cappedRate2_[0] != Null<Real>())
42✔
296
                leg = leg.withCaps(cappedRate2_);
×
297
            if (flooredRate2_[0] != Null<Real>())
42✔
298
                leg = leg.withFloors(flooredRate2_);
×
299
            legs_[1] = leg;
84✔
300
        }
42✔
301

302
        if (cms1 != nullptr) {
42✔
303
            CmsLeg leg(schedule1_, cms1);
2✔
304
            leg = leg.withNotionals(nominal1_)
1✔
305
                      .withPaymentDayCounter(dayCount1_)
1✔
306
                      .withPaymentAdjustment(paymentConvention1_)
1✔
307
                      .withSpreads(spread1_)
1✔
308
                      .withGearings(gearing1_);
1✔
309
            if (cappedRate1_[0] != Null<Real>())
1✔
310
                leg = leg.withCaps(cappedRate1_);
×
311
            if (flooredRate1_[0] != Null<Real>())
1✔
312
                leg = leg.withFloors(flooredRate1_);
×
313
            legs_[0] = leg;
2✔
314
        }
1✔
315

316
        if (cms2 != nullptr) {
42✔
317
            CmsLeg leg(schedule2_, cms2);
×
318
            leg = leg.withNotionals(nominal2_)
×
319
                      .withPaymentDayCounter(dayCount2_)
×
320
                      .withPaymentAdjustment(paymentConvention2_)
×
321
                      .withSpreads(spread2_)
×
322
                      .withGearings(gearing2_);
×
323
            if (cappedRate2_[0] != Null<Real>())
×
324
                leg = leg.withCaps(cappedRate2_);
×
325
            if (flooredRate2_[0] != Null<Real>())
×
326
                leg = leg.withFloors(flooredRate2_);
×
327
            legs_[1] = leg;
×
328
        }
×
329

330
        if (cmsspread1 != nullptr) {
42✔
331
            CmsSpreadLeg leg(schedule1_, cmsspread1);
×
332
            leg = leg.withNotionals(nominal1_)
×
333
                      .withPaymentDayCounter(dayCount1_)
×
334
                      .withPaymentAdjustment(paymentConvention1_)
×
335
                      .withSpreads(spread1_)
×
336
                      .withGearings(gearing1_);
×
337
            if (cappedRate1_[0] != Null<Real>())
×
338
                leg = leg.withCaps(cappedRate1_);
×
339
            if (flooredRate1_[0] != Null<Real>())
×
340
                leg = leg.withFloors(flooredRate1_);
×
341
            legs_[0] = leg;
×
342
        }
×
343

344
        if (cmsspread2 != nullptr) {
42✔
345
            CmsSpreadLeg leg(schedule2_, cmsspread2);
×
346
            leg = leg.withNotionals(nominal2_)
×
347
                      .withPaymentDayCounter(dayCount2_)
×
348
                      .withPaymentAdjustment(paymentConvention2_)
×
349
                      .withSpreads(spread2_)
×
350
                      .withGearings(gearing2_);
×
351
            if (cappedRate2_[0] != Null<Real>())
×
352
                leg = leg.withCaps(cappedRate2_);
×
353
            if (flooredRate2_[0] != Null<Real>())
×
354
                leg = leg.withFloors(flooredRate2_);
×
355
            legs_[1] = leg;
×
356
        }
×
357

358
        if (intermediateCapitalExchange_) {
42✔
359
            for (Size i = 0; i < legs_[0].size() - 1; i++) {
×
360
                Real cap = nominal1_[i] - nominal1_[i + 1];
×
361
                if (!close(cap, 0.0)) {
×
362
                    auto it1 = legs_[0].begin();
×
363
                    std::advance(it1, i + 1);
364
                    legs_[0].insert(
×
365
                        it1, ext::shared_ptr<CashFlow>(
×
366
                                 new Redemption(cap, legs_[0][i]->date())));
×
367
                    auto it2 = nominal1_.begin();
×
368
                    std::advance(it2, i + 1);
369
                    nominal1_.insert(it2, nominal1_[i]);
×
370
                    i++;
371
                }
372
            }
373
            for (Size i = 0; i < legs_[1].size() - 1; i++) {
×
374
                Real cap = nominal2_[i] - nominal2_[i + 1];
×
375
                if (!close(cap, 0.0)) {
×
376
                    auto it1 = legs_[1].begin();
×
377
                    std::advance(it1, i + 1);
378
                    legs_[1].insert(
×
379
                        it1, ext::shared_ptr<CashFlow>(
×
380
                                 new Redemption(cap, legs_[1][i]->date())));
×
381
                    auto it2 = nominal2_.begin();
×
382
                    std::advance(it2, i + 1);
383
                    nominal2_.insert(it2, nominal2_[i]);
×
384
                    i++;
385
                }
386
            }
387
        }
388

389
        if (finalCapitalExchange_) {
42✔
390
            legs_[0].push_back(ext::shared_ptr<CashFlow>(
×
391
                new Redemption(nominal1_.back(), legs_[0].back()->date())));
×
392
            nominal1_.push_back(nominal1_.back());
×
393
            legs_[1].push_back(ext::shared_ptr<CashFlow>(
×
394
                new Redemption(nominal2_.back(), legs_[1].back()->date())));
×
395
            nominal2_.push_back(nominal2_.back());
×
396
        }
397

398
        for (auto i = legs_[0].begin(); i < legs_[0].end(); ++i)
1,593✔
399
            registerWith(*i);
3,102✔
400

401
        for (auto i = legs_[1].begin(); i < legs_[1].end(); ++i)
843✔
402
            registerWith(*i);
1,602✔
403

404
        switch (type_) {
42✔
405
        case Swap::Payer:
23✔
406
            payer_[0] = -1.0;
23✔
407
            payer_[1] = +1.0;
23✔
408
            break;
23✔
409
        case Swap::Receiver:
19✔
410
            payer_[0] = +1.0;
19✔
411
            payer_[1] = -1.0;
19✔
412
            break;
19✔
413
        default:
×
414
            QL_FAIL("Unknown float float - swap type");
×
415
        }
416
    }
42✔
417

418
    void FloatFloatSwap::setupArguments(PricingEngine::arguments *args) const {
46✔
419

420
        Swap::setupArguments(args);
46✔
421

422
        auto* arguments = dynamic_cast<FloatFloatSwap::arguments*>(args);
46✔
423

424
        if (arguments == nullptr)
46✔
425
            return; // swap engine ... // QL_REQUIRE(arguments != 0, "argument type does not match");
426

427
        arguments->type = type_;
7✔
428
        arguments->nominal1 = nominal1_;
7✔
429
        arguments->nominal2 = nominal2_;
7✔
430
        arguments->index1 = index1_;
7✔
431
        arguments->index2 = index2_;
7✔
432

433
        const Leg &leg1Coupons = leg1();
434
        const Leg &leg2Coupons = leg2();
435

436
        arguments->leg1ResetDates = arguments->leg1PayDates =
437
            arguments->leg1FixingDates = std::vector<Date>(leg1Coupons.size());
14✔
438
        arguments->leg2ResetDates = arguments->leg2PayDates =
439
            arguments->leg2FixingDates = std::vector<Date>(leg2Coupons.size());
14✔
440

441
        arguments->leg1Spreads = arguments->leg1AccrualTimes =
442
            arguments->leg1Gearings = std::vector<Real>(leg1Coupons.size());
14✔
443
        arguments->leg2Spreads = arguments->leg2AccrualTimes =
444
            arguments->leg2Gearings = std::vector<Real>(leg2Coupons.size());
7✔
445

446
        arguments->leg1Coupons =
447
            std::vector<Real>(leg1Coupons.size(), Null<Real>());
7✔
448
        arguments->leg2Coupons =
449
            std::vector<Real>(leg2Coupons.size(), Null<Real>());
7✔
450

451
        arguments->leg1IsRedemptionFlow =
452
            std::vector<bool>(leg1Coupons.size(), false);
7✔
453
        arguments->leg2IsRedemptionFlow =
454
            std::vector<bool>(leg2Coupons.size(), false);
7✔
455

456
        arguments->leg1CappedRates = arguments->leg1FlooredRates =
457
            std::vector<Real>(leg1Coupons.size(), Null<Real>());
7✔
458
        arguments->leg2CappedRates = arguments->leg2FlooredRates =
459
            std::vector<Real>(leg2Coupons.size(), Null<Real>());
7✔
460

461
        for (Size i = 0; i < leg1Coupons.size(); ++i) {
68✔
462
            ext::shared_ptr<FloatingRateCoupon> coupon =
463
                ext::dynamic_pointer_cast<FloatingRateCoupon>(leg1Coupons[i]);
61✔
464
            if (coupon != nullptr) {
61✔
465
                arguments->leg1AccrualTimes[i] = coupon->accrualPeriod();
61✔
466
                arguments->leg1PayDates[i] = coupon->date();
61✔
467
                arguments->leg1ResetDates[i] = coupon->accrualStartDate();
61✔
468
                // compounded overnight coupons fix in arrears (fixingDate() is
469
                // the last observation date); the engine must schedule their
470
                // valuation at the accrual start, where the telescoped
471
                // zerobond-ratio estimate is a function of the model state
472
                auto onCoupon1 =
473
                    ext::dynamic_pointer_cast<OvernightIndexedCoupon>(leg1Coupons[i]);
61✔
474
                arguments->leg1FixingDates[i] = onCoupon1 != nullptr
82✔
475
                    ? onCoupon1->accrualStartDate()
21✔
476
                    : coupon->fixingDate();
40✔
477
                arguments->leg1Spreads[i] = coupon->spread();
61✔
478
                arguments->leg1Gearings[i] = coupon->gearing();
61✔
479
                try {
480
                    arguments->leg1Coupons[i] = coupon->amount();
61✔
481
                }
482
                catch (Error &) {
×
483
                    arguments->leg1Coupons[i] = Null<Real>();
×
484
                }
×
485
                ext::shared_ptr<CappedFlooredCoupon> cfcoupon =
486
                    ext::dynamic_pointer_cast<CappedFlooredCoupon>(
487
                        leg1Coupons[i]);
61✔
488
                if (cfcoupon != nullptr) {
61✔
489
                    arguments->leg1CappedRates[i] = cfcoupon->cap();
×
490
                    arguments->leg1FlooredRates[i] = cfcoupon->floor();
×
491
                }
492
            } else {
493
                ext::shared_ptr<CashFlow> cashflow =
494
                    ext::dynamic_pointer_cast<CashFlow>(leg1Coupons[i]);
×
495
                auto j =
496
                    std::find(arguments->leg1PayDates.begin(),
×
497
                              arguments->leg1PayDates.end(), cashflow->date());
×
498
                QL_REQUIRE(j != arguments->leg1PayDates.end(),
×
499
                           "nominal redemption on "
500
                               << cashflow->date()
501
                               << "has no corresponding coupon");
502
                Size jIdx = j - arguments->leg1PayDates.begin();
503
                arguments->leg1IsRedemptionFlow[i] = true;
504
                arguments->leg1Coupons[i] = cashflow->amount();
×
505
                arguments->leg1ResetDates[i] = arguments->leg1ResetDates[jIdx];
×
506
                arguments->leg1FixingDates[i] =
×
507
                    arguments->leg1FixingDates[jIdx];
508
                arguments->leg1AccrualTimes[i] = 0.0;
×
509
                arguments->leg1Spreads[i] = 0.0;
×
510
                arguments->leg1Gearings[i] = 1.0;
×
511
                arguments->leg1PayDates[i] = cashflow->date();
×
512
            }
513
        }
514

515
        for (Size i = 0; i < leg2Coupons.size(); ++i) {
108✔
516
            ext::shared_ptr<FloatingRateCoupon> coupon =
517
                ext::dynamic_pointer_cast<FloatingRateCoupon>(leg2Coupons[i]);
101✔
518
            if (coupon != nullptr) {
101✔
519
                arguments->leg2AccrualTimes[i] = coupon->accrualPeriod();
101✔
520
                arguments->leg2PayDates[i] = coupon->date();
101✔
521
                arguments->leg2ResetDates[i] = coupon->accrualStartDate();
101✔
522
                // compounded overnight coupons fix in arrears (fixingDate() is
523
                // the last observation date); the engine must schedule their
524
                // valuation at the accrual start, where the telescoped
525
                // zerobond-ratio estimate is a function of the model state
526
                auto onCoupon2 =
527
                    ext::dynamic_pointer_cast<OvernightIndexedCoupon>(leg2Coupons[i]);
101✔
528
                arguments->leg2FixingDates[i] = onCoupon2 != nullptr
101✔
NEW
529
                    ? onCoupon2->accrualStartDate()
×
530
                    : coupon->fixingDate();
101✔
531
                arguments->leg2Spreads[i] = coupon->spread();
101✔
532
                arguments->leg2Gearings[i] = coupon->gearing();
101✔
533
                try {
534
                    arguments->leg2Coupons[i] = coupon->amount();
101✔
535
                }
536
                catch (Error &) {
×
537
                    arguments->leg2Coupons[i] = Null<Real>();
×
538
                }
×
539
                ext::shared_ptr<CappedFlooredCoupon> cfcoupon =
540
                    ext::dynamic_pointer_cast<CappedFlooredCoupon>(
541
                        leg2Coupons[i]);
101✔
542
                if (cfcoupon != nullptr) {
101✔
543
                    arguments->leg2CappedRates[i] = cfcoupon->cap();
×
544
                    arguments->leg2FlooredRates[i] = cfcoupon->floor();
×
545
                }
546
            } else {
547
                ext::shared_ptr<CashFlow> cashflow =
548
                    ext::dynamic_pointer_cast<CashFlow>(leg2Coupons[i]);
×
549
                auto j =
550
                    std::find(arguments->leg2PayDates.begin(),
×
551
                              arguments->leg2PayDates.end(), cashflow->date());
×
552
                QL_REQUIRE(j != arguments->leg2PayDates.end(),
×
553
                           "nominal redemption on "
554
                               << cashflow->date()
555
                               << "has no corresponding coupon");
556
                Size jIdx = j - arguments->leg2PayDates.begin();
557
                arguments->leg2IsRedemptionFlow[i] = true;
558
                arguments->leg2Coupons[i] = cashflow->amount();
×
559
                arguments->leg2ResetDates[i] = arguments->leg2ResetDates[jIdx];
×
560
                arguments->leg2FixingDates[i] =
×
561
                    arguments->leg2FixingDates[jIdx];
562
                arguments->leg2AccrualTimes[i] = 0.0;
×
563
                arguments->leg2Spreads[i] = 0.0;
×
564
                arguments->leg2Gearings[i] = 1.0;
×
565
                arguments->leg2PayDates[i] = cashflow->date();
×
566
            }
567
        }
568
    }
569

570
    Spread FloatFloatSwap::fairSpread1() const {
10✔
571
        calculate();
10✔
572
        QL_REQUIRE(fairSpread1_ != Null<Spread>(), "fair spread 1 not available");
10✔
573
        return fairSpread1_;
10✔
574
    }
575

576
    Spread FloatFloatSwap::fairSpread2() const {
10✔
577
        calculate();
10✔
578
        QL_REQUIRE(fairSpread2_ != Null<Spread>(), "fair spread 2 not available");
10✔
579
        return fairSpread2_;
10✔
580
    }
581

582
    void FloatFloatSwap::setupExpired() const {
×
583
        Swap::setupExpired();
×
584
        fairSpread1_ = Null<Spread>();
×
585
        fairSpread2_ = Null<Spread>();
×
586
    }
×
587

588
    void FloatFloatSwap::fetchResults(const PricingEngine::results *r) const {
39✔
589
        static const Spread basisPoint = 1.0e-4;
590

591
        Swap::fetchResults(r);
39✔
592

593
        const auto* results = dynamic_cast<const FloatFloatSwap::results*>(r);
39✔
594
        if (results != nullptr) {
39✔
595
            fairSpread1_ = results->fairSpread1;
×
596
            fairSpread2_ = results->fairSpread2;
×
597
        } else {
598
            fairSpread1_ = Null<Spread>();
39✔
599
            fairSpread2_ = Null<Spread>();
39✔
600
        }
601

602
        if (fairSpread1_ == Null<Spread>()) {
39✔
603
            if (!legBPS_.empty() && legBPS_[0] != Null<Real>()) {
39✔
604
                Real currentSpread = spread1_.empty() ? 0.0 : spread1_[0];
39✔
605
                fairSpread1_ = currentSpread - NPV_/(legBPS_[0]/basisPoint);
39✔
606
            }
607
        }
608
        if (fairSpread2_ == Null<Spread>()) {
39✔
609
            if (legBPS_.size() > 1 && legBPS_[1] != Null<Real>()) {
39✔
610
                Real currentSpread = spread2_.empty() ? 0.0 : spread2_[0];
39✔
611
                fairSpread2_ = currentSpread - NPV_/(legBPS_[1]/basisPoint);
39✔
612
            }
613
        }
614
    }
39✔
615

616
    void FloatFloatSwap::arguments::validate() const {
7✔
617

618
        Swap::arguments::validate();
7✔
619

620
        QL_REQUIRE(nominal1.size() == leg1ResetDates.size(),
7✔
621
                   "nominal1 size is different from resetDates1 size");
622
        QL_REQUIRE(nominal1.size() == leg1FixingDates.size(),
7✔
623
                   "nominal1 size is different from fixingDates1 size");
624
        QL_REQUIRE(nominal1.size() == leg1PayDates.size(),
7✔
625
                   "nominal1 size is different from payDates1 size");
626
        QL_REQUIRE(nominal1.size() == leg1Spreads.size(),
7✔
627
                   "nominal1 size is different from spreads1 size");
628
        QL_REQUIRE(nominal1.size() == leg1Gearings.size(),
7✔
629
                   "nominal1 size is different from gearings1 size");
630
        QL_REQUIRE(nominal1.size() == leg1CappedRates.size(),
7✔
631
                   "nominal1 size is different from cappedRates1 size");
632
        QL_REQUIRE(nominal1.size() == leg1FlooredRates.size(),
7✔
633
                   "nominal1 size is different from flooredRates1 size");
634
        QL_REQUIRE(nominal1.size() == leg1Coupons.size(),
7✔
635
                   "nominal1 size is different from coupons1 size");
636
        QL_REQUIRE(nominal1.size() == leg1AccrualTimes.size(),
7✔
637
                   "nominal1 size is different from accrualTimes1 size");
638
        QL_REQUIRE(nominal1.size() == leg1IsRedemptionFlow.size(),
7✔
639
                   "nominal1 size is different from redemption1 size");
640

641
        QL_REQUIRE(nominal2.size() == leg2ResetDates.size(),
7✔
642
                   "nominal2 size is different from resetDates2 size");
643
        QL_REQUIRE(nominal2.size() == leg2FixingDates.size(),
7✔
644
                   "nominal2 size is different from fixingDates2 size");
645
        QL_REQUIRE(nominal2.size() == leg2PayDates.size(),
7✔
646
                   "nominal2 size is different from payDates2 size");
647
        QL_REQUIRE(nominal2.size() == leg2Spreads.size(),
7✔
648
                   "nominal2 size is different from spreads2 size");
649
        QL_REQUIRE(nominal2.size() == leg2Gearings.size(),
7✔
650
                   "nominal2 size is different from gearings2 size");
651
        QL_REQUIRE(nominal2.size() == leg2CappedRates.size(),
7✔
652
                   "nominal2 size is different from cappedRates2 size");
653
        QL_REQUIRE(nominal2.size() == leg2FlooredRates.size(),
7✔
654
                   "nominal2 size is different from flooredRates2 size");
655
        QL_REQUIRE(nominal2.size() == leg2Coupons.size(),
7✔
656
                   "nominal2 size is different from coupons2 size");
657
        QL_REQUIRE(nominal2.size() == leg2AccrualTimes.size(),
7✔
658
                   "nominal2 size is different from accrualTimes2 size");
659
        QL_REQUIRE(nominal2.size() == leg2IsRedemptionFlow.size(),
7✔
660
                   "nominal2 size is different from redemption2 size");
661

662
        QL_REQUIRE(index1 != nullptr, "index1 is null");
7✔
663
        QL_REQUIRE(index2 != nullptr, "index2 is null");
7✔
664
    }
7✔
665

666
    void FloatFloatSwap::results::reset() {
×
667
        Swap::results::reset();
×
668
        fairSpread1 = Null<Spread>();
×
669
        fairSpread2 = Null<Spread>();
×
670
    }
×
671
}
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