From 7bad74aaf61f0be759a21760103339c84698aba8 Mon Sep 17 00:00:00 2001 From: dakshbtc Date: Tue, 18 Aug 2026 09:44:46 -0500 Subject: [PATCH] cross-currency swap builder added --- ql/CMakeLists.txt | 8 + ql/experimental/fx/Makefile.am | 2 + ql/experimental/fx/all.hpp | 1 + .../fx/makemtmcrosscurrencybasisswap.cpp | 433 ++++++++++++++++++ .../fx/makemtmcrosscurrencybasisswap.hpp | 172 +++++++ ql/instruments/Makefile.am | 6 + ql/instruments/all.hpp | 3 + ...akeconstnotionalcrosscurrencybasisswap.cpp | 402 ++++++++++++++++ ...akeconstnotionalcrosscurrencybasisswap.hpp | 162 +++++++ ...tionalcrosscurrencyfixedvsfloatingswap.cpp | 389 ++++++++++++++++ ...tionalcrosscurrencyfixedvsfloatingswap.hpp | 181 ++++++++ .../makeconstnotionalcrosscurrencyswap.cpp | 115 +++++ .../makeconstnotionalcrosscurrencyswap.hpp | 92 ++++ test-suite/CMakeLists.txt | 1 + test-suite/Makefile.am | 1 + test-suite/makecrosscurrencyswap.cpp | 417 +++++++++++++++++ 16 files changed, 2385 insertions(+) create mode 100644 ql/experimental/fx/makemtmcrosscurrencybasisswap.cpp create mode 100644 ql/experimental/fx/makemtmcrosscurrencybasisswap.hpp create mode 100644 ql/instruments/makeconstnotionalcrosscurrencybasisswap.cpp create mode 100644 ql/instruments/makeconstnotionalcrosscurrencybasisswap.hpp create mode 100644 ql/instruments/makeconstnotionalcrosscurrencyfixedvsfloatingswap.cpp create mode 100644 ql/instruments/makeconstnotionalcrosscurrencyfixedvsfloatingswap.hpp create mode 100644 ql/instruments/makeconstnotionalcrosscurrencyswap.cpp create mode 100644 ql/instruments/makeconstnotionalcrosscurrencyswap.hpp create mode 100644 test-suite/makecrosscurrencyswap.cpp diff --git a/ql/CMakeLists.txt b/ql/CMakeLists.txt index 6080d9ee554..556107d205b 100644 --- a/ql/CMakeLists.txt +++ b/ql/CMakeLists.txt @@ -149,6 +149,7 @@ set(QL_SOURCES experimental/forward/analytichestonforwardeuropeanengine.cpp experimental/fx/discountingmtmcrosscurrencybasisswapengine.cpp experimental/fx/fxresetcashflows.cpp + experimental/fx/makemtmcrosscurrencybasisswap.cpp experimental/fx/mtmcrosscurrencybasisswap.cpp experimental/inflation/cpicapfloorengines.cpp experimental/inflation/cpicapfloortermpricesurface.cpp @@ -291,6 +292,9 @@ set(QL_SOURCES instruments/makecapfloor.cpp instruments/makecds.cpp instruments/makecms.cpp + instruments/makeconstnotionalcrosscurrencybasisswap.cpp + instruments/makeconstnotionalcrosscurrencyfixedvsfloatingswap.cpp + instruments/makeconstnotionalcrosscurrencyswap.cpp instruments/makemultipleresetsswap.cpp instruments/makeois.cpp instruments/makeswaption.cpp @@ -1179,6 +1183,7 @@ set(QL_HEADERS experimental/fx/deltavolquote.hpp experimental/fx/discountingmtmcrosscurrencybasisswapengine.hpp experimental/fx/fxresetcashflows.hpp + experimental/fx/makemtmcrosscurrencybasisswap.hpp experimental/fx/mtmcrosscurrencybasisswap.hpp experimental/inflation/cpicapfloorengines.hpp experimental/inflation/cpicapfloortermpricesurface.hpp @@ -1391,6 +1396,9 @@ set(QL_HEADERS instruments/makecapfloor.hpp instruments/makecds.hpp instruments/makecms.hpp + instruments/makeconstnotionalcrosscurrencybasisswap.hpp + instruments/makeconstnotionalcrosscurrencyfixedvsfloatingswap.hpp + instruments/makeconstnotionalcrosscurrencyswap.hpp instruments/makemultipleresetsswap.hpp instruments/makeois.hpp instruments/makeswaption.hpp diff --git a/ql/experimental/fx/Makefile.am b/ql/experimental/fx/Makefile.am index 1bf1f4e9bb1..f3bd1c912e5 100644 --- a/ql/experimental/fx/Makefile.am +++ b/ql/experimental/fx/Makefile.am @@ -8,11 +8,13 @@ this_include_HEADERS = \ deltavolquote.hpp \ discountingmtmcrosscurrencybasisswapengine.hpp \ fxresetcashflows.hpp \ + makemtmcrosscurrencybasisswap.hpp \ mtmcrosscurrencybasisswap.hpp cpp_files = \ discountingmtmcrosscurrencybasisswapengine.cpp \ fxresetcashflows.cpp \ + makemtmcrosscurrencybasisswap.cpp \ mtmcrosscurrencybasisswap.cpp if UNITY_BUILD diff --git a/ql/experimental/fx/all.hpp b/ql/experimental/fx/all.hpp index 82e0965d4aa..73b8b1977da 100644 --- a/ql/experimental/fx/all.hpp +++ b/ql/experimental/fx/all.hpp @@ -3,5 +3,6 @@ #include #include +#include #include diff --git a/ql/experimental/fx/makemtmcrosscurrencybasisswap.cpp b/ql/experimental/fx/makemtmcrosscurrencybasisswap.cpp new file mode 100644 index 00000000000..ea4ab695b6c --- /dev/null +++ b/ql/experimental/fx/makemtmcrosscurrencybasisswap.cpp @@ -0,0 +1,433 @@ +/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + + QuantLib is free software: you can redistribute it and/or modify it + under the terms of the QuantLib license. You should have received a + copy of the license along with this program; if not, please email + . The license is also available online at + . + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include + +namespace QuantLib { + + namespace { + + Calendar effectiveCalendar(const Calendar& legCalendar, + const Calendar& sharedCalendar, + const ext::shared_ptr& index) { + if (!legCalendar.empty()) + return legCalendar; + if (!sharedCalendar.empty()) + return sharedCalendar; + return index->fixingCalendar(); + } + + } + + MakeMtMCrossCurrencyBasisSwap::MakeMtMCrossCurrencyBasisSwap( + const Period& swapTenor, + ext::shared_ptr fxBaseIndex, + ext::shared_ptr fxQuoteIndex, + bool isFxBaseCurrencyLegResettable, + Real fxBaseNominal, + const Period& forwardStart) + : swapTenor_(swapTenor), fxBaseIndex_(std::move(fxBaseIndex)), + fxQuoteIndex_(std::move(fxQuoteIndex)), + isFxBaseCurrencyLegResettable_(isFxBaseCurrencyLegResettable), + fxBaseNominal_(fxBaseNominal), forwardStart_(forwardStart), + fxBasePaymentConvention_(fxBaseIndex_->businessDayConvention()), + fxQuotePaymentConvention_(fxQuoteIndex_->businessDayConvention()) {} + + MakeMtMCrossCurrencyBasisSwap::operator MtMCrossCurrencyBasisSwap() const { + ext::shared_ptr swap = *this; + return *swap; + } + + MakeMtMCrossCurrencyBasisSwap::operator ext::shared_ptr() const { + + QL_REQUIRE(effectiveDate_ == Date() || settlementDays_ == Null(), + "cannot set both an explicit effective date and settlement days; " + "use one or the other"); + + Currency fxBaseCcy = + fxBaseCurrency_ != Currency() ? fxBaseCurrency_ : fxBaseIndex_->currency(); + Currency fxQuoteCcy = + fxQuoteCurrency_ != Currency() ? fxQuoteCurrency_ : fxQuoteIndex_->currency(); + + Calendar jointCalendar = calendar_.empty() ? + JointCalendar(fxBaseIndex_->fixingCalendar(), + fxQuoteIndex_->fixingCalendar()) : + calendar_; + + Date startDate; + if (effectiveDate_ != Date()) { + startDate = effectiveDate_; + } else { + Date refDate = Settings::instance().evaluationDate(); + refDate = jointCalendar.adjust(refDate); + Natural settlementDays = settlementDays_ == Null() ? + std::max(fxBaseIndex_->fixingDays(), + fxQuoteIndex_->fixingDays()) : + settlementDays_; + Date spotDate = jointCalendar.advance(refDate, settlementDays * Days); + startDate = spotDate + forwardStart_; + if (forwardStart_.length() < 0) + startDate = jointCalendar.adjust(startDate, Preceding); + else if (forwardStart_.length() > 0) + startDate = jointCalendar.adjust(startDate, Following); + } + + Date endDate = terminationDate_ != Date() ? terminationDate_ : startDate + swapTenor_; + + Calendar fxBaseCal = effectiveCalendar(fxBaseCalendar_, calendar_, fxBaseIndex_); + Calendar fxQuoteCal = effectiveCalendar(fxQuoteCalendar_, calendar_, fxQuoteIndex_); + + Period fxBaseFreq = fxBaseFrequency_ ? Period(*fxBaseFrequency_) : fxBaseIndex_->tenor(); + Period fxQuoteFreq = + fxQuoteFrequency_ ? Period(*fxQuoteFrequency_) : fxQuoteIndex_->tenor(); + + Schedule fxBaseSchedule = MakeSchedule() + .from(startDate) + .to(endDate) + .withTenor(fxBaseFreq) + .withCalendar(fxBaseCal) + .withConvention(fxBasePaymentConvention_) + .withRule(rule_) + .endOfMonth(endOfMonth_); + + Schedule fxQuoteSchedule = MakeSchedule() + .from(startDate) + .to(endDate) + .withTenor(fxQuoteFreq) + .withCalendar(fxQuoteCal) + .withConvention(fxQuotePaymentConvention_) + .withRule(rule_) + .endOfMonth(endOfMonth_); + + Real fxQuoteNominal; + if (fxQuoteNominal_ != Null()) { + fxQuoteNominal = fxQuoteNominal_; + } else if (!fxSpot_.empty()) { + QL_REQUIRE(fxSpot_->value() > 0.0, + "the FX spot quote must be positive; got " << fxSpot_->value()); + fxQuoteNominal = fxBaseNominal_ * fxSpot_->value(); + } else { + QL_REQUIRE(fxBaseCcy == fxQuoteCcy, + "an FX spot quote is required to derive the " << fxQuoteCcy.code() + << " notional from the " << fxBaseCcy.code() + << " notional; use withFxSpot() or withFxQuoteNominal()"); + fxQuoteNominal = fxBaseNominal_; + } + + auto swap = ext::make_shared( + type_, fxBaseNominal_, fxBaseCcy, fxBaseSchedule, fxBaseIndex_, fxBaseSpread_, + fxBaseGearing_, fxQuoteNominal, fxQuoteCcy, fxQuoteSchedule, fxQuoteIndex_, + fxQuoteSpread_, fxQuoteGearing_, isFxBaseCurrencyLegResettable_, fxResetFixingDays_, + fxResetFixingCalendar_, fxBasePaymentLag_, fxQuotePaymentLag_, + fxBasePaymentConvention_, fxQuotePaymentConvention_, fxBaseCompoundSpread_, + fxBaseLookbackDays_, fxBaseObservationShift_, fxBaseLockoutDays_, + fxBaseAveragingMethod_, fxQuoteCompoundSpread_, fxQuoteLookbackDays_, + fxQuoteObservationShift_, fxQuoteLockoutDays_, fxQuoteAveragingMethod_, + telescopicValueDates_, useIndexedCoupons_); + + if (engine_ != nullptr) { + swap->setPricingEngine(engine_); + } else if (!fxBaseDiscountCurve_.empty() || !fxQuoteDiscountCurve_.empty()) { + QL_REQUIRE(!fxBaseDiscountCurve_.empty() && !fxQuoteDiscountCurve_.empty(), + "both discount curves are needed to build the pricing engine, but only " + "the " + << (fxBaseDiscountCurve_.empty() ? fxQuoteCcy.code() : + fxBaseCcy.code()) + << " one was given; use withFxBaseDiscountCurve() and " + "withFxQuoteDiscountCurve()"); + QL_REQUIRE(!fxSpot_.empty() || fxBaseCcy == fxQuoteCcy, + "an FX spot quote is required to build the pricing engine for a " + << fxBaseCcy.code() << "/" << fxQuoteCcy.code() + << " swap; use withFxSpot() or supply a custom engine with " + "withPricingEngine()"); + Handle engineSpotFX = fxSpot_.empty() ? makeQuoteHandle(1.0) : fxSpot_; + auto builtEngine = ext::make_shared( + fxQuoteCcy, fxQuoteDiscountCurve_, fxBaseCcy, fxBaseDiscountCurve_, engineSpotFX); + swap->setPricingEngine(builtEngine); + } + + return swap; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::payFxBaseCurrency(bool flag) { + type_ = flag ? MtMCrossCurrencyBasisSwap::Type::PayFxBaseCurrency : + MtMCrossCurrencyBasisSwap::Type::ReceiveFxBaseCurrency; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::receiveFxBaseCurrency(bool flag) { + type_ = flag ? MtMCrossCurrencyBasisSwap::Type::ReceiveFxBaseCurrency : + MtMCrossCurrencyBasisSwap::Type::PayFxBaseCurrency; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withType(MtMCrossCurrencyBasisSwap::Type type) { + type_ = type; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxBaseNominal(Real n) { + fxBaseNominal_ = n; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxQuoteNominal(Real n) { + fxQuoteNominal_ = n; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withFxSpot(const Handle& spotFX) { + fxSpot_ = spotFX; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withFxBaseCurrency(const Currency& ccy) { + fxBaseCurrency_ = ccy; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withFxQuoteCurrency(const Currency& ccy) { + fxQuoteCurrency_ = ccy; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withSettlementDays(Natural settlementDays) { + settlementDays_ = settlementDays; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withEffectiveDate(const Date& effectiveDate) { + effectiveDate_ = effectiveDate; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withTerminationDate(const Date& terminationDate) { + terminationDate_ = terminationDate; + if (terminationDate != Date()) + swapTenor_ = Period(); + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withCalendar( + const Calendar& cal) { + calendar_ = cal; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withFxBaseCalendar(const Calendar& cal) { + fxBaseCalendar_ = cal; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withFxQuoteCalendar(const Calendar& cal) { + fxQuoteCalendar_ = cal; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withPaymentConvention(BusinessDayConvention bdc) { + fxBasePaymentConvention_ = bdc; + fxQuotePaymentConvention_ = bdc; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withFxBasePaymentConvention(BusinessDayConvention bdc) { + fxBasePaymentConvention_ = bdc; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withFxQuotePaymentConvention(BusinessDayConvention bdc) { + fxQuotePaymentConvention_ = bdc; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withRule( + DateGeneration::Rule r) { + rule_ = r; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withEndOfMonth(bool flag) { + endOfMonth_ = flag; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxBaseFrequency( + Frequency f) { + fxBaseFrequency_ = f; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxQuoteFrequency( + Frequency f) { + fxQuoteFrequency_ = f; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxBaseSpread(Spread sp) { + fxBaseSpread_ = sp; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxQuoteSpread(Spread sp) { + fxQuoteSpread_ = sp; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxBaseGearing(Real g) { + fxBaseGearing_ = g; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxQuoteGearing(Real g) { + fxQuoteGearing_ = g; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxBasePaymentLag( + Integer lag) { + fxBasePaymentLag_ = lag; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxQuotePaymentLag( + Integer lag) { + fxQuotePaymentLag_ = lag; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withIndexedCoupons( + const std::optional& b) { + useIndexedCoupons_ = b; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withFxResetFixingDays(Natural fixingDays) { + fxResetFixingDays_ = fixingDays; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& + MakeMtMCrossCurrencyBasisSwap::withFxResetFixingCalendar(const Calendar& cal) { + fxResetFixingCalendar_ = cal; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxBaseCompoundSpread( + bool flag) { + fxBaseCompoundSpread_ = flag; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxQuoteCompoundSpread( + bool flag) { + fxQuoteCompoundSpread_ = flag; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxBaseLookbackDays( + Natural lookbackDays) { + fxBaseLookbackDays_ = lookbackDays; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxQuoteLookbackDays( + Natural lookbackDays) { + fxQuoteLookbackDays_ = lookbackDays; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxBaseLockoutDays( + Natural lockoutDays) { + fxBaseLockoutDays_ = lockoutDays; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxQuoteLockoutDays( + Natural lockoutDays) { + fxQuoteLockoutDays_ = lockoutDays; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxBaseObservationShift( + bool flag) { + fxBaseObservationShift_ = flag; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxQuoteObservationShift( + bool flag) { + fxQuoteObservationShift_ = flag; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxBaseAveragingMethod( + RateAveraging::Type averagingMethod) { + fxBaseAveragingMethod_ = averagingMethod; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxQuoteAveragingMethod( + RateAveraging::Type averagingMethod) { + fxQuoteAveragingMethod_ = averagingMethod; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withTelescopicValueDates( + bool flag) { + telescopicValueDates_ = flag; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxBaseDiscountCurve( + const Handle& fxBaseDiscountCurve) { + fxBaseDiscountCurve_ = fxBaseDiscountCurve; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withFxQuoteDiscountCurve( + const Handle& fxQuoteDiscountCurve) { + fxQuoteDiscountCurve_ = fxQuoteDiscountCurve; + return *this; + } + + MakeMtMCrossCurrencyBasisSwap& MakeMtMCrossCurrencyBasisSwap::withPricingEngine( + const ext::shared_ptr& engine) { + engine_ = engine; + return *this; + } + +} diff --git a/ql/experimental/fx/makemtmcrosscurrencybasisswap.hpp b/ql/experimental/fx/makemtmcrosscurrencybasisswap.hpp new file mode 100644 index 00000000000..7d463ac853f --- /dev/null +++ b/ql/experimental/fx/makemtmcrosscurrencybasisswap.hpp @@ -0,0 +1,172 @@ +/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + + QuantLib is free software: you can redistribute it and/or modify it + under the terms of the QuantLib license. You should have received a + copy of the license along with this program; if not, please email + . The license is also available online at + . + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +/*! \file makemtmcrosscurrencybasisswap.hpp + \brief Helper class to instantiate mark-to-market cross-currency basis swaps +*/ + +#ifndef quantlib_make_mtm_cross_currency_basis_swap_hpp +#define quantlib_make_mtm_cross_currency_basis_swap_hpp + +#include +#include +#include +#include +#include + +namespace QuantLib { + + //! helper class + /*! This class provides a more comfortable way to instantiate a + mark-to-market (resetting-notional) cross-currency basis swap and to + wire it up to a \c DiscountingMtMCrossCurrencyBasisSwapEngine. + + The FX-base leg is priced/quoted in the currency of \c fxBaseIndex and + the FX-quote leg in the currency of \c fxQuoteIndex, following the + naming convention of \c MtMCrossCurrencyBasisSwap. The FX spot rate + is expressed as units of the fx-quote currency per unit of the + fx-base currency, and is used both to derive the fx-quote notional + from the fx-base notional (unless explicitly overridden) and to build + the pricing engine. It is required whenever the two legs are in + different currencies, unless both the fx-quote notional and the + pricing engine are given explicitly. + + Payment dates default to the business-day convention of the + corresponding index, as in \c MakeVanillaSwap. + */ + class MakeMtMCrossCurrencyBasisSwap { + public: + MakeMtMCrossCurrencyBasisSwap(const Period& swapTenor, + ext::shared_ptr fxBaseIndex, + ext::shared_ptr fxQuoteIndex, + bool isFxBaseCurrencyLegResettable, + Real fxBaseNominal = 1.0, + const Period& forwardStart = 0 * Days); + + operator MtMCrossCurrencyBasisSwap() const; + operator ext::shared_ptr() const; + + MakeMtMCrossCurrencyBasisSwap& payFxBaseCurrency(bool flag = true); + MakeMtMCrossCurrencyBasisSwap& receiveFxBaseCurrency(bool flag = true); + MakeMtMCrossCurrencyBasisSwap& withType(MtMCrossCurrencyBasisSwap::Type type); + + MakeMtMCrossCurrencyBasisSwap& withFxBaseNominal(Real n); + MakeMtMCrossCurrencyBasisSwap& withFxQuoteNominal(Real n); + /*! units of the fx-quote currency for one unit of the fx-base + currency; used to derive the fx-quote notional if + \c withFxQuoteNominal was not called, and to build the pricing + engine. */ + MakeMtMCrossCurrencyBasisSwap& withFxSpot(const Handle& spotFX); + + MakeMtMCrossCurrencyBasisSwap& withFxBaseCurrency(const Currency& ccy); + MakeMtMCrossCurrencyBasisSwap& withFxQuoteCurrency(const Currency& ccy); + + MakeMtMCrossCurrencyBasisSwap& withSettlementDays(Natural settlementDays); + MakeMtMCrossCurrencyBasisSwap& withEffectiveDate(const Date& effectiveDate); + MakeMtMCrossCurrencyBasisSwap& withTerminationDate(const Date& terminationDate); + MakeMtMCrossCurrencyBasisSwap& withCalendar(const Calendar& cal); + MakeMtMCrossCurrencyBasisSwap& withFxBaseCalendar(const Calendar& cal); + MakeMtMCrossCurrencyBasisSwap& withFxQuoteCalendar(const Calendar& cal); + MakeMtMCrossCurrencyBasisSwap& withPaymentConvention(BusinessDayConvention bdc); + MakeMtMCrossCurrencyBasisSwap& withFxBasePaymentConvention(BusinessDayConvention bdc); + MakeMtMCrossCurrencyBasisSwap& withFxQuotePaymentConvention(BusinessDayConvention bdc); + MakeMtMCrossCurrencyBasisSwap& withRule(DateGeneration::Rule r); + MakeMtMCrossCurrencyBasisSwap& withEndOfMonth(bool flag = true); + //! payment frequency for the fx-base leg; defaults to the fx-base index tenor + MakeMtMCrossCurrencyBasisSwap& withFxBaseFrequency(Frequency f); + //! payment frequency for the fx-quote leg; defaults to the fx-quote index tenor + MakeMtMCrossCurrencyBasisSwap& withFxQuoteFrequency(Frequency f); + + MakeMtMCrossCurrencyBasisSwap& withFxBaseSpread(Spread sp); + MakeMtMCrossCurrencyBasisSwap& withFxQuoteSpread(Spread sp); + MakeMtMCrossCurrencyBasisSwap& withFxBaseGearing(Real g); + MakeMtMCrossCurrencyBasisSwap& withFxQuoteGearing(Real g); + MakeMtMCrossCurrencyBasisSwap& withFxBasePaymentLag(Integer lag); + MakeMtMCrossCurrencyBasisSwap& withFxQuotePaymentLag(Integer lag); + MakeMtMCrossCurrencyBasisSwap& withIndexedCoupons(const std::optional& b = true); + + MakeMtMCrossCurrencyBasisSwap& withFxResetFixingDays(Natural fixingDays); + MakeMtMCrossCurrencyBasisSwap& withFxResetFixingCalendar(const Calendar& cal); + + //! \name overnight-index-only leg conventions + //@{ + MakeMtMCrossCurrencyBasisSwap& withFxBaseCompoundSpread(bool flag = true); + MakeMtMCrossCurrencyBasisSwap& withFxQuoteCompoundSpread(bool flag = true); + MakeMtMCrossCurrencyBasisSwap& withFxBaseLookbackDays(Natural lookbackDays); + MakeMtMCrossCurrencyBasisSwap& withFxQuoteLookbackDays(Natural lookbackDays); + MakeMtMCrossCurrencyBasisSwap& withFxBaseLockoutDays(Natural lockoutDays); + MakeMtMCrossCurrencyBasisSwap& withFxQuoteLockoutDays(Natural lockoutDays); + MakeMtMCrossCurrencyBasisSwap& withFxBaseObservationShift(bool flag = true); + MakeMtMCrossCurrencyBasisSwap& withFxQuoteObservationShift(bool flag = true); + MakeMtMCrossCurrencyBasisSwap& + withFxBaseAveragingMethod(RateAveraging::Type averagingMethod); + MakeMtMCrossCurrencyBasisSwap& + withFxQuoteAveragingMethod(RateAveraging::Type averagingMethod); + MakeMtMCrossCurrencyBasisSwap& withTelescopicValueDates(bool flag = true); + //@} + + MakeMtMCrossCurrencyBasisSwap& + withFxBaseDiscountCurve(const Handle& fxBaseDiscountCurve); + MakeMtMCrossCurrencyBasisSwap& + withFxQuoteDiscountCurve(const Handle& fxQuoteDiscountCurve); + MakeMtMCrossCurrencyBasisSwap& + withPricingEngine(const ext::shared_ptr& engine); + + private: + Period swapTenor_; + ext::shared_ptr fxBaseIndex_, fxQuoteIndex_; + bool isFxBaseCurrencyLegResettable_; + Real fxBaseNominal_; + Period forwardStart_; + + MtMCrossCurrencyBasisSwap::Type type_ = MtMCrossCurrencyBasisSwap::Type::PayFxBaseCurrency; + Real fxQuoteNominal_ = Null(); + Handle fxSpot_; + + Currency fxBaseCurrency_, fxQuoteCurrency_; + + Natural settlementDays_ = Null(); + Date effectiveDate_, terminationDate_; + Calendar calendar_, fxBaseCalendar_, fxQuoteCalendar_; + BusinessDayConvention fxBasePaymentConvention_, fxQuotePaymentConvention_; + DateGeneration::Rule rule_ = DateGeneration::Backward; + bool endOfMonth_ = false; + std::optional fxBaseFrequency_, fxQuoteFrequency_; + + Spread fxBaseSpread_ = 0.0, fxQuoteSpread_ = 0.0; + Real fxBaseGearing_ = 1.0, fxQuoteGearing_ = 1.0; + Integer fxBasePaymentLag_ = 0, fxQuotePaymentLag_ = 0; + std::optional useIndexedCoupons_; + + Natural fxResetFixingDays_ = 0; + Calendar fxResetFixingCalendar_; + + bool fxBaseCompoundSpread_ = false, fxQuoteCompoundSpread_ = false; + Natural fxBaseLookbackDays_ = Null(), fxQuoteLookbackDays_ = Null(); + bool fxBaseObservationShift_ = false, fxQuoteObservationShift_ = false; + Natural fxBaseLockoutDays_ = 0, fxQuoteLockoutDays_ = 0; + RateAveraging::Type fxBaseAveragingMethod_ = RateAveraging::Compound, + fxQuoteAveragingMethod_ = RateAveraging::Compound; + bool telescopicValueDates_ = false; + + Handle fxBaseDiscountCurve_, fxQuoteDiscountCurve_; + ext::shared_ptr engine_; + }; + +} + +#endif diff --git a/ql/instruments/Makefile.am b/ql/instruments/Makefile.am index e1734d8b036..310a7e11a9d 100644 --- a/ql/instruments/Makefile.am +++ b/ql/instruments/Makefile.am @@ -49,6 +49,9 @@ this_include_HEADERS = \ makecapfloor.hpp \ makecds.hpp \ makecms.hpp \ + makeconstnotionalcrosscurrencybasisswap.hpp \ + makeconstnotionalcrosscurrencyfixedvsfloatingswap.hpp \ + makeconstnotionalcrosscurrencyswap.hpp \ makemultipleresetsswap.hpp \ makeois.hpp \ makeswaption.hpp \ @@ -128,6 +131,9 @@ cpp_files = \ makecapfloor.cpp \ makecds.cpp \ makecms.cpp \ + makeconstnotionalcrosscurrencybasisswap.cpp \ + makeconstnotionalcrosscurrencyfixedvsfloatingswap.cpp \ + makeconstnotionalcrosscurrencyswap.cpp \ makemultipleresetsswap.cpp \ makeois.cpp \ makeswaption.cpp \ diff --git a/ql/instruments/all.hpp b/ql/instruments/all.hpp index 50a4ead7968..6426ff277ee 100644 --- a/ql/instruments/all.hpp +++ b/ql/instruments/all.hpp @@ -44,6 +44,9 @@ #include #include #include +#include +#include +#include #include #include #include diff --git a/ql/instruments/makeconstnotionalcrosscurrencybasisswap.cpp b/ql/instruments/makeconstnotionalcrosscurrencybasisswap.cpp new file mode 100644 index 00000000000..67c96e8d0b8 --- /dev/null +++ b/ql/instruments/makeconstnotionalcrosscurrencybasisswap.cpp @@ -0,0 +1,402 @@ +/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + + QuantLib is free software: you can redistribute it and/or modify it + under the terms of the QuantLib license. You should have received a + copy of the license along with this program; if not, please email + . The license is also available online at + . + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include + +namespace QuantLib { + + namespace { + + Calendar effectiveCalendar(const Calendar& legCalendar, + const Calendar& sharedCalendar, + const ext::shared_ptr& index) { + if (!legCalendar.empty()) + return legCalendar; + if (!sharedCalendar.empty()) + return sharedCalendar; + return index->fixingCalendar(); + } + + } + + MakeConstNotionalCrossCurrencyBasisSwap::MakeConstNotionalCrossCurrencyBasisSwap( + const Period& swapTenor, + ext::shared_ptr payIndex, + ext::shared_ptr recIndex, + Real payNominal, + const Period& forwardStart) + : swapTenor_(swapTenor), payIndex_(std::move(payIndex)), recIndex_(std::move(recIndex)), + payNominal_(payNominal), forwardStart_(forwardStart), + payConvention_(payIndex_->businessDayConvention()), + recConvention_(recIndex_->businessDayConvention()) {} + + MakeConstNotionalCrossCurrencyBasisSwap::operator ConstNotionalCrossCurrencyBasisSwap() const { + ext::shared_ptr swap = *this; + return *swap; + } + + MakeConstNotionalCrossCurrencyBasisSwap:: + operator ext::shared_ptr() const { + + QL_REQUIRE(effectiveDate_ == Date() || settlementDays_ == Null(), + "cannot set both an explicit effective date and settlement days; " + "use one or the other"); + + Currency payCcy = payCurrency_ != Currency() ? payCurrency_ : payIndex_->currency(); + Currency recCcy = recCurrency_ != Currency() ? recCurrency_ : recIndex_->currency(); + + Calendar jointCalendar = calendar_.empty() ? + JointCalendar(payIndex_->fixingCalendar(), + recIndex_->fixingCalendar()) : + calendar_; + + Date startDate; + if (effectiveDate_ != Date()) { + startDate = effectiveDate_; + } else { + Date refDate = Settings::instance().evaluationDate(); + refDate = jointCalendar.adjust(refDate); + Natural settlementDays = settlementDays_ == Null() ? + std::max(payIndex_->fixingDays(), + recIndex_->fixingDays()) : + settlementDays_; + Date spotDate = jointCalendar.advance(refDate, settlementDays * Days); + startDate = spotDate + forwardStart_; + if (forwardStart_.length() < 0) + startDate = jointCalendar.adjust(startDate, Preceding); + else if (forwardStart_.length() > 0) + startDate = jointCalendar.adjust(startDate, Following); + } + + Date endDate = terminationDate_ != Date() ? terminationDate_ : startDate + swapTenor_; + + Calendar payCal = effectiveCalendar(payCalendar_, calendar_, payIndex_); + Calendar recCal = effectiveCalendar(recCalendar_, calendar_, recIndex_); + + Period payFreq = payFrequency_ ? Period(*payFrequency_) : payIndex_->tenor(); + Period recFreq = recFrequency_ ? Period(*recFrequency_) : recIndex_->tenor(); + + Schedule paySchedule = MakeSchedule() + .from(startDate) + .to(endDate) + .withTenor(payFreq) + .withCalendar(payCal) + .withConvention(payConvention_) + .withRule(rule_) + .endOfMonth(endOfMonth_); + + Schedule recSchedule = MakeSchedule() + .from(startDate) + .to(endDate) + .withTenor(recFreq) + .withCalendar(recCal) + .withConvention(recConvention_) + .withRule(rule_) + .endOfMonth(endOfMonth_); + + Real recNominal; + if (recNominal_ != Null()) { + recNominal = recNominal_; + } else if (!fxSpot_.empty()) { + QL_REQUIRE(fxSpot_->value() > 0.0, + "the FX spot quote must be positive; got " << fxSpot_->value()); + recNominal = payNominal_ * fxSpot_->value(); + } else { + QL_REQUIRE(payCcy == recCcy, + "an FX spot quote is required to derive the " << recCcy.code() + << " receive notional from the " << payCcy.code() + << " pay notional; use withFxSpot() or withRecNominal()"); + recNominal = payNominal_; + } + + auto swap = ext::make_shared( + payNominal_, payCcy, paySchedule, payIndex_, paySpread_, payGearing_, recNominal, + recCcy, recSchedule, recIndex_, recSpread_, recGearing_, payPaymentLag_, + recPaymentLag_, payCompoundSpread_, payLookbackDays_, payObservationShift_, + payLockoutDays_, payAveragingMethod_, recCompoundSpread_, recLookbackDays_, + recObservationShift_, recLockoutDays_, recAveragingMethod_, telescopicValueDates_, + useIndexedCoupons_); + + if (engine_ != nullptr) { + swap->setPricingEngine(engine_); + } else if (!payDiscountCurve_.empty() || !recDiscountCurve_.empty()) { + QL_REQUIRE(!payDiscountCurve_.empty() && !recDiscountCurve_.empty(), + "both discount curves are needed to build the pricing engine, but only " + "the " + << (payDiscountCurve_.empty() ? recCcy.code() : payCcy.code()) + << " one was given; use withPayDiscountCurve() and " + "withRecDiscountCurve()"); + QL_REQUIRE(!fxSpot_.empty() || payCcy == recCcy, + "an FX spot quote is required to build the pricing engine for a " + << payCcy.code() << "/" << recCcy.code() + << " swap; use withFxSpot() or supply a custom engine with " + "withPricingEngine()"); + Handle engineSpotFX = fxSpot_.empty() ? makeQuoteHandle(1.0) : fxSpot_; + auto builtEngine = ext::make_shared( + recCcy, recDiscountCurve_, payCcy, payDiscountCurve_, engineSpotFX); + swap->setPricingEngine(builtEngine); + } + + return swap; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPayNominal(Real n) { + payNominal_ = n; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecNominal(Real n) { + recNominal_ = n; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withFxSpot(const Handle& spotFX) { + fxSpot_ = spotFX; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPayCurrency(const Currency& ccy) { + payCurrency_ = ccy; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecCurrency(const Currency& ccy) { + recCurrency_ = ccy; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withSettlementDays(Natural settlementDays) { + settlementDays_ = settlementDays; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withEffectiveDate(const Date& effectiveDate) { + effectiveDate_ = effectiveDate; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withTerminationDate(const Date& terminationDate) { + terminationDate_ = terminationDate; + if (terminationDate != Date()) + swapTenor_ = Period(); + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withCalendar(const Calendar& cal) { + calendar_ = cal; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPayCalendar(const Calendar& cal) { + payCalendar_ = cal; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecCalendar(const Calendar& cal) { + recCalendar_ = cal; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPaymentConvention(BusinessDayConvention bdc) { + payConvention_ = bdc; + recConvention_ = bdc; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPayPaymentConvention(BusinessDayConvention bdc) { + payConvention_ = bdc; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecPaymentConvention(BusinessDayConvention bdc) { + recConvention_ = bdc; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRule(DateGeneration::Rule r) { + rule_ = r; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withEndOfMonth(bool flag) { + endOfMonth_ = flag; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPayFrequency(Frequency f) { + payFrequency_ = f; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecFrequency(Frequency f) { + recFrequency_ = f; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPaySpread(Spread sp) { + paySpread_ = sp; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecSpread(Spread sp) { + recSpread_ = sp; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPayGearing(Real g) { + payGearing_ = g; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecGearing(Real g) { + recGearing_ = g; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPayPaymentLag(Integer lag) { + payPaymentLag_ = lag; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecPaymentLag(Integer lag) { + recPaymentLag_ = lag; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withIndexedCoupons(const std::optional& b) { + useIndexedCoupons_ = b; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPayCompoundSpread(bool flag) { + payCompoundSpread_ = flag; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecCompoundSpread(bool flag) { + recCompoundSpread_ = flag; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPayLookbackDays(Natural lookbackDays) { + payLookbackDays_ = lookbackDays; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecLookbackDays(Natural lookbackDays) { + recLookbackDays_ = lookbackDays; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPayLockoutDays(Natural lockoutDays) { + payLockoutDays_ = lockoutDays; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecLockoutDays(Natural lockoutDays) { + recLockoutDays_ = lockoutDays; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPayObservationShift(bool flag) { + payObservationShift_ = flag; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecObservationShift(bool flag) { + recObservationShift_ = flag; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withPayAveragingMethod( + RateAveraging::Type averagingMethod) { + payAveragingMethod_ = averagingMethod; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withRecAveragingMethod( + RateAveraging::Type averagingMethod) { + recAveragingMethod_ = averagingMethod; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& + MakeConstNotionalCrossCurrencyBasisSwap::withTelescopicValueDates(bool flag) { + telescopicValueDates_ = flag; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& MakeConstNotionalCrossCurrencyBasisSwap:: + withPayDiscountCurve(const Handle& payDiscountCurve) { + payDiscountCurve_ = payDiscountCurve; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& MakeConstNotionalCrossCurrencyBasisSwap:: + withRecDiscountCurve(const Handle& recDiscountCurve) { + recDiscountCurve_ = recDiscountCurve; + return *this; + } + + MakeConstNotionalCrossCurrencyBasisSwap& MakeConstNotionalCrossCurrencyBasisSwap:: + withPricingEngine(const ext::shared_ptr& engine) { + engine_ = engine; + return *this; + } + +} diff --git a/ql/instruments/makeconstnotionalcrosscurrencybasisswap.hpp b/ql/instruments/makeconstnotionalcrosscurrencybasisswap.hpp new file mode 100644 index 00000000000..9cd5ef7445e --- /dev/null +++ b/ql/instruments/makeconstnotionalcrosscurrencybasisswap.hpp @@ -0,0 +1,162 @@ +/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + + QuantLib is free software: you can redistribute it and/or modify it + under the terms of the QuantLib license. You should have received a + copy of the license along with this program; if not, please email + . The license is also available online at + . + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +/*! \file makeconstnotionalcrosscurrencybasisswap.hpp + \brief Helper class to instantiate constant-notional cross-currency basis swaps +*/ + +#ifndef quantlib_make_const_notional_cross_currency_basis_swap_hpp +#define quantlib_make_const_notional_cross_currency_basis_swap_hpp + +#include +#include +#include +#include +#include + +namespace QuantLib { + + //! helper class + /*! This class provides a more comfortable way to instantiate a + constant-notional, float-vs-float cross-currency basis swap (both legs + built from a tenor and an index) and to wire it up to a + \c DiscountingConstNotionalCrossCurrencySwapEngine. + + The pay leg is priced/quoted in the currency of \c payIndex and the + receive leg in the currency of \c recIndex, following the naming + convention of \c ConstNotionalCrossCurrencyBasisSwap. The FX spot + rate is expressed as units of the receive currency per unit of the + pay currency, and is used both to derive the receive notional from + the pay notional (unless explicitly overridden) and to build the + pricing engine. It is required whenever the two legs are in + different currencies, unless both the receive notional and the + pricing engine are given explicitly. + + Payment dates default to the business-day convention of the + corresponding index, as in \c MakeVanillaSwap. + */ + class MakeConstNotionalCrossCurrencyBasisSwap { + public: + MakeConstNotionalCrossCurrencyBasisSwap(const Period& swapTenor, + ext::shared_ptr payIndex, + ext::shared_ptr recIndex, + Real payNominal = 1.0, + const Period& forwardStart = 0 * Days); + + operator ConstNotionalCrossCurrencyBasisSwap() const; + operator ext::shared_ptr() const; + + MakeConstNotionalCrossCurrencyBasisSwap& withPayNominal(Real n); + MakeConstNotionalCrossCurrencyBasisSwap& withRecNominal(Real n); + /*! units of the receive currency for one unit of the pay currency; + used to derive the receive notional if \c withRecNominal was not + called, and to build the pricing engine. */ + MakeConstNotionalCrossCurrencyBasisSwap& withFxSpot(const Handle& spotFX); + + MakeConstNotionalCrossCurrencyBasisSwap& withPayCurrency(const Currency& ccy); + MakeConstNotionalCrossCurrencyBasisSwap& withRecCurrency(const Currency& ccy); + + MakeConstNotionalCrossCurrencyBasisSwap& withSettlementDays(Natural settlementDays); + MakeConstNotionalCrossCurrencyBasisSwap& withEffectiveDate(const Date& effectiveDate); + MakeConstNotionalCrossCurrencyBasisSwap& withTerminationDate(const Date& terminationDate); + MakeConstNotionalCrossCurrencyBasisSwap& withCalendar(const Calendar& cal); + MakeConstNotionalCrossCurrencyBasisSwap& withPayCalendar(const Calendar& cal); + MakeConstNotionalCrossCurrencyBasisSwap& withRecCalendar(const Calendar& cal); + MakeConstNotionalCrossCurrencyBasisSwap& withPaymentConvention(BusinessDayConvention bdc); + MakeConstNotionalCrossCurrencyBasisSwap& + withPayPaymentConvention(BusinessDayConvention bdc); + MakeConstNotionalCrossCurrencyBasisSwap& + withRecPaymentConvention(BusinessDayConvention bdc); + MakeConstNotionalCrossCurrencyBasisSwap& withRule(DateGeneration::Rule r); + MakeConstNotionalCrossCurrencyBasisSwap& withEndOfMonth(bool flag = true); + //! payment frequency for the pay leg; defaults to the pay index tenor + MakeConstNotionalCrossCurrencyBasisSwap& withPayFrequency(Frequency f); + //! payment frequency for the rec leg; defaults to the rec index tenor + MakeConstNotionalCrossCurrencyBasisSwap& withRecFrequency(Frequency f); + + MakeConstNotionalCrossCurrencyBasisSwap& withPaySpread(Spread sp); + MakeConstNotionalCrossCurrencyBasisSwap& withRecSpread(Spread sp); + MakeConstNotionalCrossCurrencyBasisSwap& withPayGearing(Real g); + MakeConstNotionalCrossCurrencyBasisSwap& withRecGearing(Real g); + MakeConstNotionalCrossCurrencyBasisSwap& withPayPaymentLag(Integer lag); + MakeConstNotionalCrossCurrencyBasisSwap& withRecPaymentLag(Integer lag); + MakeConstNotionalCrossCurrencyBasisSwap& + withIndexedCoupons(const std::optional& b = true); + + //! \name overnight-index-only leg conventions + //@{ + MakeConstNotionalCrossCurrencyBasisSwap& withPayCompoundSpread(bool flag = true); + MakeConstNotionalCrossCurrencyBasisSwap& withRecCompoundSpread(bool flag = true); + MakeConstNotionalCrossCurrencyBasisSwap& withPayLookbackDays(Natural lookbackDays); + MakeConstNotionalCrossCurrencyBasisSwap& withRecLookbackDays(Natural lookbackDays); + MakeConstNotionalCrossCurrencyBasisSwap& withPayLockoutDays(Natural lockoutDays); + MakeConstNotionalCrossCurrencyBasisSwap& withRecLockoutDays(Natural lockoutDays); + MakeConstNotionalCrossCurrencyBasisSwap& withPayObservationShift(bool flag = true); + MakeConstNotionalCrossCurrencyBasisSwap& withRecObservationShift(bool flag = true); + MakeConstNotionalCrossCurrencyBasisSwap& + withPayAveragingMethod(RateAveraging::Type averagingMethod); + MakeConstNotionalCrossCurrencyBasisSwap& + withRecAveragingMethod(RateAveraging::Type averagingMethod); + MakeConstNotionalCrossCurrencyBasisSwap& withTelescopicValueDates(bool flag = true); + //@} + + MakeConstNotionalCrossCurrencyBasisSwap& + withPayDiscountCurve(const Handle& payDiscountCurve); + MakeConstNotionalCrossCurrencyBasisSwap& + withRecDiscountCurve(const Handle& recDiscountCurve); + MakeConstNotionalCrossCurrencyBasisSwap& + withPricingEngine(const ext::shared_ptr& engine); + + private: + Period swapTenor_; + ext::shared_ptr payIndex_, recIndex_; + Real payNominal_; + Period forwardStart_; + + Real recNominal_ = Null(); + Handle fxSpot_; + + Currency payCurrency_, recCurrency_; + + Natural settlementDays_ = Null(); + Date effectiveDate_, terminationDate_; + Calendar calendar_, payCalendar_, recCalendar_; + BusinessDayConvention payConvention_, recConvention_; + DateGeneration::Rule rule_ = DateGeneration::Backward; + bool endOfMonth_ = false; + std::optional payFrequency_, recFrequency_; + + Spread paySpread_ = 0.0, recSpread_ = 0.0; + Real payGearing_ = 1.0, recGearing_ = 1.0; + Integer payPaymentLag_ = 0, recPaymentLag_ = 0; + std::optional useIndexedCoupons_; + + bool payCompoundSpread_ = false, recCompoundSpread_ = false; + Natural payLookbackDays_ = Null(), recLookbackDays_ = Null(); + bool payObservationShift_ = false, recObservationShift_ = false; + Natural payLockoutDays_ = 0, recLockoutDays_ = 0; + RateAveraging::Type payAveragingMethod_ = RateAveraging::Compound, + recAveragingMethod_ = RateAveraging::Compound; + bool telescopicValueDates_ = false; + + Handle payDiscountCurve_, recDiscountCurve_; + ext::shared_ptr engine_; + }; + +} + +#endif diff --git a/ql/instruments/makeconstnotionalcrosscurrencyfixedvsfloatingswap.cpp b/ql/instruments/makeconstnotionalcrosscurrencyfixedvsfloatingswap.cpp new file mode 100644 index 00000000000..ba883e9c49c --- /dev/null +++ b/ql/instruments/makeconstnotionalcrosscurrencyfixedvsfloatingswap.cpp @@ -0,0 +1,389 @@ +/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + + QuantLib is free software: you can redistribute it and/or modify it + under the terms of the QuantLib license. You should have received a + copy of the license along with this program; if not, please email + . The license is also available online at + . + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include + +namespace QuantLib { + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap:: + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap(const Period& swapTenor, + Currency fixedCurrency, + ext::shared_ptr floatIndex, + Rate fixedRate, + Real floatNominal, + const Period& forwardStart) + : swapTenor_(swapTenor), fixedCurrency_(std::move(fixedCurrency)), + floatIndex_(std::move(floatIndex)), fixedRate_(fixedRate), floatNominal_(floatNominal), + forwardStart_(forwardStart) {} + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap:: + operator ConstNotionalCrossCurrencyFixedVsFloatingSwap() const { + ext::shared_ptr swap = *this; + return *swap; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap:: + operator ext::shared_ptr() const { + + QL_REQUIRE(effectiveDate_ == Date() || settlementDays_ == Null(), + "cannot set both an explicit effective date and settlement days; " + "use one or the other"); + + Currency floatCcy = floatCurrency_ != Currency() ? floatCurrency_ : floatIndex_->currency(); + + Calendar sharedCalendar = calendar_.empty() ? floatIndex_->fixingCalendar() : calendar_; + + Date startDate; + if (effectiveDate_ != Date()) { + startDate = effectiveDate_; + } else { + Date refDate = Settings::instance().evaluationDate(); + refDate = sharedCalendar.adjust(refDate); + Natural settlementDays = + settlementDays_ == Null() ? floatIndex_->fixingDays() : settlementDays_; + Date spotDate = sharedCalendar.advance(refDate, settlementDays * Days); + startDate = spotDate + forwardStart_; + if (forwardStart_.length() < 0) + startDate = sharedCalendar.adjust(startDate, Preceding); + else if (forwardStart_.length() > 0) + startDate = sharedCalendar.adjust(startDate, Following); + } + + Date endDate = terminationDate_ != Date() ? terminationDate_ : startDate + swapTenor_; + + Calendar fixedCal = fixedPaymentCalendar_.empty() ? sharedCalendar : fixedPaymentCalendar_; + Calendar floatCal = floatPaymentCalendar_.empty() ? sharedCalendar : floatPaymentCalendar_; + + Period fixedFreq = fixedFrequency_ ? Period(*fixedFrequency_) : Period(Annual); + Period floatFreq = floatFrequency_ ? Period(*floatFrequency_) : floatIndex_->tenor(); + + Schedule fixedSchedule = MakeSchedule() + .from(startDate) + .to(endDate) + .withTenor(fixedFreq) + .withCalendar(fixedCal) + .withConvention(fixedPaymentConvention_) + .withRule(rule_) + .endOfMonth(endOfMonth_); + + BusinessDayConvention floatConvention = + floatPaymentConvention_ ? *floatPaymentConvention_ : floatIndex_->businessDayConvention(); + + Schedule floatSchedule = MakeSchedule() + .from(startDate) + .to(endDate) + .withTenor(floatFreq) + .withCalendar(floatCal) + .withConvention(floatConvention) + .withRule(rule_) + .endOfMonth(endOfMonth_); + + DayCounter fixedDayCount = fixedDayCount_ != DayCounter() ? fixedDayCount_ : Actual365Fixed(); + DayCounter floatDayCount = floatDayCount_ != DayCounter() ? floatDayCount_ : floatIndex_->dayCounter(); + + // the spot quote is units of the floating currency per unit of the + // fixed currency, so the floating notional is divided by it. + Real fixedNominal; + if (fixedNominal_ != Null()) { + fixedNominal = fixedNominal_; + } else if (!fxSpot_.empty()) { + QL_REQUIRE(fxSpot_->value() > 0.0, + "the FX spot quote must be positive; got " << fxSpot_->value()); + fixedNominal = floatNominal_ / fxSpot_->value(); + } else { + QL_REQUIRE(fixedCurrency_ == floatCcy, + "an FX spot quote is required to derive the " << fixedCurrency_.code() + << " fixed notional from the " << floatCcy.code() + << " floating notional; use withFxSpot() or withFixedNominal()"); + fixedNominal = floatNominal_; + } + + ext::shared_ptr engineToUse = engine_; + if (engineToUse == nullptr && + (!fixedDiscountCurve_.empty() || !floatDiscountCurve_.empty())) { + QL_REQUIRE(!fixedDiscountCurve_.empty() && !floatDiscountCurve_.empty(), + "both discount curves are needed to build the pricing engine, but only " + "the " + << (fixedDiscountCurve_.empty() ? floatCcy.code() : + fixedCurrency_.code()) + << " one was given; use withFixedDiscountCurve() and " + "withFloatDiscountCurve()"); + QL_REQUIRE(!fxSpot_.empty() || fixedCurrency_ == floatCcy, + "an FX spot quote is required to build the pricing engine for a " + << fixedCurrency_.code() << "/" << floatCcy.code() + << " swap; use withFxSpot() or supply a custom engine with " + "withPricingEngine()"); + Handle engineSpotFX = fxSpot_.empty() ? makeQuoteHandle(1.0) : fxSpot_; + engineToUse = ext::make_shared( + floatCcy, floatDiscountCurve_, fixedCurrency_, fixedDiscountCurve_, engineSpotFX); + } + + Rate usedFixedRate = fixedRate_; + if (fixedRate_ == Null()) { + QL_REQUIRE(engineToUse != nullptr, + "fixed rate is null and no pricing engine (or pair of discount curves) " + "was provided to compute the fair rate"); + auto temp = ext::make_shared( + type_, fixedNominal, fixedCurrency_, fixedSchedule, 0.0, fixedDayCount, + fixedPaymentConvention_, fixedPaymentLag_, fixedCal, floatNominal_, floatCcy, + floatSchedule, floatIndex_, floatSpread_, floatConvention, floatPaymentLag_, + floatCal, telescopicValueDates_, floatCompoundSpread_, floatLookbackDays_, + floatObservationShift_, floatLockoutDays_, floatAveragingMethod_, + useIndexedCoupons_); + temp->setPricingEngine(engineToUse); + usedFixedRate = temp->fairRate(); + } + + auto swap = ext::make_shared( + type_, fixedNominal, fixedCurrency_, fixedSchedule, usedFixedRate, fixedDayCount, + fixedPaymentConvention_, fixedPaymentLag_, fixedCal, floatNominal_, floatCcy, + floatSchedule, floatIndex_, floatSpread_, floatConvention, floatPaymentLag_, floatCal, + telescopicValueDates_, floatCompoundSpread_, floatLookbackDays_, + floatObservationShift_, floatLockoutDays_, floatAveragingMethod_, useIndexedCoupons_); + + if (engineToUse != nullptr) + swap->setPricingEngine(engineToUse); + + return swap; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::payFixed(bool flag) { + type_ = flag ? Swap::Payer : Swap::Receiver; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::receiveFixed(bool flag) { + type_ = flag ? Swap::Receiver : Swap::Payer; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withType(Swap::Type type) { + type_ = type; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFixedNominal(Real n) { + fixedNominal_ = n; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatNominal(Real n) { + floatNominal_ = n; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFxSpot(const Handle& spotFX) { + fxSpot_ = spotFX; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatCurrency(const Currency& ccy) { + floatCurrency_ = ccy; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withSettlementDays( + Natural settlementDays) { + settlementDays_ = settlementDays; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withEffectiveDate( + const Date& effectiveDate) { + effectiveDate_ = effectiveDate; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withTerminationDate( + const Date& terminationDate) { + terminationDate_ = terminationDate; + if (terminationDate != Date()) + swapTenor_ = Period(); + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withCalendar(const Calendar& cal) { + calendar_ = cal; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withRule(DateGeneration::Rule r) { + rule_ = r; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withEndOfMonth(bool flag) { + endOfMonth_ = flag; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFixedFrequency(Frequency f) { + fixedFrequency_ = f; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFixedDayCount(const DayCounter& dc) { + fixedDayCount_ = dc; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFixedPaymentConvention( + BusinessDayConvention bdc) { + fixedPaymentConvention_ = bdc; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFixedPaymentLag(Natural lag) { + fixedPaymentLag_ = lag; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFixedPaymentCalendar( + const Calendar& cal) { + fixedPaymentCalendar_ = cal; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatFrequency(Frequency f) { + floatFrequency_ = f; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatDayCount(const DayCounter& dc) { + floatDayCount_ = dc; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatSpread(Spread sp) { + floatSpread_ = sp; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatPaymentConvention( + BusinessDayConvention bdc) { + floatPaymentConvention_ = bdc; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatPaymentLag(Natural lag) { + floatPaymentLag_ = lag; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatPaymentCalendar( + const Calendar& cal) { + floatPaymentCalendar_ = cal; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withIndexedCoupons( + const std::optional& b) { + useIndexedCoupons_ = b; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatCompoundSpread(bool flag) { + floatCompoundSpread_ = flag; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatLookbackDays( + Natural lookbackDays) { + floatLookbackDays_ = lookbackDays; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatLockoutDays(Natural lockoutDays) { + floatLockoutDays_ = lockoutDays; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatObservationShift(bool flag) { + floatObservationShift_ = flag; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatAveragingMethod( + RateAveraging::Type averagingMethod) { + floatAveragingMethod_ = averagingMethod; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withTelescopicValueDates(bool flag) { + telescopicValueDates_ = flag; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFixedDiscountCurve( + const Handle& fixedDiscountCurve) { + fixedDiscountCurve_ = fixedDiscountCurve; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withFloatDiscountCurve( + const Handle& floatDiscountCurve) { + floatDiscountCurve_ = floatDiscountCurve; + return *this; + } + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap::withPricingEngine( + const ext::shared_ptr& engine) { + engine_ = engine; + return *this; + } + +} diff --git a/ql/instruments/makeconstnotionalcrosscurrencyfixedvsfloatingswap.hpp b/ql/instruments/makeconstnotionalcrosscurrencyfixedvsfloatingswap.hpp new file mode 100644 index 00000000000..404e5e3de8e --- /dev/null +++ b/ql/instruments/makeconstnotionalcrosscurrencyfixedvsfloatingswap.hpp @@ -0,0 +1,181 @@ +/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + + QuantLib is free software: you can redistribute it and/or modify it + under the terms of the QuantLib license. You should have received a + copy of the license along with this program; if not, please email + . The license is also available online at + . + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +/*! \file makeconstnotionalcrosscurrencyfixedvsfloatingswap.hpp + \brief Helper class to instantiate constant-notional cross-currency + fixed-vs-floating swaps +*/ + +#ifndef quantlib_make_const_notional_cross_currency_fixed_vs_floating_swap_hpp +#define quantlib_make_const_notional_cross_currency_fixed_vs_floating_swap_hpp + +#include +#include +#include +#include +#include +#include + +namespace QuantLib { + + //! helper class + /*! This class provides a more comfortable way to instantiate a + constant-notional cross-currency fixed-vs-floating swap and to wire + it up to a \c DiscountingConstNotionalCrossCurrencySwapEngine. + + If the fixed rate is left unspecified (the default, \c Null()), + it is solved for as the fair rate of the swap once a pricing engine + (or a pair of discount curves and an FX spot) has been supplied, + mirroring the at-market convenience of \c MakeVanillaSwap. + + The FX spot rate is expressed as units of the floating currency per + unit of the fixed currency, so that the fixed notional is the + floating one divided by the spot (unless explicitly overridden); it + is also used to build the pricing engine. It is required whenever + the two legs are in different currencies, unless both the fixed + notional and the pricing engine are given explicitly. + */ + class MakeConstNotionalCrossCurrencyFixedVsFloatingSwap { + public: + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap( + const Period& swapTenor, + Currency fixedCurrency, + ext::shared_ptr floatIndex, + Rate fixedRate = Null(), + Real floatNominal = 1.0, + const Period& forwardStart = 0 * Days); + + operator ConstNotionalCrossCurrencyFixedVsFloatingSwap() const; + operator ext::shared_ptr() const; + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& payFixed(bool flag = true); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& receiveFixed(bool flag = true); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& withType(Swap::Type type); + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& withFixedNominal(Real n); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& withFloatNominal(Real n); + /*! units of the floating currency for one unit of the fixed + currency; the floating notional is divided by it to derive the + fixed notional if \c withFixedNominal was not called, and it is + also used to build the pricing engine. */ + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& withFxSpot(const Handle& spotFX); + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& withFloatCurrency(const Currency& ccy); + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withSettlementDays(Natural settlementDays); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withEffectiveDate(const Date& effectiveDate); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withTerminationDate(const Date& terminationDate); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& withCalendar(const Calendar& cal); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withRule(DateGeneration::Rule r); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& withEndOfMonth(bool flag = true); + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& withFixedFrequency(Frequency f); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& withFixedDayCount(const DayCounter& dc); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFixedPaymentConvention(BusinessDayConvention bdc); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFixedPaymentLag(Natural lag); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFixedPaymentCalendar(const Calendar& cal); + + //! defaults to the floating index tenor + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& withFloatFrequency(Frequency f); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& withFloatDayCount(const DayCounter& dc); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& withFloatSpread(Spread sp); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFloatPaymentConvention(BusinessDayConvention bdc); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFloatPaymentLag(Natural lag); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFloatPaymentCalendar(const Calendar& cal); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withIndexedCoupons(const std::optional& b = true); + + //! \name overnight-index-only floating leg conventions + //@{ + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFloatCompoundSpread(bool flag = true); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFloatLookbackDays(Natural lookbackDays); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFloatLockoutDays(Natural lockoutDays); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFloatObservationShift(bool flag = true); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFloatAveragingMethod(RateAveraging::Type averagingMethod); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withTelescopicValueDates(bool flag = true); + //@} + + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFixedDiscountCurve(const Handle& fixedDiscountCurve); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withFloatDiscountCurve(const Handle& floatDiscountCurve); + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap& + withPricingEngine(const ext::shared_ptr& engine); + + private: + Period swapTenor_; + Currency fixedCurrency_; + ext::shared_ptr floatIndex_; + Rate fixedRate_; + Real floatNominal_; + Period forwardStart_; + + Swap::Type type_ = Swap::Payer; + Real fixedNominal_ = Null(); + Handle fxSpot_; + Currency floatCurrency_; + + Natural settlementDays_ = Null(); + Date effectiveDate_, terminationDate_; + Calendar calendar_; + DateGeneration::Rule rule_ = DateGeneration::Backward; + bool endOfMonth_ = false; + + std::optional fixedFrequency_; + DayCounter fixedDayCount_; + BusinessDayConvention fixedPaymentConvention_ = ModifiedFollowing; + Natural fixedPaymentLag_ = 0; + Calendar fixedPaymentCalendar_; + + std::optional floatFrequency_; + DayCounter floatDayCount_; + Spread floatSpread_ = 0.0; + std::optional floatPaymentConvention_; + Natural floatPaymentLag_ = 0; + Calendar floatPaymentCalendar_; + std::optional useIndexedCoupons_; + + bool floatCompoundSpread_ = false; + Natural floatLookbackDays_ = Null(); + bool floatObservationShift_ = false; + Natural floatLockoutDays_ = 0; + RateAveraging::Type floatAveragingMethod_ = RateAveraging::Compound; + bool telescopicValueDates_ = false; + + Handle fixedDiscountCurve_, floatDiscountCurve_; + ext::shared_ptr engine_; + }; + +} + +#endif diff --git a/ql/instruments/makeconstnotionalcrosscurrencyswap.cpp b/ql/instruments/makeconstnotionalcrosscurrencyswap.cpp new file mode 100644 index 00000000000..c8316f61c2b --- /dev/null +++ b/ql/instruments/makeconstnotionalcrosscurrencyswap.cpp @@ -0,0 +1,115 @@ +/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + + QuantLib is free software: you can redistribute it and/or modify it + under the terms of the QuantLib license. You should have received a + copy of the license along with this program; if not, please email + . The license is also available online at + . + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +#include +#include +#include +#include + +namespace QuantLib { + + MakeConstNotionalCrossCurrencySwap::MakeConstNotionalCrossCurrencySwap( + Leg payLeg, Currency payCurrency, Leg recLeg, Currency recCurrency) + : payLeg_(std::move(payLeg)), recLeg_(std::move(recLeg)), + payCurrency_(std::move(payCurrency)), recCurrency_(std::move(recCurrency)) {} + + MakeConstNotionalCrossCurrencySwap::operator ConstNotionalCrossCurrencySwap() const { + ext::shared_ptr swap = *this; + return *swap; + } + + MakeConstNotionalCrossCurrencySwap:: + operator ext::shared_ptr() const { + + auto swap = ext::make_shared(payLeg_, payCurrency_, + recLeg_, recCurrency_); + + if (engine_ != nullptr) { + swap->setPricingEngine(engine_); + } else if (!payDiscountCurve_.empty() || !recDiscountCurve_.empty()) { + QL_REQUIRE(!payDiscountCurve_.empty() && !recDiscountCurve_.empty(), + "both discount curves are needed to build the pricing engine, but only " + "the " + << (payDiscountCurve_.empty() ? recCurrency_.code() : + payCurrency_.code()) + << " one was given; use withPayDiscountCurve() and " + "withRecDiscountCurve()"); + QL_REQUIRE(!spotFX_.empty() || payCurrency_ == recCurrency_, + "an FX spot quote is required to build the pricing engine for a " + << payCurrency_.code() << "/" << recCurrency_.code() + << " swap; use withFxSpot() or supply a custom engine with " + "withPricingEngine()"); + Handle engineSpotFX = spotFX_.empty() ? makeQuoteHandle(1.0) : spotFX_; + auto builtEngine = ext::make_shared( + recCurrency_, recDiscountCurve_, payCurrency_, payDiscountCurve_, engineSpotFX, + includeSettlementDateFlows_, settlementDate_, npvDate_, spotFXSettleDate_); + swap->setPricingEngine(builtEngine); + } + + return swap; + } + + MakeConstNotionalCrossCurrencySwap& MakeConstNotionalCrossCurrencySwap::withPayDiscountCurve( + const Handle& payDiscountCurve) { + payDiscountCurve_ = payDiscountCurve; + return *this; + } + + MakeConstNotionalCrossCurrencySwap& MakeConstNotionalCrossCurrencySwap::withRecDiscountCurve( + const Handle& recDiscountCurve) { + recDiscountCurve_ = recDiscountCurve; + return *this; + } + + MakeConstNotionalCrossCurrencySwap& + MakeConstNotionalCrossCurrencySwap::withFxSpot(const Handle& spotFX) { + spotFX_ = spotFX; + return *this; + } + + MakeConstNotionalCrossCurrencySwap& + MakeConstNotionalCrossCurrencySwap::withIncludeSettlementDateFlows( + bool includeSettlementDateFlows) { + includeSettlementDateFlows_ = includeSettlementDateFlows; + return *this; + } + + MakeConstNotionalCrossCurrencySwap& + MakeConstNotionalCrossCurrencySwap::withSettlementDate(const Date& settlementDate) { + settlementDate_ = settlementDate; + return *this; + } + + MakeConstNotionalCrossCurrencySwap& + MakeConstNotionalCrossCurrencySwap::withNpvDate(const Date& npvDate) { + npvDate_ = npvDate; + return *this; + } + + MakeConstNotionalCrossCurrencySwap& + MakeConstNotionalCrossCurrencySwap::withSpotFXSettleDate(const Date& spotFXSettleDate) { + spotFXSettleDate_ = spotFXSettleDate; + return *this; + } + + MakeConstNotionalCrossCurrencySwap& MakeConstNotionalCrossCurrencySwap::withPricingEngine( + const ext::shared_ptr& engine) { + engine_ = engine; + return *this; + } + +} diff --git a/ql/instruments/makeconstnotionalcrosscurrencyswap.hpp b/ql/instruments/makeconstnotionalcrosscurrencyswap.hpp new file mode 100644 index 00000000000..f359e93a79e --- /dev/null +++ b/ql/instruments/makeconstnotionalcrosscurrencyswap.hpp @@ -0,0 +1,92 @@ +/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + + QuantLib is free software: you can redistribute it and/or modify it + under the terms of the QuantLib license. You should have received a + copy of the license along with this program; if not, please email + . The license is also available online at + . + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +/*! \file makeconstnotionalcrosscurrencyswap.hpp + \brief Helper class to instantiate a constant-notional cross-currency swap + from two already-built legs. +*/ + +#ifndef quantlib_make_const_notional_cross_currency_swap_hpp +#define quantlib_make_const_notional_cross_currency_swap_hpp + +#include +#include +#include + +namespace QuantLib { + + //! helper class + /*! This class provides a more comfortable way to instantiate a generic + constant-notional cross-currency swap out of two already-built legs + (e.g. an \c IborLeg, \c OvernightLeg, or \c FixedRateLeg, including any + notional-exchange cash flows the user wants attached) and to wire up + the resulting instrument to a \c DiscountingConstNotionalCrossCurrencySwapEngine. + + The pay leg is the first leg (payer) and the receive leg is the second + leg (receiver), following the convention of \c CrossCurrencySwap. + + For a more convenient way of building the two legs from a tenor and a + pair of indices, see \c MakeConstNotionalCrossCurrencyBasisSwap and + \c MakeConstNotionalCrossCurrencyFixedVsFloatingSwap. + */ + class MakeConstNotionalCrossCurrencySwap { + public: + MakeConstNotionalCrossCurrencySwap(Leg payLeg, + Currency payCurrency, + Leg recLeg, + Currency recCurrency); + + operator ConstNotionalCrossCurrencySwap() const; + operator ext::shared_ptr() const; + + //! discount curve used for the pay-currency leg + MakeConstNotionalCrossCurrencySwap& + withPayDiscountCurve(const Handle& payDiscountCurve); + //! discount curve used for the receive-currency leg + MakeConstNotionalCrossCurrencySwap& + withRecDiscountCurve(const Handle& recDiscountCurve); + /*! spot FX rate, expressed as units of the receive currency per unit + of the pay currency, quoted for settlement on the npv date (unless + \c withSpotFXSettleDate is used). + */ + MakeConstNotionalCrossCurrencySwap& withFxSpot(const Handle& spotFX); + + MakeConstNotionalCrossCurrencySwap& + withIncludeSettlementDateFlows(bool includeSettlementDateFlows); + MakeConstNotionalCrossCurrencySwap& withSettlementDate(const Date& settlementDate); + MakeConstNotionalCrossCurrencySwap& withNpvDate(const Date& npvDate); + MakeConstNotionalCrossCurrencySwap& withSpotFXSettleDate(const Date& spotFXSettleDate); + + //! overrides any engine that would otherwise be built from the curves/spot above + MakeConstNotionalCrossCurrencySwap& + withPricingEngine(const ext::shared_ptr& engine); + + private: + Leg payLeg_, recLeg_; + Currency payCurrency_, recCurrency_; + + Handle payDiscountCurve_, recDiscountCurve_; + Handle spotFX_; + std::optional includeSettlementDateFlows_; + Date settlementDate_, npvDate_, spotFXSettleDate_; + + ext::shared_ptr engine_; + }; + +} + +#endif diff --git a/test-suite/CMakeLists.txt b/test-suite/CMakeLists.txt index 2f6bf46c362..02152dda3a7 100644 --- a/test-suite/CMakeLists.txt +++ b/test-suite/CMakeLists.txt @@ -106,6 +106,7 @@ set(QL_TEST_SOURCES linearleastsquaresregression.cpp lookbackoptions.cpp lowdiscrepancysequences.cpp + makecrosscurrencyswap.cpp margrabeoption.cpp marketmodel.cpp marketmodel_cms.cpp diff --git a/test-suite/Makefile.am b/test-suite/Makefile.am index 886f1afc9dc..9af822409ce 100644 --- a/test-suite/Makefile.am +++ b/test-suite/Makefile.am @@ -107,6 +107,7 @@ QL_TEST_SRCS = \ linearleastsquaresregression.cpp \ lookbackoptions.cpp \ lowdiscrepancysequences.cpp \ + makecrosscurrencyswap.cpp \ margrabeoption.cpp \ marketmodel.cpp \ marketmodel_cms.cpp \ diff --git a/test-suite/makecrosscurrencyswap.cpp b/test-suite/makecrosscurrencyswap.cpp new file mode 100644 index 00000000000..c17e4bb5d95 --- /dev/null +++ b/test-suite/makecrosscurrencyswap.cpp @@ -0,0 +1,417 @@ +/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* + This file is part of QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + + QuantLib is free software: you can redistribute it and/or modify it + under the terms of the QuantLib license. You should have received a + copy of the license along with this program; if not, please email + . The license is also available online at + . + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +#include "toplevelfixture.hpp" +#include "utilities.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace QuantLib; +using namespace boost::unit_test_framework; + +BOOST_FIXTURE_TEST_SUITE(QuantLibTests, TopLevelFixture) + +BOOST_AUTO_TEST_SUITE(MakeCrossCurrencySwapTests) + +namespace { + + struct CommonVars { + Date today; + // discounting and forecasting curves are kept distinct so that the + // legs are not trivially par and the cross-currency basis is non-zero + Handle eurCurve, usdCurve; + Handle eurForecast, usdForecast; + ext::shared_ptr eurIndex, usdIndex; + Handle spotFX; // USD per EUR + + CommonVars() { + today = Date(15, Jun, 2023); + Settings::instance().evaluationDate() = today; + + eurCurve = Handle( + ext::make_shared(today, 0.0280, Actual360())); + usdCurve = Handle( + ext::make_shared(today, 0.0475, Actual360())); + eurForecast = Handle( + ext::make_shared(today, 0.0310, Actual360())); + usdForecast = Handle( + ext::make_shared(today, 0.0530, Actual360())); + + eurIndex = ext::make_shared(3 * Months, eurForecast); + usdIndex = ext::make_shared(3 * Months, usdForecast); + + spotFX = Handle(ext::make_shared(1.10)); + } + }; + +} + +BOOST_AUTO_TEST_CASE(testConstNotionalBasisSwapBuilder) { + BOOST_TEST_MESSAGE("Testing MakeConstNotionalCrossCurrencyBasisSwap builder..."); + + CommonVars vars; + + ext::shared_ptr swap = + MakeConstNotionalCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, vars.usdIndex, 10000000.0) + .withFxSpot(vars.spotFX) + .withPayDiscountCurve(vars.eurCurve) + .withRecDiscountCurve(vars.usdCurve); + + BOOST_CHECK_EQUAL(swap->payCurrency(), EURCurrency()); + BOOST_CHECK_EQUAL(swap->recCurrency(), USDCurrency()); + BOOST_CHECK_CLOSE(swap->recNominal(), 10000000.0 * 1.10, 1.0e-8); + + Spread fairPaySpread = swap->fairPaySpread(); + Spread fairRecSpread = swap->fairRecSpread(); + BOOST_CHECK(fairPaySpread != Null()); + BOOST_CHECK(fairRecSpread != Null()); + + // Rebuilding the swap at the fair pay spread should reprice it to par. + ext::shared_ptr parSwap = + MakeConstNotionalCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, vars.usdIndex, 10000000.0) + .withFxSpot(vars.spotFX) + .withPaySpread(fairPaySpread) + .withPayDiscountCurve(vars.eurCurve) + .withRecDiscountCurve(vars.usdCurve); + + if (std::fabs(parSwap->NPV()) > 1.0) + BOOST_ERROR("Failed to reproduce par NPV using the fair pay spread:\n" + << " NPV: " << parSwap->NPV()); +} + +BOOST_AUTO_TEST_CASE(testConstNotionalFixedVsFloatingBuilder) { + BOOST_TEST_MESSAGE( + "Testing MakeConstNotionalCrossCurrencyFixedVsFloatingSwap builder..."); + + CommonVars vars; + + // Fixed rate left unspecified: the builder should solve for the fair rate. + ext::shared_ptr swap = + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap(5 * Years, EURCurrency(), vars.usdIndex, + Null(), 10000000.0) + .withFxSpot(vars.spotFX) + .withFixedDiscountCurve(vars.eurCurve) + .withFloatDiscountCurve(vars.usdCurve); + + BOOST_CHECK(swap->fixedRate() != Null()); + + if (std::fabs(swap->NPV()) > 1.0) + BOOST_ERROR("Failed to solve for the at-market fixed rate:\n" + << " NPV: " << swap->NPV()); + + // Explicit fixed rate: fairRate()/fairSpread() should still be available. + ext::shared_ptr offMarketSwap = + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap(5 * Years, EURCurrency(), vars.usdIndex, + 0.02, 10000000.0) + .withFxSpot(vars.spotFX) + .withFixedDiscountCurve(vars.eurCurve) + .withFloatDiscountCurve(vars.usdCurve); + + BOOST_CHECK(offMarketSwap->fairRate() != Null()); + BOOST_CHECK(offMarketSwap->fairSpread() != Null()); +} + +BOOST_AUTO_TEST_CASE(testConstNotionalCrossCurrencySwapGenericBuilder) { + BOOST_TEST_MESSAGE("Testing MakeConstNotionalCrossCurrencySwap generic builder..."); + + CommonVars vars; + + Date start = TARGET().advance(vars.today, 2 * Days); + Schedule schedule(start, start + 2 * Years, 3 * Months, TARGET(), ModifiedFollowing, + ModifiedFollowing, DateGeneration::Backward, false); + + Leg payLeg = IborLeg(schedule, vars.eurIndex).withNotionals(1000000.0); + Leg recLeg = IborLeg(schedule, vars.usdIndex).withNotionals(1100000.0); + + ext::shared_ptr swap = + MakeConstNotionalCrossCurrencySwap(payLeg, EURCurrency(), recLeg, USDCurrency()) + .withFxSpot(vars.spotFX) + .withPayDiscountCurve(vars.eurCurve) + .withRecDiscountCurve(vars.usdCurve); + + BOOST_CHECK_NO_THROW(swap->NPV()); + BOOST_CHECK_EQUAL(swap->legCurrency(0), EURCurrency()); + BOOST_CHECK_EQUAL(swap->legCurrency(1), USDCurrency()); +} + +BOOST_AUTO_TEST_CASE(testMtMBasisSwapBuilder) { + BOOST_TEST_MESSAGE("Testing MakeMtMCrossCurrencyBasisSwap builder..."); + + CommonVars vars; + + ext::shared_ptr swap = + MakeMtMCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, vars.usdIndex, + /*isFxBaseCurrencyLegResettable=*/true, 10000000.0) + .withFxSpot(vars.spotFX) + .withFxBaseDiscountCurve(vars.eurCurve) + .withFxQuoteDiscountCurve(vars.usdCurve); + + Spread fairFxBaseSpread = swap->fairFxBaseSpread(); + Spread fairFxQuoteSpread = swap->fairFxQuoteSpread(); + BOOST_CHECK(fairFxBaseSpread != Null()); + BOOST_CHECK(fairFxQuoteSpread != Null()); + + ext::shared_ptr parSwap = + MakeMtMCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, vars.usdIndex, true, 10000000.0) + .withFxSpot(vars.spotFX) + .withFxBaseSpread(fairFxBaseSpread) + .withFxBaseDiscountCurve(vars.eurCurve) + .withFxQuoteDiscountCurve(vars.usdCurve); + + if (std::fabs(parSwap->NPV()) > 1.0) + BOOST_ERROR("Failed to reproduce par NPV using the fair FX-base spread:\n" + << " NPV: " << parSwap->NPV()); +} + +BOOST_AUTO_TEST_CASE(testAutoAttachedEngineMatchesManualEngine) { + BOOST_TEST_MESSAGE("Testing that the auto-attached engine matches a hand-wired one..."); + + CommonVars vars; + const Real nominal = 10000000.0; + + ext::shared_ptr autoSwap = + MakeConstNotionalCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, vars.usdIndex, nominal) + .withPaySpread(0.0025) + .withFxSpot(vars.spotFX) + .withPayDiscountCurve(vars.eurCurve) + .withRecDiscountCurve(vars.usdCurve); + + // same trade, with the engine wired up by hand + ext::shared_ptr manualSwap = + MakeConstNotionalCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, vars.usdIndex, nominal) + .withPaySpread(0.0025) + .withFxSpot(vars.spotFX); + manualSwap->setPricingEngine(ext::make_shared( + USDCurrency(), vars.usdCurve, EURCurrency(), vars.eurCurve, vars.spotFX)); + + if (std::fabs(autoSwap->NPV() - manualSwap->NPV()) > 1.0e-6) + BOOST_ERROR("The engine built by the helper class does not match a manual one:\n" + << " auto: " << autoSwap->NPV() << "\n" + << " manual: " << manualSwap->NPV()); + + // the NPV is expressed in the receive currency, so the pay leg must be + // converted with the spot quoted as receive currency per pay currency + Real expected = + autoSwap->inCcyLegNPV(1) + autoSwap->inCcyLegNPV(0) * vars.spotFX->value(); + if (std::fabs(autoSwap->NPV() - expected) > 1.0e-6) + BOOST_ERROR("The FX spot is not applied in the documented direction:\n" + << " NPV: " << autoSwap->NPV() << "\n" + << " recLegNPV + payLegNPV * fxSpot: " << expected); +} + +BOOST_AUTO_TEST_CASE(testNotionalDerivationFromFxSpot) { + BOOST_TEST_MESSAGE("Testing notional derivation from the FX spot quote..."); + + CommonVars vars; + const Real nominal = 10000000.0; + const Real spot = vars.spotFX->value(); + + // spot is quoted as receive currency per pay currency + ext::shared_ptr basisSwap = + MakeConstNotionalCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, vars.usdIndex, nominal) + .withFxSpot(vars.spotFX) + .withPayDiscountCurve(vars.eurCurve) + .withRecDiscountCurve(vars.usdCurve); + BOOST_CHECK_CLOSE(basisSwap->recNominal(), nominal * spot, 1.0e-8); + + // spot is quoted as floating currency per fixed currency, so the fixed + // notional is the floating one divided by the spot + ext::shared_ptr fixedVsFloating = + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap(5 * Years, EURCurrency(), vars.usdIndex, + 0.02, nominal) + .withFxSpot(vars.spotFX) + .withFixedDiscountCurve(vars.eurCurve) + .withFloatDiscountCurve(vars.usdCurve); + BOOST_CHECK_CLOSE(fixedVsFloating->fixedNominal(), nominal / spot, 1.0e-8); + + // spot is quoted as fx-quote currency per fx-base currency + ext::shared_ptr mtmSwap = + MakeMtMCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, vars.usdIndex, true, nominal) + .withFxSpot(vars.spotFX) + .withFxBaseDiscountCurve(vars.eurCurve) + .withFxQuoteDiscountCurve(vars.usdCurve); + BOOST_CHECK_CLOSE(mtmSwap->fxQuoteNominal(), nominal * spot, 1.0e-8); + + // an explicitly given notional is never overridden + ext::shared_ptr explicitNotional = + MakeConstNotionalCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, vars.usdIndex, nominal) + .withFxSpot(vars.spotFX) + .withRecNominal(12000000.0) + .withPayDiscountCurve(vars.eurCurve) + .withRecDiscountCurve(vars.usdCurve); + BOOST_CHECK_CLOSE(explicitNotional->recNominal(), 12000000.0, 1.0e-8); +} + +BOOST_AUTO_TEST_CASE(testMissingFxSpotIsRejected) { + BOOST_TEST_MESSAGE("Testing that a missing FX spot quote is reported..."); + + CommonVars vars; + const Real nominal = 10000000.0; + + // Without a spot quote the helper classes cannot convert the notional or + // build the engine; assuming a unit FX rate would silently misprice the + // swap, so an exception is expected instead. + BOOST_CHECK_THROW(ext::shared_ptr swap = + MakeConstNotionalCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, + vars.usdIndex, nominal) + .withPayDiscountCurve(vars.eurCurve) + .withRecDiscountCurve(vars.usdCurve), + Error); + + BOOST_CHECK_THROW(ext::shared_ptr swap = + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap( + 5 * Years, EURCurrency(), vars.usdIndex, 0.02, nominal) + .withFixedDiscountCurve(vars.eurCurve) + .withFloatDiscountCurve(vars.usdCurve), + Error); + + BOOST_CHECK_THROW(ext::shared_ptr swap = + MakeMtMCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, vars.usdIndex, + true, nominal) + .withFxBaseDiscountCurve(vars.eurCurve) + .withFxQuoteDiscountCurve(vars.usdCurve), + Error); + + Date start = TARGET().advance(vars.today, 2 * Days); + Schedule schedule = MakeSchedule().from(start).to(start + 2 * Years).withTenor(3 * Months); + Leg payLeg = IborLeg(schedule, vars.eurIndex).withNotionals(nominal); + Leg recLeg = IborLeg(schedule, vars.usdIndex).withNotionals(nominal * 1.10); + BOOST_CHECK_THROW(ext::shared_ptr swap = + MakeConstNotionalCrossCurrencySwap(payLeg, EURCurrency(), recLeg, + USDCurrency()) + .withPayDiscountCurve(vars.eurCurve) + .withRecDiscountCurve(vars.usdCurve), + Error); + + // Legs in the same currency, on the other hand, need no FX spot at all. + ext::shared_ptr singleCurrency = + MakeConstNotionalCrossCurrencyBasisSwap(2 * Years, vars.eurIndex, vars.eurIndex, nominal) + .withRecSpread(0.0010) + .withPayDiscountCurve(vars.eurCurve) + .withRecDiscountCurve(vars.eurCurve); + BOOST_CHECK_NO_THROW(singleCurrency->NPV()); + + // An explicitly supplied notional and engine also make the spot redundant. + BOOST_CHECK_NO_THROW(ext::shared_ptr swap = + MakeConstNotionalCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, + vars.usdIndex, nominal) + .withRecNominal(nominal * 1.10) + .withPricingEngine( + ext::make_shared< + DiscountingConstNotionalCrossCurrencySwapEngine>( + USDCurrency(), vars.usdCurve, EURCurrency(), vars.eurCurve, + vars.spotFX))); +} + +BOOST_AUTO_TEST_CASE(testIncompleteDiscountCurvesAreRejected) { + BOOST_TEST_MESSAGE("Testing that a single discount curve is reported..."); + + CommonVars vars; + const Real nominal = 10000000.0; + + // Supplying one curve out of two cannot be anything but a mistake, and + // would otherwise only surface as a null-engine error at pricing time. + BOOST_CHECK_THROW(ext::shared_ptr swap = + MakeConstNotionalCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, + vars.usdIndex, nominal) + .withFxSpot(vars.spotFX) + .withPayDiscountCurve(vars.eurCurve), + Error); + + BOOST_CHECK_THROW(ext::shared_ptr swap = + MakeConstNotionalCrossCurrencyFixedVsFloatingSwap( + 5 * Years, EURCurrency(), vars.usdIndex, 0.02, nominal) + .withFxSpot(vars.spotFX) + .withFloatDiscountCurve(vars.usdCurve), + Error); + + BOOST_CHECK_THROW(ext::shared_ptr swap = + MakeMtMCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, vars.usdIndex, + true, nominal) + .withFxSpot(vars.spotFX) + .withFxBaseDiscountCurve(vars.eurCurve), + Error); + + // Giving no curve at all remains legitimate: the user may want to attach + // an engine to the instrument afterwards. + ext::shared_ptr engineLess = + MakeConstNotionalCrossCurrencyBasisSwap(5 * Years, vars.eurIndex, vars.usdIndex, nominal) + .withFxSpot(vars.spotFX); + engineLess->setPricingEngine(ext::make_shared( + USDCurrency(), vars.usdCurve, EURCurrency(), vars.eurCurve, vars.spotFX)); + BOOST_CHECK_NO_THROW(engineLess->NPV()); +} + +BOOST_AUTO_TEST_CASE(testDefaultPaymentConventions) { + BOOST_TEST_MESSAGE("Testing that default payment conventions follow the index..."); + + CommonVars vars; + const Real nominal = 10000000.0; + + // 30 September 2023 is a Saturday: with a Following convention the + // payment would roll into October, as it would not with the + // ModifiedFollowing convention of the underlying index. + Date monthEnd(30, Sep, 2023); + + ext::shared_ptr swap = + MakeConstNotionalCrossCurrencyBasisSwap(1 * Years, vars.eurIndex, vars.eurIndex, nominal) + .withEffectiveDate(Date(30, Jun, 2023)) + .withEndOfMonth() + .withPayDiscountCurve(vars.eurCurve) + .withRecDiscountCurve(vars.eurCurve); + + ext::shared_ptr vanillaSwap = MakeVanillaSwap(1 * Years, vars.eurIndex, 0.03) + .withEffectiveDate(Date(30, Jun, 2023)) + .withFloatingLegEndOfMonth() + .withDiscountingTermStructure(vars.eurCurve); + + std::vector builderDates, vanillaDates; + for (const auto& cf : swap->leg(0)) + if (ext::dynamic_pointer_cast(cf) != nullptr) + builderDates.push_back(cf->date()); + for (const auto& cf : vanillaSwap->floatingLeg()) + vanillaDates.push_back(cf->date()); + + if (builderDates != vanillaDates) + BOOST_ERROR("Floating-leg payment dates differ from the MakeVanillaSwap equivalent"); + + bool paidInMonthEnd = false; + for (const Date& d : builderDates) + if (d.month() == Sep && d.year() == 2023) + paidInMonthEnd = true; + if (!paidInMonthEnd) + BOOST_ERROR("A month-end payment was rolled into the following month; the default " + "payment convention should follow the index (" + << monthEnd << " expected to be adjusted backwards)"); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END()