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

lballabio / QuantLib / 23752055041

30 Mar 2026 03:10PM UTC coverage: 74.431% (+0.01%) from 74.417%
23752055041

push

github

web-flow
Implement fair spread for `FloatFloatSwap` (#2411)

20 of 32 new or added lines in 1 file covered. (62.5%)

1 existing line in 1 file now uncovered.

58257 of 78270 relevant lines covered (74.43%)

8779091.04 hits per line

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

60.58
/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/termstructures/yieldtermstructure.hpp>
32
#include <ql/optional.hpp>
33
#include <utility>
34

35
namespace QuantLib {
36

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

74
        init(paymentConvention1, paymentConvention2);
39✔
75
    }
39✔
76

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

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

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

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

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

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

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

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

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

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

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

247
        if (ibor1 != nullptr) {
39✔
248
            IborLeg leg(schedule1_, ibor1);
76✔
249
            leg = leg.withNotionals(nominal1_)
38✔
250
                      .withPaymentDayCounter(dayCount1_)
38✔
251
                      .withPaymentAdjustment(paymentConvention1_)
38✔
252
                      .withSpreads(spread1_)
38✔
253
                      .withGearings(gearing1_);
38✔
254
            if (cappedRate1_[0] != Null<Real>())
38✔
255
                leg = leg.withCaps(cappedRate1_);
×
256
            if (flooredRate1_[0] != Null<Real>())
38✔
257
                leg = leg.withFloors(flooredRate1_);
×
258
            legs_[0] = leg;
76✔
259
        }
38✔
260

261
        if (ibor2 != nullptr) {
39✔
262
            IborLeg leg(schedule2_, ibor2);
78✔
263
            leg = leg.withNotionals(nominal2_)
39✔
264
                      .withPaymentDayCounter(dayCount2_)
39✔
265
                      .withPaymentAdjustment(paymentConvention2_)
39✔
266
                      .withSpreads(spread2_)
39✔
267
                      .withGearings(gearing2_);
39✔
268
            if (cappedRate2_[0] != Null<Real>())
39✔
269
                leg = leg.withCaps(cappedRate2_);
×
270
            if (flooredRate2_[0] != Null<Real>())
39✔
271
                leg = leg.withFloors(flooredRate2_);
×
272
            legs_[1] = leg;
78✔
273
        }
39✔
274

275
        if (cms1 != nullptr) {
39✔
276
            CmsLeg leg(schedule1_, cms1);
2✔
277
            leg = leg.withNotionals(nominal1_)
1✔
278
                      .withPaymentDayCounter(dayCount1_)
1✔
279
                      .withPaymentAdjustment(paymentConvention1_)
1✔
280
                      .withSpreads(spread1_)
1✔
281
                      .withGearings(gearing1_);
1✔
282
            if (cappedRate1_[0] != Null<Real>())
1✔
283
                leg = leg.withCaps(cappedRate1_);
×
284
            if (flooredRate1_[0] != Null<Real>())
1✔
285
                leg = leg.withFloors(flooredRate1_);
×
286
            legs_[0] = leg;
2✔
287
        }
1✔
288

289
        if (cms2 != nullptr) {
39✔
290
            CmsLeg leg(schedule2_, cms2);
×
291
            leg = leg.withNotionals(nominal2_)
×
292
                      .withPaymentDayCounter(dayCount2_)
×
293
                      .withPaymentAdjustment(paymentConvention2_)
×
294
                      .withSpreads(spread2_)
×
295
                      .withGearings(gearing2_);
×
296
            if (cappedRate2_[0] != Null<Real>())
×
297
                leg = leg.withCaps(cappedRate2_);
×
298
            if (flooredRate2_[0] != Null<Real>())
×
299
                leg = leg.withFloors(flooredRate2_);
×
300
            legs_[1] = leg;
×
301
        }
×
302

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

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

331
        if (intermediateCapitalExchange_) {
39✔
332
            for (Size i = 0; i < legs_[0].size() - 1; i++) {
×
333
                Real cap = nominal1_[i] - nominal1_[i + 1];
×
334
                if (!close(cap, 0.0)) {
×
335
                    auto it1 = legs_[0].begin();
×
336
                    std::advance(it1, i + 1);
337
                    legs_[0].insert(
×
338
                        it1, ext::shared_ptr<CashFlow>(
×
339
                                 new Redemption(cap, legs_[0][i]->date())));
×
340
                    auto it2 = nominal1_.begin();
×
341
                    std::advance(it2, i + 1);
342
                    nominal1_.insert(it2, nominal1_[i]);
×
343
                    i++;
344
                }
345
            }
346
            for (Size i = 0; i < legs_[1].size() - 1; i++) {
×
347
                Real cap = nominal2_[i] - nominal2_[i + 1];
×
348
                if (!close(cap, 0.0)) {
×
349
                    auto it1 = legs_[1].begin();
×
350
                    std::advance(it1, i + 1);
351
                    legs_[1].insert(
×
352
                        it1, ext::shared_ptr<CashFlow>(
×
353
                                 new Redemption(cap, legs_[1][i]->date())));
×
354
                    auto it2 = nominal2_.begin();
×
355
                    std::advance(it2, i + 1);
356
                    nominal2_.insert(it2, nominal2_[i]);
×
357
                    i++;
358
                }
359
            }
360
        }
361

362
        if (finalCapitalExchange_) {
39✔
363
            legs_[0].push_back(ext::shared_ptr<CashFlow>(
×
364
                new Redemption(nominal1_.back(), legs_[0].back()->date())));
×
365
            nominal1_.push_back(nominal1_.back());
×
366
            legs_[1].push_back(ext::shared_ptr<CashFlow>(
×
367
                new Redemption(nominal2_.back(), legs_[1].back()->date())));
×
368
            nominal2_.push_back(nominal2_.back());
×
369
        }
370

371
        for (auto i = legs_[0].begin(); i < legs_[0].end(); ++i)
1,569✔
372
            registerWith(*i);
3,060✔
373

374
        for (auto i = legs_[1].begin(); i < legs_[1].end(); ++i)
819✔
375
            registerWith(*i);
1,560✔
376

377
        switch (type_) {
39✔
378
        case Swap::Payer:
20✔
379
            payer_[0] = -1.0;
20✔
380
            payer_[1] = +1.0;
20✔
381
            break;
20✔
382
        case Swap::Receiver:
19✔
383
            payer_[0] = +1.0;
19✔
384
            payer_[1] = -1.0;
19✔
385
            break;
19✔
386
        default:
×
387
            QL_FAIL("Unknown float float - swap type");
×
388
        }
389
    }
39✔
390

391
    void FloatFloatSwap::setupArguments(PricingEngine::arguments *args) const {
43✔
392

393
        Swap::setupArguments(args);
43✔
394

395
        auto* arguments = dynamic_cast<FloatFloatSwap::arguments*>(args);
43✔
396

397
        if (arguments == nullptr)
43✔
398
            return; // swap engine ... // QL_REQUIRE(arguments != 0, "argument type does not match");
399

400
        arguments->type = type_;
4✔
401
        arguments->nominal1 = nominal1_;
4✔
402
        arguments->nominal2 = nominal2_;
4✔
403
        arguments->index1 = index1_;
4✔
404
        arguments->index2 = index2_;
4✔
405

406
        const Leg &leg1Coupons = leg1();
407
        const Leg &leg2Coupons = leg2();
408

409
        arguments->leg1ResetDates = arguments->leg1PayDates =
410
            arguments->leg1FixingDates = std::vector<Date>(leg1Coupons.size());
8✔
411
        arguments->leg2ResetDates = arguments->leg2PayDates =
412
            arguments->leg2FixingDates = std::vector<Date>(leg2Coupons.size());
8✔
413

414
        arguments->leg1Spreads = arguments->leg1AccrualTimes =
415
            arguments->leg1Gearings = std::vector<Real>(leg1Coupons.size());
8✔
416
        arguments->leg2Spreads = arguments->leg2AccrualTimes =
417
            arguments->leg2Gearings = std::vector<Real>(leg2Coupons.size());
4✔
418

419
        arguments->leg1Coupons =
420
            std::vector<Real>(leg1Coupons.size(), Null<Real>());
4✔
421
        arguments->leg2Coupons =
422
            std::vector<Real>(leg2Coupons.size(), Null<Real>());
4✔
423

424
        arguments->leg1IsRedemptionFlow =
425
            std::vector<bool>(leg1Coupons.size(), false);
4✔
426
        arguments->leg2IsRedemptionFlow =
427
            std::vector<bool>(leg2Coupons.size(), false);
4✔
428

429
        arguments->leg1CappedRates = arguments->leg1FlooredRates =
430
            std::vector<Real>(leg1Coupons.size(), Null<Real>());
4✔
431
        arguments->leg2CappedRates = arguments->leg2FlooredRates =
432
            std::vector<Real>(leg2Coupons.size(), Null<Real>());
4✔
433

434
        for (Size i = 0; i < leg1Coupons.size(); ++i) {
44✔
435
            ext::shared_ptr<FloatingRateCoupon> coupon =
436
                ext::dynamic_pointer_cast<FloatingRateCoupon>(leg1Coupons[i]);
40✔
437
            if (coupon != nullptr) {
40✔
438
                arguments->leg1AccrualTimes[i] = coupon->accrualPeriod();
40✔
439
                arguments->leg1PayDates[i] = coupon->date();
40✔
440
                arguments->leg1ResetDates[i] = coupon->accrualStartDate();
40✔
441
                arguments->leg1FixingDates[i] = coupon->fixingDate();
40✔
442
                arguments->leg1Spreads[i] = coupon->spread();
40✔
443
                arguments->leg1Gearings[i] = coupon->gearing();
40✔
444
                try {
445
                    arguments->leg1Coupons[i] = coupon->amount();
40✔
446
                }
447
                catch (Error &) {
×
448
                    arguments->leg1Coupons[i] = Null<Real>();
×
449
                }
×
450
                ext::shared_ptr<CappedFlooredCoupon> cfcoupon =
451
                    ext::dynamic_pointer_cast<CappedFlooredCoupon>(
452
                        leg1Coupons[i]);
40✔
453
                if (cfcoupon != nullptr) {
40✔
454
                    arguments->leg1CappedRates[i] = cfcoupon->cap();
×
455
                    arguments->leg1FlooredRates[i] = cfcoupon->floor();
×
456
                }
457
            } else {
458
                ext::shared_ptr<CashFlow> cashflow =
459
                    ext::dynamic_pointer_cast<CashFlow>(leg1Coupons[i]);
×
460
                auto j =
461
                    std::find(arguments->leg1PayDates.begin(),
×
462
                              arguments->leg1PayDates.end(), cashflow->date());
×
463
                QL_REQUIRE(j != arguments->leg1PayDates.end(),
×
464
                           "nominal redemption on "
465
                               << cashflow->date()
466
                               << "has no corresponding coupon");
467
                Size jIdx = j - arguments->leg1PayDates.begin();
468
                arguments->leg1IsRedemptionFlow[i] = true;
469
                arguments->leg1Coupons[i] = cashflow->amount();
×
470
                arguments->leg1ResetDates[i] = arguments->leg1ResetDates[jIdx];
×
471
                arguments->leg1FixingDates[i] =
×
472
                    arguments->leg1FixingDates[jIdx];
473
                arguments->leg1AccrualTimes[i] = 0.0;
×
474
                arguments->leg1Spreads[i] = 0.0;
×
475
                arguments->leg1Gearings[i] = 1.0;
×
476
                arguments->leg1PayDates[i] = cashflow->date();
×
477
            }
478
        }
479

480
        for (Size i = 0; i < leg2Coupons.size(); ++i) {
84✔
481
            ext::shared_ptr<FloatingRateCoupon> coupon =
482
                ext::dynamic_pointer_cast<FloatingRateCoupon>(leg2Coupons[i]);
80✔
483
            if (coupon != nullptr) {
80✔
484
                arguments->leg2AccrualTimes[i] = coupon->accrualPeriod();
80✔
485
                arguments->leg2PayDates[i] = coupon->date();
80✔
486
                arguments->leg2ResetDates[i] = coupon->accrualStartDate();
80✔
487
                arguments->leg2FixingDates[i] = coupon->fixingDate();
80✔
488
                arguments->leg2Spreads[i] = coupon->spread();
80✔
489
                arguments->leg2Gearings[i] = coupon->gearing();
80✔
490
                try {
491
                    arguments->leg2Coupons[i] = coupon->amount();
80✔
492
                }
493
                catch (Error &) {
×
494
                    arguments->leg2Coupons[i] = Null<Real>();
×
495
                }
×
496
                ext::shared_ptr<CappedFlooredCoupon> cfcoupon =
497
                    ext::dynamic_pointer_cast<CappedFlooredCoupon>(
498
                        leg2Coupons[i]);
80✔
499
                if (cfcoupon != nullptr) {
80✔
500
                    arguments->leg2CappedRates[i] = cfcoupon->cap();
×
501
                    arguments->leg2FlooredRates[i] = cfcoupon->floor();
×
502
                }
503
            } else {
504
                ext::shared_ptr<CashFlow> cashflow =
505
                    ext::dynamic_pointer_cast<CashFlow>(leg2Coupons[i]);
×
506
                auto j =
507
                    std::find(arguments->leg2PayDates.begin(),
×
508
                              arguments->leg2PayDates.end(), cashflow->date());
×
509
                QL_REQUIRE(j != arguments->leg2PayDates.end(),
×
510
                           "nominal redemption on "
511
                               << cashflow->date()
512
                               << "has no corresponding coupon");
513
                Size jIdx = j - arguments->leg2PayDates.begin();
514
                arguments->leg2IsRedemptionFlow[i] = true;
515
                arguments->leg2Coupons[i] = cashflow->amount();
×
516
                arguments->leg2ResetDates[i] = arguments->leg2ResetDates[jIdx];
×
517
                arguments->leg2FixingDates[i] =
×
518
                    arguments->leg2FixingDates[jIdx];
519
                arguments->leg2AccrualTimes[i] = 0.0;
×
520
                arguments->leg2Spreads[i] = 0.0;
×
521
                arguments->leg2Gearings[i] = 1.0;
×
522
                arguments->leg2PayDates[i] = cashflow->date();
×
523
            }
524
        }
525
    }
526

527
    Spread FloatFloatSwap::fairSpread1() const {
10✔
528
        calculate();
10✔
529
        QL_REQUIRE(fairSpread1_ != Null<Spread>(), "fair spread 1 not available");
10✔
530
        return fairSpread1_;
10✔
531
    }
532

533
    Spread FloatFloatSwap::fairSpread2() const {
10✔
534
        calculate();
10✔
535
        QL_REQUIRE(fairSpread2_ != Null<Spread>(), "fair spread 2 not available");
10✔
536
        return fairSpread2_;
10✔
537
    }
538

NEW
539
    void FloatFloatSwap::setupExpired() const {
×
NEW
540
        Swap::setupExpired();
×
NEW
541
        fairSpread1_ = Null<Spread>();
×
NEW
542
        fairSpread2_ = Null<Spread>();
×
NEW
543
    }
×
544

545
    void FloatFloatSwap::fetchResults(const PricingEngine::results *r) const {
39✔
546
        static const Spread basisPoint = 1.0e-4;
547

548
        Swap::fetchResults(r);
39✔
549

550
        const auto* results = dynamic_cast<const FloatFloatSwap::results*>(r);
39✔
551
        if (results != nullptr) {
39✔
NEW
552
            fairSpread1_ = results->fairSpread1;
×
NEW
553
            fairSpread2_ = results->fairSpread2;
×
554
        } else {
555
            fairSpread1_ = Null<Spread>();
39✔
556
            fairSpread2_ = Null<Spread>();
39✔
557
        }
558

559
        if (fairSpread1_ == Null<Spread>()) {
39✔
560
            if (legBPS_.size() > 0 && legBPS_[0] != Null<Real>()) {
39✔
561
                Real currentSpread = spread1_.empty() ? 0.0 : spread1_[0];
39✔
562
                fairSpread1_ = currentSpread - NPV_/(legBPS_[0]/basisPoint);
39✔
563
            }
564
        }
565
        if (fairSpread2_ == Null<Spread>()) {
39✔
566
            if (legBPS_.size() > 1 && legBPS_[1] != Null<Real>()) {
39✔
567
                Real currentSpread = spread2_.empty() ? 0.0 : spread2_[0];
39✔
568
                fairSpread2_ = currentSpread - NPV_/(legBPS_[1]/basisPoint);
39✔
569
            }
570
        }
571
    }
39✔
572

573
    void FloatFloatSwap::arguments::validate() const {
4✔
574

575
        Swap::arguments::validate();
4✔
576

577
        QL_REQUIRE(nominal1.size() == leg1ResetDates.size(),
4✔
578
                   "nominal1 size is different from resetDates1 size");
579
        QL_REQUIRE(nominal1.size() == leg1FixingDates.size(),
4✔
580
                   "nominal1 size is different from fixingDates1 size");
581
        QL_REQUIRE(nominal1.size() == leg1PayDates.size(),
4✔
582
                   "nominal1 size is different from payDates1 size");
583
        QL_REQUIRE(nominal1.size() == leg1Spreads.size(),
4✔
584
                   "nominal1 size is different from spreads1 size");
585
        QL_REQUIRE(nominal1.size() == leg1Gearings.size(),
4✔
586
                   "nominal1 size is different from gearings1 size");
587
        QL_REQUIRE(nominal1.size() == leg1CappedRates.size(),
4✔
588
                   "nominal1 size is different from cappedRates1 size");
589
        QL_REQUIRE(nominal1.size() == leg1FlooredRates.size(),
4✔
590
                   "nominal1 size is different from flooredRates1 size");
591
        QL_REQUIRE(nominal1.size() == leg1Coupons.size(),
4✔
592
                   "nominal1 size is different from coupons1 size");
593
        QL_REQUIRE(nominal1.size() == leg1AccrualTimes.size(),
4✔
594
                   "nominal1 size is different from accrualTimes1 size");
595
        QL_REQUIRE(nominal1.size() == leg1IsRedemptionFlow.size(),
4✔
596
                   "nominal1 size is different from redemption1 size");
597

598
        QL_REQUIRE(nominal2.size() == leg2ResetDates.size(),
4✔
599
                   "nominal2 size is different from resetDates2 size");
600
        QL_REQUIRE(nominal2.size() == leg2FixingDates.size(),
4✔
601
                   "nominal2 size is different from fixingDates2 size");
602
        QL_REQUIRE(nominal2.size() == leg2PayDates.size(),
4✔
603
                   "nominal2 size is different from payDates2 size");
604
        QL_REQUIRE(nominal2.size() == leg2Spreads.size(),
4✔
605
                   "nominal2 size is different from spreads2 size");
606
        QL_REQUIRE(nominal2.size() == leg2Gearings.size(),
4✔
607
                   "nominal2 size is different from gearings2 size");
608
        QL_REQUIRE(nominal2.size() == leg2CappedRates.size(),
4✔
609
                   "nominal2 size is different from cappedRates2 size");
610
        QL_REQUIRE(nominal2.size() == leg2FlooredRates.size(),
4✔
611
                   "nominal2 size is different from flooredRates2 size");
612
        QL_REQUIRE(nominal2.size() == leg2Coupons.size(),
4✔
613
                   "nominal2 size is different from coupons2 size");
614
        QL_REQUIRE(nominal2.size() == leg2AccrualTimes.size(),
4✔
615
                   "nominal2 size is different from accrualTimes2 size");
616
        QL_REQUIRE(nominal2.size() == leg2IsRedemptionFlow.size(),
4✔
617
                   "nominal2 size is different from redemption2 size");
618

619
        QL_REQUIRE(index1 != nullptr, "index1 is null");
4✔
620
        QL_REQUIRE(index2 != nullptr, "index2 is null");
4✔
621
    }
4✔
622

NEW
623
    void FloatFloatSwap::results::reset() {
×
NEW
624
        Swap::results::reset();
×
NEW
625
        fairSpread1 = Null<Spread>();
×
NEW
626
        fairSpread2 = Null<Spread>();
×
NEW
627
    }
×
628
}
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