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

lballabio / QuantLib / 30114572110

24 Jul 2026 05:50PM UTC coverage: 75.01% (+0.07%) from 74.943%
30114572110

Pull #2642

github

web-flow
Merge ae376647b into ecf01b598
Pull Request #2642: MTM Cross Currency Basis Swap instruments

346 of 386 new or added lines in 8 files covered. (89.64%)

22 existing lines in 1 file now uncovered.

60281 of 80364 relevant lines covered (75.01%)

8625312.81 hits per line

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

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

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

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

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

18
#include <ql/cashflows/cashflows.hpp>
19
#include <ql/cashflows/fxresetcashflows.hpp>
20
#include <ql/cashflows/iborcoupon.hpp>
21
#include <ql/cashflows/overnightindexedcoupon.hpp>
22
#include <ql/instruments/mtmcrosscurrencybasisswap.hpp>
23
#include <utility>
24

25
namespace QuantLib {
26

27
MtMCrossCurrencyBasisSwap::MtMCrossCurrencyBasisSwap(
225✔
28
    Type type,
29
    Real fxBaseNominal, Currency fxBaseCurrency, Schedule fxBaseSchedule,
30
    const ext::shared_ptr<IborIndex>& fxBaseIndex, Spread fxBaseSpread, Real fxBaseGearing,
31
    Real fxQuoteNominal, Currency fxQuoteCurrency, Schedule fxQuoteSchedule,
32
    const ext::shared_ptr<IborIndex>& fxQuoteIndex, Spread fxQuoteSpread, Real fxQuoteGearing,
33
    bool isFxBaseCurrencyLegResettable,
34
    Integer fxBasePaymentLag, Integer fxQuotePaymentLag,
35
    bool fxBaseCompoundSpread, Natural fxBaseLookbackDays, bool fxBaseObservationShift,
36
    Natural fxBaseLockoutDays, RateAveraging::Type fxBaseAveragingMethod,
37
    bool fxQuoteCompoundSpread, Natural fxQuoteLookbackDays, bool fxQuoteObservationShift,
38
    Natural fxQuoteLockoutDays, RateAveraging::Type fxQuoteAveragingMethod,
39
    const bool telescopicValueDates)
225✔
40
: CrossCurrencySwap(2),
41
  type_(type),
225✔
42
  fxBaseNominal_(fxBaseNominal),
225✔
43
  fxBaseCurrency_(std::move(fxBaseCurrency)), fxBaseSchedule_(std::move(fxBaseSchedule)),
225✔
44
  fxBaseIndex_(fxBaseIndex), fxBaseSpread_(fxBaseSpread), fxBaseGearing_(fxBaseGearing),
225✔
45
  fxQuoteNominal_(fxQuoteNominal), fxQuoteCurrency_(std::move(fxQuoteCurrency)),
225✔
46
  fxQuoteSchedule_(std::move(fxQuoteSchedule)), fxQuoteIndex_(fxQuoteIndex),
225✔
47
  fxQuoteSpread_(fxQuoteSpread), fxQuoteGearing_(fxQuoteGearing),
225✔
48
  isFxBaseCurrencyLegResettable_(isFxBaseCurrencyLegResettable),
225✔
49
  fxBasePaymentLag_(fxBasePaymentLag), fxQuotePaymentLag_(fxQuotePaymentLag),
225✔
50
  fxBaseCompoundSpread_(fxBaseCompoundSpread), fxBaseLookbackDays_(fxBaseLookbackDays),
225✔
51
  fxBaseObservationShift_(fxBaseObservationShift), fxBaseLockoutDays_(fxBaseLockoutDays),
225✔
52
  fxBaseAveragingMethod_(fxBaseAveragingMethod),
225✔
53
  fxQuoteCompoundSpread_(fxQuoteCompoundSpread), fxQuoteLookbackDays_(fxQuoteLookbackDays),
225✔
54
  fxQuoteObservationShift_(fxQuoteObservationShift), fxQuoteLockoutDays_(fxQuoteLockoutDays),
225✔
55
  fxQuoteAveragingMethod_(fxQuoteAveragingMethod), telescopicValueDates_(telescopicValueDates) {
225✔
56
    registerWith(fxBaseIndex_);
450✔
57
    registerWith(fxQuoteIndex_);
225✔
58
    initialize();
225✔
59
}
225✔
60

61
void MtMCrossCurrencyBasisSwap::initialize() {
225✔
62
    // Base-currency leg
63
    if (auto on = ext::dynamic_pointer_cast<OvernightIndex>(fxBaseIndex_)) {
225✔
64
        legs_[0] = OvernightLeg(fxBaseSchedule_, on)
24✔
65
                       .withNotionals(fxBaseNominal_)
12✔
66
                       .withSpreads(fxBaseSpread_)
12✔
67
                       .withGearings(fxBaseGearing_)
12✔
68
                       .withPaymentLag(fxBasePaymentLag_)
12✔
69
                       .compoundingSpreadDaily(fxBaseCompoundSpread_)
12✔
70
                       .withLookbackDays(fxBaseLookbackDays_)
12✔
71
                       .withObservationShift(fxBaseObservationShift_)
12✔
72
                       .withLockoutDays(fxBaseLockoutDays_)
12✔
73
                       .withAveragingMethod(fxBaseAveragingMethod_)
12✔
74
                       .withTelescopicValueDates(telescopicValueDates_);
24✔
75
    } else {
76
        legs_[0] = IborLeg(fxBaseSchedule_, fxBaseIndex_)
426✔
77
                       .withNotionals(fxBaseNominal_)
213✔
78
                       .withSpreads(fxBaseSpread_)
213✔
79
                       .withGearings(fxBaseGearing_)
213✔
80
                       .withPaymentLag(fxBasePaymentLag_);
426✔
81
    }
82
    payer_[0] = paysFxBaseCurrency() ? -1.0 : +1.0;
225✔
83
    currencies_[0] = fxBaseCurrency_;
84

85
    // Quote-currency leg
86
    if (auto on = ext::dynamic_pointer_cast<OvernightIndex>(fxQuoteIndex_)) {
225✔
87
        legs_[1] = OvernightLeg(fxQuoteSchedule_, on)
24✔
88
                       .withNotionals(fxQuoteNominal_)
12✔
89
                       .withSpreads(fxQuoteSpread_)
12✔
90
                       .withGearings(fxQuoteGearing_)
12✔
91
                       .withPaymentLag(fxQuotePaymentLag_)
12✔
92
                       .compoundingSpreadDaily(fxQuoteCompoundSpread_)
12✔
93
                       .withLookbackDays(fxQuoteLookbackDays_)
12✔
94
                       .withObservationShift(fxQuoteObservationShift_)
12✔
95
                       .withLockoutDays(fxQuoteLockoutDays_)
12✔
96
                       .withAveragingMethod(fxQuoteAveragingMethod_)
12✔
97
                       .withTelescopicValueDates(telescopicValueDates_);
24✔
98
    } else {
99
        legs_[1] = IborLeg(fxQuoteSchedule_, fxQuoteIndex_)
426✔
100
                       .withNotionals(fxQuoteNominal_)
213✔
101
                       .withSpreads(fxQuoteSpread_)
213✔
102
                       .withGearings(fxQuoteGearing_)
213✔
103
                       .withPaymentLag(fxQuotePaymentLag_);
426✔
104
    }
105
    payer_[1] = -payer_[0];
225✔
106
    currencies_[1] = fxQuoteCurrency_;
107

108
    auto earliestDate =
109
        std::min(CashFlows::startDate(legs_[0]), CashFlows::startDate(legs_[1]));
450✔
110
    auto maturityDate =
111
        std::max(CashFlows::maturityDate(legs_[0]), CashFlows::maturityDate(legs_[1]));
450✔
112

113
    // The resettable leg's coupons are replaced by FX-resetting equivalents,
114
    // whose notional is the constant-leg notional converted at each period's
115
    // reset, and the netted notional exchanges of a mark-to-market leg are
116
    // added: the first notional at inception, the reset difference at each
117
    // period boundary, and the last notional at maturity.  The FX rates the
118
    // flows convert at are attached later by the pricing engine.
119
    QL_REQUIRE(constantLegNotional() != Null<Real>(), "Constant leg notional cannot be null");
225✔
120
    QL_REQUIRE(constantLegNotional() != 0.0, "Constant leg notional cannot be zero");
225✔
121

122
    Size resettingLegNo = resettingLegIndex();
123
    const Schedule& resettingSchedule = resettingLegNo == 0 ? fxBaseSchedule_ : fxQuoteSchedule_;
225✔
124
    Integer paymentLag = resettingLegNo == 0 ? fxBasePaymentLag_ : fxQuotePaymentLag_;
225✔
125
    Calendar paymentCalendar = resettingSchedule.calendar();
126

127
    Leg resettingLeg;
128
    resettingLeg.reserve(2 * legs_[resettingLegNo].size() + 1);
225✔
129
    Date previousResetDate;    // null: the first exchange has no maturing period
225✔
130
    Date previousPaymentDate;  // null: ditto
225✔
131
    for (const auto& cf : legs_[resettingLegNo]) {
12,476✔
132
        auto coupon = ext::dynamic_pointer_cast<FloatingRateCoupon>(cf);
12,251✔
133
        QL_REQUIRE(coupon, "unexpected non-coupon cash flow on the resettable leg");
12,251✔
134
        Date resetDate = coupon->accrualStartDate();
12,251✔
135
        // The exchanges settle with the coupons: each period-boundary exchange
136
        // pays on the maturing coupon's payment date, and the initial exchange
137
        // follows the leg's own payment calendar, lag and convention (IborLeg
138
        // and OvernightLeg adjust payment dates with Following by default).
139
        Date exchangeDate =
140
            previousPaymentDate != Date() ?
24,502✔
141
                previousPaymentDate :
142
                paymentCalendar.advance(resetDate, paymentLag, Days, Following);
12,251✔
143
        resettingLeg.push_back(ext::make_shared<FxResetNotionalExchange>(
12,251✔
144
            exchangeDate, constantLegNotional(), previousResetDate, resetDate));
24,502✔
145
        resettingLeg.push_back(ext::make_shared<FxResetCoupon>(coupon, constantLegNotional()));
24,502✔
146
        previousResetDate = resetDate;
12,251✔
147
        previousPaymentDate = coupon->date();
12,251✔
148
    }
149
    resettingLeg.push_back(ext::make_shared<FxResetNotionalExchange>(
225✔
150
        previousPaymentDate, constantLegNotional(), previousResetDate, Date()));
675✔
151
    legs_[resettingLegNo] = resettingLeg;
225✔
152

153
    // Only the constant-notional leg gets the inception/maturity exchange flows.
154
    if (resettingLegNo != 0)
225✔
155
        CrossCurrencySwap::addNotionalExchangesToLeg(
161✔
156
            legs_[0], fxBaseSchedule_.calendar(), earliestDate, maturityDate, fxBasePaymentLag_,
161✔
157
            fxBaseSchedule_.businessDayConvention(), fxBaseNominal_);
158

159
    if (resettingLegNo != 1)
225✔
160
        CrossCurrencySwap::addNotionalExchangesToLeg(
64✔
161
            legs_[1], fxQuoteSchedule_.calendar(), earliestDate, maturityDate, fxQuotePaymentLag_,
64✔
162
            fxQuoteSchedule_.businessDayConvention(), fxQuoteNominal_);
163

164
    for (Size legNo = 0; legNo < 2; ++legNo) {
675✔
165
        for (auto& cf : legs_[legNo])
37,681✔
166
            registerWith(cf);
74,462✔
167
    }
168
}
450✔
169

170
void MtMCrossCurrencyBasisSwap::setupArguments(PricingEngine::arguments* args) const {
93✔
171

172
    CrossCurrencySwap::setupArguments(args);
93✔
173

174
    auto* arguments = dynamic_cast<MtMCrossCurrencyBasisSwap::arguments*>(args);
93✔
175

176
    QL_REQUIRE(arguments != nullptr, "wrong argument type");
93✔
177

178
    arguments->resettingLegIndex = resettingLegIndex();
93✔
179
    arguments->constantLegIndex = constantLegIndex();
93✔
180
    arguments->fxBaseSpread = fxBaseSpread_;
93✔
181
    arguments->fxQuoteSpread = fxQuoteSpread_;
93✔
182
}
93✔
183

184
void MtMCrossCurrencyBasisSwap::fetchResults(const PricingEngine::results* r) const {
92✔
185

186
    CrossCurrencySwap::fetchResults(r);
92✔
187

188
    const auto* results = dynamic_cast<const MtMCrossCurrencyBasisSwap::results*>(r);
92✔
189
    if (results != nullptr) {
92✔
190
        fairFxBaseSpread_ = results->fairFxBaseSpread;
92✔
191
        fairFxQuoteSpread_ = results->fairFxQuoteSpread;
92✔
192
    } else {
NEW
193
        fairFxBaseSpread_ = Null<Spread>();
×
NEW
194
        fairFxQuoteSpread_ = Null<Spread>();
×
195
    }
196

197
    static Spread basisPoint = 1.0e-4;
198
    if (fairFxBaseSpread_ == Null<Spread>()) {
92✔
199
        if (legBPS_[0] != Null<Real>())
92✔
200
            fairFxBaseSpread_ = fxBaseSpread_ - NPV_ / (legBPS_[0] / basisPoint);
92✔
201
    }
202
    if (fairFxQuoteSpread_ == Null<Spread>()) {
92✔
203
        if (legBPS_[1] != Null<Real>())
92✔
204
            fairFxQuoteSpread_ = fxQuoteSpread_ - NPV_ / (legBPS_[1] / basisPoint);
92✔
205
    }
206
}
92✔
207

NEW
208
void MtMCrossCurrencyBasisSwap::setupExpired() const {
×
NEW
209
    CrossCurrencySwap::setupExpired();
×
NEW
210
    fairFxBaseSpread_ = Null<Spread>();
×
NEW
211
    fairFxQuoteSpread_ = Null<Spread>();
×
NEW
212
}
×
213

214
void MtMCrossCurrencyBasisSwap::arguments::validate() const {
93✔
215
    CrossCurrencySwap::arguments::validate();
93✔
216
    QL_REQUIRE(resettingLegIndex != Null<Size>(), "Resetting leg index cannot be null");
93✔
217
    QL_REQUIRE(constantLegIndex != Null<Size>(), "Constant leg index cannot be null");
93✔
218
    QL_REQUIRE(resettingLegIndex < legs.size(),
93✔
219
               "Resetting leg index (" << resettingLegIndex << ") out of range");
220
    QL_REQUIRE(constantLegIndex < legs.size(),
93✔
221
               "Constant leg index (" << constantLegIndex << ") out of range");
222
    QL_REQUIRE(resettingLegIndex != constantLegIndex,
93✔
223
               "Resetting leg and constant leg must be different");
224
    QL_REQUIRE(fxBaseSpread != Null<Spread>(), "FX-base spread cannot be null");
93✔
225
    QL_REQUIRE(fxQuoteSpread != Null<Spread>(), "FX-quote spread cannot be null");
93✔
226
}
93✔
227

228
void MtMCrossCurrencyBasisSwap::results::reset() {
93✔
229
    CrossCurrencySwap::results::reset();
93✔
230
    fairFxBaseSpread = Null<Spread>();
93✔
231
    fairFxQuoteSpread = Null<Spread>();
93✔
232
}
93✔
233

234
} // namespace QuantLib
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc