Coverage Report

Created: 2026-10-31 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/experimental/fx/mtmcrosscurrencybasisswap.hpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2026 Kyrylo Protsenko
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
/*! \file mtmcrosscurrencybasisswap.hpp
21
    \brief Mark-to-market (resetting-notional) cross-currency basis swap
22
*/
23
24
#ifndef quantlib_mtm_cross_currency_basis_swap_hpp
25
#define quantlib_mtm_cross_currency_basis_swap_hpp
26
27
#include <ql/cashflows/iborcoupon.hpp>
28
#include <ql/cashflows/stubiborcoupon.hpp>
29
#include <ql/cashflows/rateaveraging.hpp>
30
#include <ql/experimental/fx/fxresetcashflows.hpp>
31
#include <ql/indexes/iborindex.hpp>
32
#include <ql/instruments/crosscurrencyswap.hpp>
33
#include <ql/optional.hpp>
34
#include <ql/pricingengine.hpp>
35
#include <ql/time/schedule.hpp>
36
37
namespace QuantLib {
38
39
//! Mark-to-market cross-currency basis swap
40
/*! A float-vs-float cross-currency basis swap in which the notional of one leg
41
    (the \e resettable leg) is reset for each coupon period to track the
42
    prevailing FX rate, while the other leg keeps a constant notional that is
43
    exchanged at inception and maturity.  Each reset has a fixing date, on
44
    which the FX rate is observed, and a value date at the start of the accrual
45
    period.
46
47
    Following the convention of MtMCrossCurrencyBasisSwapRateHelper, the two legs
48
    are identified by their role in the FX pair: the \e base-currency leg and the
49
    \e quote-currency leg, and \c isFxBaseCurrencyLegResettable selects which of
50
    them carries the resetting notional.  The trade \c Type controls whether the
51
    base-currency leg is paid or received.
52
53
    Resetting the notional removes most of the FX exposure of the principal and
54
    is the market-standard convention for many currency pairs.  The resettable
55
    leg is built from FX-linked cash flows: FxResetCoupon instances, whose
56
    notional is the constant-leg notional converted at the period's FX-reset
57
    observation, and FxResetNotionalExchange instances paying the netted
58
    notional differences (the first notional at inception, the reset difference
59
    at each period boundary, and the last notional at maturity).
60
61
    During instrument pricing, the FX rates are supplied to local cash-flow
62
    wrappers by an engine that understands FX-resetting legs (see
63
    DiscountingMtMCrossCurrencyBasisSwapEngine).  Independently querying the
64
    live reset cash flows requires assigning an FxResetPricer with
65
    setFxResetPricer.  A future reset is projected as the forward FX implied by
66
    its pricer's discount curves.  If a reset fixing date precedes the
67
    evaluation date, the realized exchange rate must be available from
68
    \c ExchangeRateManager on that fixing date.  Direct, inverse, and derived
69
    exchange rates are supported.
70
71
    \ingroup instruments
72
*/
73
class MtMCrossCurrencyBasisSwap : public CrossCurrencySwap {
74
  public:
75
    class arguments;
76
    class results;
77
    class engine;
78
79
    enum Type { PayFxBaseCurrency, ReceiveFxBaseCurrency };
80
81
    //! \name Constructors
82
    //@{
83
    /*! Payment lags apply only to coupon payments.  Initial and final notional
84
        exchanges remain on the effective and maturity dates, adjusted by their
85
        leg's payment convention.  Interim reset exchanges occur on the current
86
        reset value date.
87
88
        \param type  Pay or receive the FX base-currency leg.
89
        \param isFxBaseCurrencyLegResettable  If true the base-currency (first)
90
                                   leg is the resettable leg; otherwise the
91
                                   quote-currency (second) leg resets.  The other
92
                                   leg keeps a constant notional and exchanges it
93
                                   at inception and maturity.
94
        \param fxResetFixingDays   Fixing days used to derive each FX fixing date
95
                                   from its accrual-start value date.  The default
96
                                   preserves the legacy behavior of fixing on the
97
                                   accrual start date.
98
        \param fxResetFixingCalendar   Calendar used to derive each FX fixing date.
99
                                       For a non-zero fixing lag,
100
                                       an empty fixing calendar is replaced by the
101
                                       resettable leg's schedule calendar.
102
        \param fxBasePaymentLag  Coupon payment lag for the base-currency leg.
103
        \param fxQuotePaymentLag Coupon payment lag for the quote-currency leg.
104
        \param fxBasePaymentConvention  Payment convention for the base-currency
105
                                        leg and its notional exchanges.
106
        \param fxQuotePaymentConvention Payment convention for the quote-currency
107
                                        leg and its notional exchanges.
108
        \param useIndexedCoupons If provided, overrides the global IborCoupon
109
                                  setting for both legs.
110
        \param fxBaseStubIndexSelection  Index selection applied to irregular
111
                                  coupons of the base-currency leg when it is an
112
                                  Ibor leg (see StubIndexSelection).  The default
113
                                  prices broken periods off the leg's own index.
114
        \param fxQuoteStubIndexSelection  As fxBaseStubIndexSelection, for the
115
                                  quote-currency leg.
116
    */
117
    MtMCrossCurrencyBasisSwap(
118
        Type type,
119
        Real fxBaseNominal, Currency fxBaseCurrency, Schedule fxBaseSchedule,
120
        const ext::shared_ptr<IborIndex>& fxBaseIndex,
121
        Spread fxBaseSpread,
122
        Real fxBaseGearing,
123
        Real fxQuoteNominal, Currency fxQuoteCurrency, Schedule fxQuoteSchedule,
124
        const ext::shared_ptr<IborIndex>& fxQuoteIndex,
125
        Spread fxQuoteSpread,
126
        Real fxQuoteGearing,
127
        bool isFxBaseCurrencyLegResettable,
128
        Natural fxResetFixingDays = 0,
129
        const Calendar& fxResetFixingCalendar = Calendar(),
130
        Integer fxBasePaymentLag = 0, Integer fxQuotePaymentLag = 0,
131
        BusinessDayConvention fxBasePaymentConvention = Following,
132
        BusinessDayConvention fxQuotePaymentConvention = Following,
133
        bool fxBaseCompoundSpread = false,
134
        Natural fxBaseLookbackDays = Null<Natural>(),
135
        bool fxBaseObservationShift = false,
136
        Natural fxBaseLockoutDays = 0,
137
        RateAveraging::Type fxBaseAveragingMethod = RateAveraging::Compound,
138
        bool fxQuoteCompoundSpread = false,
139
        Natural fxQuoteLookbackDays = Null<Natural>(),
140
        bool fxQuoteObservationShift = false,
141
        Natural fxQuoteLockoutDays = 0,
142
        RateAveraging::Type fxQuoteAveragingMethod = RateAveraging::Compound,
143
        bool telescopicValueDates = false,
144
        std::optional<bool> useIndexedCoupons = std::nullopt,
145
        StubIndexSelection fxBaseStubIndexSelection = {},
146
        StubIndexSelection fxQuoteStubIndexSelection = {});
147
    //@}
148
149
    //! \name Instrument interface
150
    //@{
151
    void setupArguments(PricingEngine::arguments* args) const override;
152
    void fetchResults(const PricingEngine::results*) const override;
153
    //@}
154
155
    //! \name Inspectors
156
    //@{
157
0
    Type type() const { return type_; }
158
159
0
    bool paysFxBaseCurrency() const { return type_ == PayFxBaseCurrency; }
160
161
0
    Real fxBaseNominal() const { return fxBaseNominal_; }
162
0
    const Currency& fxBaseCurrency() const { return fxBaseCurrency_; }
163
0
    const Schedule& fxBaseSchedule() const { return fxBaseSchedule_; }
164
0
    const ext::shared_ptr<IborIndex>& fxBaseIndex() const { return fxBaseIndex_; }
165
0
    Spread fxBaseSpread() const { return fxBaseSpread_; }
166
0
    Real fxBaseGearing() const { return fxBaseGearing_; }
167
0
    const StubIndexSelection& fxBaseStubIndexSelection() const {
168
0
        return fxBaseStubIndexSelection_;
169
0
    }
170
0
    const StubIndexSelection& fxQuoteStubIndexSelection() const {
171
0
        return fxQuoteStubIndexSelection_;
172
0
    }
173
174
0
    Real fxQuoteNominal() const { return fxQuoteNominal_; }
175
0
    const Currency& fxQuoteCurrency() const { return fxQuoteCurrency_; }
176
0
    const Schedule& fxQuoteSchedule() const { return fxQuoteSchedule_; }
177
0
    const ext::shared_ptr<IborIndex>& fxQuoteIndex() const { return fxQuoteIndex_; }
178
0
    Spread fxQuoteSpread() const { return fxQuoteSpread_; }
179
0
    Real fxQuoteGearing() const { return fxQuoteGearing_; }
180
181
0
    Real payNominal() const { return paysFxBaseCurrency() ? fxBaseNominal_ : fxQuoteNominal_; }
182
0
    const Currency& payCurrency() const {
183
0
        return paysFxBaseCurrency() ? fxBaseCurrency_ : fxQuoteCurrency_;
184
0
    }
185
0
    const Schedule& paySchedule() const {
186
0
        return paysFxBaseCurrency() ? fxBaseSchedule_ : fxQuoteSchedule_;
187
0
    }
188
0
    const ext::shared_ptr<IborIndex>& payIndex() const {
189
0
        return paysFxBaseCurrency() ? fxBaseIndex_ : fxQuoteIndex_;
190
0
    }
191
0
    Spread paySpread() const { return paysFxBaseCurrency() ? fxBaseSpread_ : fxQuoteSpread_; }
192
0
    Real payGearing() const { return paysFxBaseCurrency() ? fxBaseGearing_ : fxQuoteGearing_; }
193
194
0
    Real recNominal() const { return paysFxBaseCurrency() ? fxQuoteNominal_ : fxBaseNominal_; }
195
0
    const Currency& recCurrency() const {
196
0
        return paysFxBaseCurrency() ? fxQuoteCurrency_ : fxBaseCurrency_;
197
0
    }
198
0
    const Schedule& recSchedule() const {
199
0
        return paysFxBaseCurrency() ? fxQuoteSchedule_ : fxBaseSchedule_;
200
0
    }
201
0
    const ext::shared_ptr<IborIndex>& recIndex() const {
202
0
        return paysFxBaseCurrency() ? fxQuoteIndex_ : fxBaseIndex_;
203
0
    }
204
0
    Spread recSpread() const { return paysFxBaseCurrency() ? fxQuoteSpread_ : fxBaseSpread_; }
205
0
    Real recGearing() const { return paysFxBaseCurrency() ? fxQuoteGearing_ : fxBaseGearing_; }
206
207
    //! true if the base-currency (first) leg is the resettable leg
208
0
    bool isFxBaseCurrencyLegResettable() const { return isFxBaseCurrencyLegResettable_; }
209
0
    Size resettingLegIndex() const { return isFxBaseCurrencyLegResettable_ ? 0 : 1; }
210
0
    Size constantLegIndex() const { return isFxBaseCurrencyLegResettable_ ? 1 : 0; }
211
0
    const Leg& resettingLeg() const { return legs_[resettingLegIndex()]; }
212
0
    const Leg& constantLeg() const { return legs_[constantLegIndex()]; }
213
    //! notional of the constant leg, in its own currency
214
0
    Real constantLegNotional() const {
215
0
        return isFxBaseCurrencyLegResettable_ ? fxQuoteNominal_ : fxBaseNominal_;
216
0
    }
217
0
    Natural fxResetFixingDays() const { return fxResetConvention_.fixingDays(); }
218
0
    Calendar fxResetFixingCalendar() const { return fxResetConvention_.fixingCalendar(); }
219
0
    BusinessDayConvention fxBasePaymentConvention() const {
220
0
        return fxBasePaymentConvention_;
221
0
    }
222
0
    BusinessDayConvention fxQuotePaymentConvention() const {
223
0
        return fxQuotePaymentConvention_;
224
0
    }
225
    /*! FX rates used to determine the resettable coupon notionals.
226
227
        The values follow the order of the non-occurred FxResetCoupon
228
        instances in resettingLeg().
229
    */
230
0
    const std::vector<Real>& fxResetRates() const {
231
0
        calculate();
232
0
        return fxResetRates_;
233
0
    }
234
    /*! Notionals of the resettable coupons, in resetting-leg currency.
235
236
        The values follow the order of the non-occurred FxResetCoupon
237
        instances in resettingLeg().
238
    */
239
0
    const std::vector<Real>& fxResetNotionals() const {
240
0
        calculate();
241
0
        return fxResetNotionals_;
242
0
    }
243
    //@}
244
245
    //! \name Additional interface
246
    //@{
247
0
    Spread fairFxBaseSpread() const {
248
0
        calculate();
249
0
        QL_REQUIRE(fairFxBaseSpread_ != Null<Real>(), "Fair FX-base spread is not available");
250
0
        return fairFxBaseSpread_;
251
0
    }
252
0
    Spread fairFxQuoteSpread() const {
253
0
        calculate();
254
0
        QL_REQUIRE(fairFxQuoteSpread_ != Null<Real>(), "Fair FX-quote spread is not available");
255
0
        return fairFxQuoteSpread_;
256
0
    }
257
0
    Spread fairPaySpread() const {
258
0
        return paysFxBaseCurrency() ? fairFxBaseSpread() : fairFxQuoteSpread();
259
0
    }
260
0
    Spread fairRecSpread() const {
261
0
        return paysFxBaseCurrency() ? fairFxQuoteSpread() : fairFxBaseSpread();
262
0
    }
263
    //@}
264
265
  protected:
266
    //! \name Instrument interface
267
    //@{
268
    void setupExpired() const override;
269
    //@}
270
271
  private:
272
    void initialize();
273
274
    Type type_;
275
    Real fxBaseNominal_;
276
    Currency fxBaseCurrency_;
277
    Schedule fxBaseSchedule_;
278
    ext::shared_ptr<IborIndex> fxBaseIndex_;
279
    Spread fxBaseSpread_;
280
    Real fxBaseGearing_;
281
    StubIndexSelection fxBaseStubIndexSelection_;
282
283
    Real fxQuoteNominal_;
284
    Currency fxQuoteCurrency_;
285
    Schedule fxQuoteSchedule_;
286
    ext::shared_ptr<IborIndex> fxQuoteIndex_;
287
    Spread fxQuoteSpread_;
288
    Real fxQuoteGearing_;
289
    StubIndexSelection fxQuoteStubIndexSelection_;
290
291
    bool isFxBaseCurrencyLegResettable_;
292
    FxResetConvention fxResetConvention_;
293
294
    Integer fxBasePaymentLag_;
295
    Integer fxQuotePaymentLag_;
296
    BusinessDayConvention fxBasePaymentConvention_;
297
    BusinessDayConvention fxQuotePaymentConvention_;
298
    std::optional<bool> useIndexedCoupons_;
299
300
    // OIS only
301
    bool fxBaseCompoundSpread_;
302
    Natural fxBaseLookbackDays_;
303
    bool fxBaseObservationShift_;
304
    Natural fxBaseLockoutDays_;
305
    RateAveraging::Type fxBaseAveragingMethod_;
306
    bool fxQuoteCompoundSpread_;
307
    Natural fxQuoteLookbackDays_;
308
    bool fxQuoteObservationShift_;
309
    Natural fxQuoteLockoutDays_;
310
    RateAveraging::Type fxQuoteAveragingMethod_;
311
    bool telescopicValueDates_;
312
313
    mutable Spread fairFxBaseSpread_;
314
    mutable Spread fairFxQuoteSpread_;
315
    mutable std::vector<Real> fxResetRates_;
316
    mutable std::vector<Real> fxResetNotionals_;
317
};
318
319
320
class MtMCrossCurrencyBasisSwap::arguments : public CrossCurrencySwap::arguments {
321
  public:
322
    //! index of the resettable leg
323
    Size resettingLegIndex = Null<Size>();
324
    //! index of the constant-notional leg whose notional is converted at reset
325
    Size constantLegIndex = Null<Size>();
326
    //! convention for the FX fixing and its associated spot value date
327
    Natural fxResetFixingDays = 0;
328
    Calendar fxResetFixingCalendar;
329
    Spread fxBaseSpread = Null<Spread>();
330
    Spread fxQuoteSpread = Null<Spread>();
331
    void validate() const override;
332
};
333
334
335
class MtMCrossCurrencyBasisSwap::results : public CrossCurrencySwap::results {
336
  public:
337
    Spread fairFxBaseSpread = Null<Spread>();
338
    Spread fairFxQuoteSpread = Null<Spread>();
339
    std::vector<Real> fxResetRates;
340
    std::vector<Real> fxResetNotionals;
341
    void reset() override;
342
};
343
344
345
class MtMCrossCurrencyBasisSwap::engine
346
    : public GenericEngine<MtMCrossCurrencyBasisSwap::arguments,
347
                           MtMCrossCurrencyBasisSwap::results> {};
348
349
}
350
351
#endif