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

lballabio / QuantLib / 19370422724

14 Nov 2025 04:10PM UTC coverage: 74.313% (+0.02%) from 74.295%
19370422724

Pull #2367

github

web-flow
Merge ae36b13f2 into e3bf39e73
Pull Request #2367: Add CrossCurrencySwapRateHelper for cross-currency swap bootstrapping

84 of 90 new or added lines in 1 file covered. (93.33%)

142 existing lines in 6 files now uncovered.

57061 of 76785 relevant lines covered (74.31%)

8793256.53 hits per line

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

91.89
/ql/experimental/termstructures/crosscurrencyratehelpers.cpp
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2

3
/*
4
 Copyright (C) 2025 Uzair Beg
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/overnightindexedcoupon.hpp>
22
#include <ql/cashflows/iborcoupon.hpp>
23
#include <ql/cashflows/cashflows.hpp>
24
#include <ql/cashflows/simplecashflow.hpp>
25
#include <ql/cashflows/fixedratecoupon.hpp>
26
#include <ql/experimental/termstructures/crosscurrencyratehelpers.hpp>
27
#include <ql/utilities/null_deleter.hpp>
28
#include <utility>
29

30
namespace QuantLib {
31

32
    namespace {
33

34
        Schedule legSchedule(const Date& evaluationDate,
397✔
35
                             const Period& tenor,
36
                             const Period& frequency,
37
                             Natural fixingDays,
38
                             const Calendar& calendar,
39
                             BusinessDayConvention convention,
40
                             bool endOfMonth) {
41
            QL_REQUIRE(tenor >= frequency,
399✔
42
                       "XCCY instrument tenor should not be smaller than coupon frequency.");
43

44
            Date referenceDate = calendar.adjust(evaluationDate);
396✔
45
            Date earliestDate = calendar.advance(referenceDate, fixingDays * Days, convention);
396✔
46
            Date maturity = earliestDate + tenor;
396✔
47
            return MakeSchedule()
396✔
48
                .from(earliestDate)
396✔
49
                .to(maturity)
396✔
50
                .withTenor(frequency)
396✔
51
                .withCalendar(calendar)
396✔
52
                .withConvention(convention)
396✔
53
                .endOfMonth(endOfMonth)
396✔
54
                .backwards();
792✔
55
        }
56

57
        Leg buildFloatingLeg(const Date& evaluationDate,
398✔
58
                         const Period& tenor,
59
                         Natural fixingDays,
60
                         const Calendar& calendar,
61
                         BusinessDayConvention convention,
62
                         bool endOfMonth,
63
                         const ext::shared_ptr<IborIndex>& idx,
64
                         Frequency paymentFrequency,
65
                         Integer paymentLag) {
66
            auto overnightIndex = ext::dynamic_pointer_cast<OvernightIndex>(idx);
398✔
67

68
            Period freqPeriod;
398✔
69
            if (paymentFrequency == NoFrequency) {
398✔
70
                QL_REQUIRE(!overnightIndex, "Require payment frequency for overnight indices.");
356✔
71
                freqPeriod = idx->tenor();
353✔
72
            } else {
73
                freqPeriod = Period(paymentFrequency);
44✔
74
            }
75

76
            Schedule sch = legSchedule(evaluationDate, tenor, freqPeriod, fixingDays, calendar,
77
                                       convention, endOfMonth);
397✔
78
            if (overnightIndex != nullptr) {
396✔
79
                return OvernightLeg(sch, overnightIndex)
44✔
80
                    .withNotionals(1.0)
22✔
81
                    .withPaymentLag(paymentLag);
22✔
82
            }
83
            return IborLeg(sch, idx).withNotionals(1.0).withPaymentLag(paymentLag);
374✔
84
        }
396✔
85

86
        std::pair<Real, Real>
87
        npvbpsConstNotionalLeg(const Leg& leg,
2,626✔
88
                               const Date& initialNotionalExchangeDate,
89
                               const Date& finalNotionalExchangeDate,
90
                               const Handle<YieldTermStructure>& discountCurveHandle) {
91
            const Spread basisPoint = 1.0e-4;
92
            Date refDt = discountCurveHandle->referenceDate();
2,626✔
93
            const YieldTermStructure& discountRef = **discountCurveHandle;
2,626✔
94
            bool includeSettleDtFlows = true;
95
            auto [npv, bps] = CashFlows::npvbps(leg, discountRef, includeSettleDtFlows, refDt, refDt);
2,626✔
96
            // Include NPV of the notional exchange at start and maturity.
97
            npv += (-1.0) * discountRef.discount(initialNotionalExchangeDate);
2,626✔
98
            npv += discountRef.discount(finalNotionalExchangeDate);
2,626✔
99
            bps /= basisPoint;
2,626✔
100
            return { npv, bps };
2,626✔
101
        }
102

103
        class ResettingLegHelper {
104
          public:
105
            explicit ResettingLegHelper(const YieldTermStructure& discountCurve,
106
                                        const YieldTermStructure& foreignCurve)
107
            : discountCurve_(discountCurve), foreignCurve_(foreignCurve) {}
652✔
108
            DiscountFactor discount(const Date& d) const {
109
                return discountCurve_.discount(d);
64,644✔
110
            }
111
            Real notionalAdjustment(const Date& d) const {
64,644✔
112
                return foreignCurve_.discount(d) / discountCurve_.discount(d);
64,644✔
113
            }
114

115
          private:
116
            const YieldTermStructure& discountCurve_;
117
            const YieldTermStructure& foreignCurve_;
118
        };
119

120
        class ResettingLegCalculator : public AcyclicVisitor, public Visitor<Coupon> {
121
          public:
122
            explicit ResettingLegCalculator(const YieldTermStructure& discountCurve,
123
                                            const YieldTermStructure& foreignCurve,
124
                                            Integer paymentLag,
125
                                            Calendar paymentCalendar,
126
                                            BusinessDayConvention convention)
127
            : helper_(discountCurve, foreignCurve), paymentLag_(paymentLag),
652✔
128
              paymentCalendar_(std::move(paymentCalendar)), convention_(convention) {}
652✔
129

130
            void visit(Coupon& c) override {
64,644✔
131
                Date start = c.accrualStartDate();
64,644✔
132
                Date end = c.accrualEndDate();
64,644✔
133
                Time accrual = c.accrualPeriod();
64,644✔
134
                Real adjustedNotional = c.nominal() * helper_.notionalAdjustment(start);
64,644✔
135

136
                DiscountFactor discountStart, discountEnd;
137

138
                if (paymentLag_ == 0) {
64,644✔
139
                    discountStart = helper_.discount(start);
140
                    discountEnd = helper_.discount(end);
141
                } else {
142
                    Date paymentStart =
143
                        paymentCalendar_.advance(start, paymentLag_, Days, convention_);
3,360✔
144
                    Date paymentEnd = paymentCalendar_.advance(end, paymentLag_, Days, convention_);
3,360✔
145
                    discountStart = helper_.discount(paymentStart);
146
                    discountEnd = helper_.discount(paymentEnd);
147
                }
148

149
                // NPV of a resetting coupon consists of a redemption of borrowed amount occurring
150
                // at the end of the accrual period plus the accrued interest, minus the borrowed
151
                // amount at the start of the period. All amounts are corrected by an adjustment
152
                // corresponding to the implied forward exchange rate, which is estimated by
153
                // the ratio of foreign and domestic curves discount factors.
154
                Real npvRedeemedAmount =
155
                    adjustedNotional * discountEnd * (1.0 + c.rate() * accrual);
64,644✔
156
                Real npvBorrowedAmount = -adjustedNotional * discountStart;
64,644✔
157

158
                npv_ += (npvRedeemedAmount + npvBorrowedAmount);
64,644✔
159
                bps_ += adjustedNotional * discountEnd * accrual;
64,644✔
160
            }
64,644✔
161
            Real NPV() const { return npv_; }
652✔
162
            Real BPS() const { return bps_; }
652✔
163

164
          private:
165
            ResettingLegHelper helper_;
166
            Real npv_ = 0.0;
167
            Real bps_ = 0.0;
168
            Integer paymentLag_;
169
            Calendar paymentCalendar_;
170
            BusinessDayConvention convention_;
171
        };
172

173
        std::pair<Real, Real> npvbpsResettingLeg(const Leg& iborLeg,
652✔
174
                                                 Integer paymentLag,
175
                                                 const Calendar& paymentCalendar,
176
                                                 BusinessDayConvention convention,
177
                                                 const Handle<YieldTermStructure>& discountCurveHandle,
178
                                                 const Handle<YieldTermStructure>& foreignCurveHandle) {
179
            const YieldTermStructure& discountCurveRef = **discountCurveHandle;
652✔
180
            const YieldTermStructure& foreignCurveRef = **foreignCurveHandle;
652✔
181

182
            ResettingLegCalculator calc(discountCurveRef, foreignCurveRef, paymentLag,
183
                                        paymentCalendar, convention);
652✔
184
            for (const auto& i : iborLeg) {
65,296✔
185
                CashFlow& cf = *i;
64,644✔
186
                cf.accept(calc);
64,644✔
187
            }
188
            return { calc.NPV(), calc.BPS() };
652✔
189
        }
652✔
190
    }
191

192
    CrossCurrencyBasisSwapRateHelperBase::CrossCurrencyBasisSwapRateHelperBase(
200✔
193
        const Handle<Quote>& basis,
194
        const Period& tenor,
195
        Natural fixingDays,
196
        Calendar calendar,
197
        BusinessDayConvention convention,
198
        bool endOfMonth,
199
        ext::shared_ptr<IborIndex> baseCurrencyIndex,
200
        ext::shared_ptr<IborIndex> quoteCurrencyIndex,
201
        Handle<YieldTermStructure> collateralCurve,
202
        bool isFxBaseCurrencyCollateralCurrency,
203
        bool isBasisOnFxBaseCurrencyLeg,
204
        Frequency paymentFrequency,
205
        Integer paymentLag)
200✔
206
    : RelativeDateRateHelper(basis), tenor_(tenor), fixingDays_(fixingDays),
200✔
207
      calendar_(std::move(calendar)), convention_(convention), endOfMonth_(endOfMonth),
200✔
208
      baseCcyIdx_(std::move(baseCurrencyIndex)), quoteCcyIdx_(std::move(quoteCurrencyIndex)),
209
      collateralHandle_(std::move(collateralCurve)),
210
      isFxBaseCurrencyCollateralCurrency_(isFxBaseCurrencyCollateralCurrency),
200✔
211
      isBasisOnFxBaseCurrencyLeg_(isBasisOnFxBaseCurrencyLeg),
200✔
212
      paymentFrequency_(paymentFrequency), paymentLag_(paymentLag) {
400✔
213
        registerWith(baseCcyIdx_);
400✔
214
        registerWith(quoteCcyIdx_);
400✔
215
        registerWith(collateralHandle_);
202✔
216
        CrossCurrencyBasisSwapRateHelperBase::initializeDates();
200✔
217
    }
202✔
218

219
    void CrossCurrencyBasisSwapRateHelperBase::initializeDates() {
200✔
220
        baseCcyIborLeg_ = buildFloatingLeg(evaluationDate_, tenor_, fixingDays_, calendar_, convention_,
198✔
221
                                           endOfMonth_, baseCcyIdx_, paymentFrequency_, paymentLag_);
200✔
222
    
223
        quoteCcyIborLeg_ = buildFloatingLeg(evaluationDate_, tenor_, fixingDays_, calendar_,
198✔
224
                                            convention_, endOfMonth_, quoteCcyIdx_, paymentFrequency_, paymentLag_);
198✔
225
    
226
        earliestDate_ = std::min(CashFlows::startDate(baseCcyIborLeg_),
198✔
227
                                 CashFlows::startDate(quoteCcyIborLeg_));
198✔
228
    
229
        maturityDate_ = std::max(CashFlows::maturityDate(baseCcyIborLeg_),
198✔
230
                                 CashFlows::maturityDate(quoteCcyIborLeg_));
198✔
231
    
232
        if (paymentLag_ == 0) {
198✔
233
            initialNotionalExchangeDate_ = earliestDate_;
187✔
234
            finalNotionalExchangeDate_   = maturityDate_;
187✔
235
        } else {
236
            initialNotionalExchangeDate_ = calendar_.advance(earliestDate_, paymentLag_, Days, convention_);
11✔
237
            finalNotionalExchangeDate_   = calendar_.advance(maturityDate_, paymentLag_, Days, convention_);
11✔
238
        }
239
    
240
        Date lastPaymentDate =
241
            std::max(baseCcyIborLeg_.back()->date(),
198✔
242
                     quoteCcyIborLeg_.back()->date());
198✔
243
    
244
        Date last = std::max(maturityDate_, lastPaymentDate);
198✔
245
    
246
        const_cast<Date&>(latestRelevantDate_) = last;
198✔
247
        const_cast<Date&>(latestDate_)        = last;
198✔
248
    }
198✔
249
    
250

251
    const Handle<YieldTermStructure>&
252
    CrossCurrencyBasisSwapRateHelperBase::baseCcyLegDiscountHandle() const {
2,087✔
253
        QL_REQUIRE(!termStructureHandle_.empty(), "term structure not set");
2,087✔
254
        QL_REQUIRE(!collateralHandle_.empty(), "collateral term structure not set");
2,087✔
255
        return isFxBaseCurrencyCollateralCurrency_ ? collateralHandle_ : termStructureHandle_;
2,087✔
256
    }
257

258
    const Handle<YieldTermStructure>&
259
    CrossCurrencyBasisSwapRateHelperBase::quoteCcyLegDiscountHandle() const {
1,809✔
260
        QL_REQUIRE(!termStructureHandle_.empty(), "term structure not set");
1,809✔
261
        QL_REQUIRE(!collateralHandle_.empty(), "collateral term structure not set");
1,809✔
262
        return isFxBaseCurrencyCollateralCurrency_ ? termStructureHandle_ : collateralHandle_;
1,809✔
263
    }
264

265
    void CrossCurrencyBasisSwapRateHelperBase::setTermStructure(YieldTermStructure* t) {
198✔
266
        // do not set the relinkable handle as an observer -
267
        // force recalculation when needed
268
        bool observer = false;
269

270
        ext::shared_ptr<YieldTermStructure> temp(t, null_deleter());
271
        termStructureHandle_.linkTo(temp, observer);
198✔
272

273
        RelativeDateRateHelper::setTermStructure(t);
198✔
274
    }
198✔
275

276
    ConstNotionalCrossCurrencyBasisSwapRateHelper::ConstNotionalCrossCurrencyBasisSwapRateHelper(
122✔
277
        const Handle<Quote>& basis,
278
        const Period& tenor,
279
        Natural fixingDays,
280
        const Calendar& calendar,
281
        BusinessDayConvention convention,
282
        bool endOfMonth,
283
        const ext::shared_ptr<IborIndex>& baseCurrencyIndex,
284
        const ext::shared_ptr<IborIndex>& quoteCurrencyIndex,
285
        const Handle<YieldTermStructure>& collateralCurve,
286
        bool isFxBaseCurrencyCollateralCurrency,
287
        bool isBasisOnFxBaseCurrencyLeg,
288
        Frequency paymentFrequency,
289
        Integer paymentLag)
122✔
290
    : CrossCurrencyBasisSwapRateHelperBase(basis,
291
                                           tenor,
292
                                           fixingDays,
293
                                           calendar,
294
                                           convention,
295
                                           endOfMonth,
296
                                           baseCurrencyIndex,
297
                                           quoteCurrencyIndex,
298
                                           collateralCurve,
299
                                           isFxBaseCurrencyCollateralCurrency,
300
                                           isBasisOnFxBaseCurrencyLeg,
301
                                           paymentFrequency,
302
                                           paymentLag) {}
368✔
303

304
    Real ConstNotionalCrossCurrencyBasisSwapRateHelper::impliedQuote() const {
970✔
305
        auto [npvBaseCcy, bpsBaseCcy] = npvbpsConstNotionalLeg(baseCcyIborLeg_, initialNotionalExchangeDate_, finalNotionalExchangeDate_, baseCcyLegDiscountHandle());
970✔
306
        auto [npvQuoteCcy, bpsQuoteCcy] = npvbpsConstNotionalLeg(quoteCcyIborLeg_, initialNotionalExchangeDate_, finalNotionalExchangeDate_, quoteCcyLegDiscountHandle());
970✔
307
        Real bps = isBasisOnFxBaseCurrencyLeg_ ? -bpsBaseCcy : bpsQuoteCcy;
970✔
308
        return -(npvQuoteCcy - npvBaseCcy) / bps;
970✔
309
    }
310

311
    void ConstNotionalCrossCurrencyBasisSwapRateHelper::accept(AcyclicVisitor& v) {
×
312
        auto* v1 = dynamic_cast<Visitor<ConstNotionalCrossCurrencyBasisSwapRateHelper>*>(&v);
×
313
        if (v1 != nullptr)
×
314
            v1->visit(*this);
×
315
        else
316
            RateHelper::accept(v);
×
317
    }
×
318

319
    MtMCrossCurrencyBasisSwapRateHelper::MtMCrossCurrencyBasisSwapRateHelper(
78✔
320
        const Handle<Quote>& basis,
321
        const Period& tenor,
322
        Natural fixingDays,
323
        const Calendar& calendar,
324
        BusinessDayConvention convention,
325
        bool endOfMonth,
326
        const ext::shared_ptr<IborIndex>& baseCurrencyIndex,
327
        const ext::shared_ptr<IborIndex>& quoteCurrencyIndex,
328
        const Handle<YieldTermStructure>& collateralCurve,
329
        bool isFxBaseCurrencyCollateralCurrency,
330
        bool isBasisOnFxBaseCurrencyLeg,
331
        bool isFxBaseCurrencyLegResettable,
332
        Frequency paymentFrequency,
333
        Integer paymentLag)
78✔
334
    : CrossCurrencyBasisSwapRateHelperBase(basis,
335
                                           tenor,
336
                                           fixingDays,
337
                                           calendar,
338
                                           convention,
339
                                           endOfMonth,
340
                                           baseCurrencyIndex,
341
                                           quoteCurrencyIndex,
342
                                           collateralCurve,
343
                                           isFxBaseCurrencyCollateralCurrency,
344
                                           isBasisOnFxBaseCurrencyLeg,
345
                                           paymentFrequency,
346
                                           paymentLag),
347
      isFxBaseCurrencyLegResettable_(isFxBaseCurrencyLegResettable) {}
236✔
348

349
    Real MtMCrossCurrencyBasisSwapRateHelper::impliedQuote() const {
652✔
350
        Real npvBaseCcy = 0.0, bpsBaseCcy = 0.0;
652✔
351
        Real npvQuoteCcy = 0.0, bpsQuoteCcy = 0.0;
652✔
352
        if (isFxBaseCurrencyLegResettable_) {
652✔
353
            std::tie(npvBaseCcy, bpsBaseCcy) =
354
                npvbpsResettingLeg(baseCcyIborLeg_, paymentLag_, calendar_, convention_,
187✔
355
                                   baseCcyLegDiscountHandle(), quoteCcyLegDiscountHandle());
356
            std::tie(npvQuoteCcy, bpsQuoteCcy) =
357
                npvbpsConstNotionalLeg(quoteCcyIborLeg_, initialNotionalExchangeDate_,
374✔
358
                                       finalNotionalExchangeDate_, quoteCcyLegDiscountHandle());
187✔
359
        } else {
360
            std::tie(npvBaseCcy, bpsBaseCcy) =
361
                npvbpsConstNotionalLeg(baseCcyIborLeg_, initialNotionalExchangeDate_,
465✔
362
                                       finalNotionalExchangeDate_, baseCcyLegDiscountHandle());
465✔
363
            std::tie(npvQuoteCcy, bpsQuoteCcy) = npvbpsResettingLeg(
930✔
364
                                       quoteCcyIborLeg_, paymentLag_, calendar_, convention_,
465✔
365
                                       quoteCcyLegDiscountHandle(), baseCcyLegDiscountHandle());
366
        }
367

368
        Real bps = isBasisOnFxBaseCurrencyLeg_ ? -bpsBaseCcy : bpsQuoteCcy;
652✔
369

370
        return -(npvQuoteCcy - npvBaseCcy) / bps;
652✔
371
    }
372

373
    void MtMCrossCurrencyBasisSwapRateHelper::accept(AcyclicVisitor& v) {
×
374
        auto* v1 = dynamic_cast<Visitor<MtMCrossCurrencyBasisSwapRateHelper>*>(&v);
×
375
        if (v1 != nullptr)
×
376
            v1->visit(*this);
×
377
        else
378
            RateHelper::accept(v);
×
379
    }
×
380
// -----------------------------------------------------------------------------
381
// CrossCurrencySwapRateHelper
382
// -----------------------------------------------------------------------------
383

384
Leg CrossCurrencySwapRateHelper::buildFixedLeg(const Schedule& fixedSchedule) const {
8✔
385
    return FixedRateLeg(fixedSchedule)
16✔
386
        .withNotionals(1.0)
8✔
387
        .withCouponRates(1.0, fixedDayCount_);
16✔
388
}
389

390
Leg CrossCurrencySwapRateHelper::buildFloatingLeg(const Schedule& floatSchedule) const {
8✔
391
    return IborLeg(floatSchedule, floatIndex_)
16✔
392
        .withNotionals(1.0)
8✔
393
        .withSpreads(0.0);
16✔
394
}
395

396
Handle<YieldTermStructure> CrossCurrencySwapRateHelper::fixedLegDiscountHandle() const {
17✔
397
    if (collateralOnFixedLeg_) {
17✔
398
        return collateralCurve_;
399
    }
400
    QL_REQUIRE(termStructure_, "term structure not set");
1✔
401
    return Handle<YieldTermStructure>(ext::shared_ptr<YieldTermStructure>(
1✔
402
        termStructure_, null_deleter()));
1✔
403
}
404

405
Handle<YieldTermStructure> CrossCurrencySwapRateHelper::floatingLegDiscountHandle() const {
17✔
406
    if (collateralOnFixedLeg_) {
17✔
407
        QL_REQUIRE(termStructure_, "term structure not set");
16✔
408
        return Handle<YieldTermStructure>(ext::shared_ptr<YieldTermStructure>(
16✔
409
            termStructure_, null_deleter()));
16✔
410
    }
411
    return collateralCurve_;
412
}
413

414

415
CrossCurrencySwapRateHelper::CrossCurrencySwapRateHelper(
8✔
416
    const Handle<Quote>& fixedRate,
417
    const Period& tenor,
418
    Natural fixingDays,
419
    const Calendar& calendar,
420
    BusinessDayConvention convention,
421
    bool endOfMonth,
422
    Frequency fixedFrequency,
423
    const DayCounter& fixedDayCount,
424
    const Currency& fixedCurrency,
425
    const ext::shared_ptr<IborIndex>& floatIndex,
426
    const Currency& floatCurrency,
427
    const Handle<YieldTermStructure>& collateralCurve,
428
    bool collateralOnFixedLeg)
8✔
429
: RelativeDateRateHelper(fixedRate),
430
  tenor_(tenor),
8✔
431
  fixingDays_(fixingDays),
8✔
432
  calendar_(calendar),
433
  convention_(convention),
8✔
434
  endOfMonth_(endOfMonth),
8✔
435
  fixedFrequency_(fixedFrequency),
8✔
436
  fixedDayCount_(fixedDayCount),
437
  fixedCurrency_(fixedCurrency),
438
  floatCurrency_(floatCurrency),
439
  floatIndex_(floatIndex),
8✔
440
  collateralCurve_(collateralCurve),
441
  collateralOnFixedLeg_(collateralOnFixedLeg) {
24✔
442

443
    QL_REQUIRE(floatIndex_, "floating index required");
8✔
444
    QL_REQUIRE(!collateralCurve_.empty(),
8✔
445
               "collateral curve must be provided");
446
    QL_REQUIRE(!floatIndex_->forwardingTermStructure().empty(),
16✔
447
               "floating index must have a forwarding curve");
448
               registerWith(floatIndex_);
16✔
449
               registerWith(collateralCurve_);
8✔
450
               
451
               
452

453
    initializeDates();
8✔
454
}
8✔
455

456
void CrossCurrencySwapRateHelper::initializeDates() {
8✔
457
    Date ref = Settings::instance().evaluationDate();
8✔
458
    QL_REQUIRE(ref != Date(), "evaluation date not set");
8✔
459
    instrumentSettlementDate_ = calendar_.advance(ref, fixingDays_, Days);
8✔
460
    maturityDate_ = calendar_.advance(instrumentSettlementDate_, tenor_, convention_, endOfMonth_);
8✔
461
    earliestDate_ = instrumentSettlementDate_;
8✔
462

463
    initialNotionalExchangeDate_ = instrumentSettlementDate_;
8✔
464
    finalNotionalExchangeDate_ = maturityDate_;
8✔
465

466
    fixedSchedule_ = Schedule(instrumentSettlementDate_, maturityDate_,
24✔
467
                              Period(fixedFrequency_), calendar_,
16✔
468
                              convention_, convention_,
469
                              DateGeneration::Forward, endOfMonth_);
16✔
470

471
    floatSchedule_ = Schedule(instrumentSettlementDate_, maturityDate_,
16✔
472
                              floatIndex_->tenor(),
16✔
473
                              floatIndex_->fixingCalendar(),
16✔
474
                              floatIndex_->businessDayConvention(),
475
                              floatIndex_->businessDayConvention(),
476
                              DateGeneration::Forward, false);
8✔
477

478
    QL_REQUIRE(fixedSchedule_.size() >= 2, "fixed schedule too short");
8✔
479
    QL_REQUIRE(floatSchedule_.size() >= 2, "floating schedule too short");
8✔
480

481
    fixedLeg_ = buildFixedLeg(fixedSchedule_);
8✔
482
    floatLeg_ = buildFloatingLeg(floatSchedule_);
8✔
483

484
    Date lastPayment = std::max(fixedSchedule_.back(), floatSchedule_.back());
16✔
485
    Date last = std::max(maturityDate_, lastPayment);
8✔
486

487
    const_cast<Date&>(latestRelevantDate_) = last;
8✔
488
    const_cast<Date&>(latestDate_) = last;
8✔
489
}
8✔
490

491

492
void CrossCurrencySwapRateHelper::setTermStructure(YieldTermStructure* t) {
8✔
493
    RelativeDateRateHelper::setTermStructure(t);
8✔
494
}
8✔
495

496
Real CrossCurrencySwapRateHelper::impliedQuote() const {
17✔
497
    QL_REQUIRE(termStructure_, "term structure not set");
17✔
498

499
    Handle<YieldTermStructure> fixedDisc  = fixedLegDiscountHandle();
17✔
500
    Handle<YieldTermStructure> floatDisc  = floatingLegDiscountHandle();
17✔
501

502
    auto [fixedNpv, fixedBps] = npvbpsConstNotionalLeg(
34✔
503
        fixedLeg_, initialNotionalExchangeDate_, finalNotionalExchangeDate_, fixedDisc);
17✔
504

505
    auto [floatNpv, floatBps] = npvbpsConstNotionalLeg(
17✔
506
        floatLeg_, initialNotionalExchangeDate_, finalNotionalExchangeDate_, floatDisc);
17✔
507

508
    QL_REQUIRE(std::fabs(fixedBps) > 0.0, "fixed leg BPS is zero");
17✔
509

510
    return 1.0 + (floatNpv - fixedNpv) / fixedBps;
34✔
511
}
512

513

514

NEW
515
void CrossCurrencySwapRateHelper::accept(AcyclicVisitor& v) {
×
NEW
516
    auto* v1 = dynamic_cast<Visitor<CrossCurrencySwapRateHelper>*>(&v);
×
NEW
517
    if (v1 != nullptr)
×
NEW
518
        v1->visit(*this);
×
519
    else
NEW
520
        RateHelper::accept(v);
×
NEW
521
}
×
522

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