/src/quantlib/ql/experimental/termstructures/crosscurrencyratehelpers.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2021 Marcin Rybacki |
5 | | |
6 | | This file is part of QuantLib, a free-software/open-source library |
7 | | for financial quantitative analysts and developers - http://quantlib.org/ |
8 | | |
9 | | QuantLib is free software: you can redistribute it and/or modify it |
10 | | under the terms of the QuantLib license. You should have received a |
11 | | copy of the license along with this program; if not, please email |
12 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
13 | | <http://quantlib.org/license.shtml>. |
14 | | |
15 | | This program is distributed in the hope that it will be useful, but WITHOUT |
16 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
17 | | FOR A PARTICULAR PURPOSE. See the license for more details. |
18 | | */ |
19 | | |
20 | | #include <ql/cashflows/overnightindexedcoupon.hpp> |
21 | | #include <ql/cashflows/iborcoupon.hpp> |
22 | | #include <ql/cashflows/cashflows.hpp> |
23 | | #include <ql/cashflows/simplecashflow.hpp> |
24 | | #include <ql/experimental/termstructures/crosscurrencyratehelpers.hpp> |
25 | | #include <ql/utilities/null_deleter.hpp> |
26 | | #include <utility> |
27 | | |
28 | | namespace QuantLib { |
29 | | |
30 | | namespace { |
31 | | |
32 | | Schedule legSchedule(const Date& evaluationDate, |
33 | | const Period& tenor, |
34 | | const Period& frequency, |
35 | | Natural fixingDays, |
36 | | const Calendar& calendar, |
37 | | BusinessDayConvention convention, |
38 | 0 | bool endOfMonth) { |
39 | 0 | QL_REQUIRE(tenor >= frequency, |
40 | 0 | "XCCY instrument tenor should not be smaller than coupon frequency."); |
41 | | |
42 | 0 | Date referenceDate = calendar.adjust(evaluationDate); |
43 | 0 | Date earliestDate = calendar.advance(referenceDate, fixingDays * Days, convention); |
44 | 0 | Date maturity = earliestDate + tenor; |
45 | 0 | return MakeSchedule() |
46 | 0 | .from(earliestDate) |
47 | 0 | .to(maturity) |
48 | 0 | .withTenor(frequency) |
49 | 0 | .withCalendar(calendar) |
50 | 0 | .withConvention(convention) |
51 | 0 | .endOfMonth(endOfMonth) |
52 | 0 | .backwards(); |
53 | 0 | } |
54 | | |
55 | | Leg buildFloatingLeg(const Date& evaluationDate, |
56 | | const Period& tenor, |
57 | | Natural fixingDays, |
58 | | const Calendar& calendar, |
59 | | BusinessDayConvention convention, |
60 | | bool endOfMonth, |
61 | | const ext::shared_ptr<IborIndex>& idx, |
62 | | Frequency paymentFrequency, |
63 | 0 | Integer paymentLag) { |
64 | 0 | auto overnightIndex = ext::dynamic_pointer_cast<OvernightIndex>(idx); |
65 | |
|
66 | 0 | Period freqPeriod; |
67 | 0 | if (paymentFrequency == NoFrequency) { |
68 | 0 | QL_REQUIRE(!overnightIndex, "Require payment frequency for overnight indices."); |
69 | 0 | freqPeriod = idx->tenor(); |
70 | 0 | } else { |
71 | 0 | freqPeriod = Period(paymentFrequency); |
72 | 0 | } |
73 | | |
74 | 0 | Schedule sch = legSchedule(evaluationDate, tenor, freqPeriod, fixingDays, calendar, |
75 | 0 | convention, endOfMonth); |
76 | 0 | if (overnightIndex != nullptr) { |
77 | 0 | return OvernightLeg(sch, overnightIndex) |
78 | 0 | .withNotionals(1.0) |
79 | 0 | .withPaymentLag(paymentLag); |
80 | 0 | } |
81 | 0 | return IborLeg(sch, idx).withNotionals(1.0).withPaymentLag(paymentLag); |
82 | 0 | } |
83 | | |
84 | | std::pair<Real, Real> |
85 | | npvbpsConstNotionalLeg(const Leg& iborLeg, |
86 | | const Date& initialNotionalExchangeDate, |
87 | | const Date& finalNotionalExchangeDate, |
88 | 0 | const Handle<YieldTermStructure>& discountCurveHandle) { |
89 | 0 | const Spread basisPoint = 1.0e-4; |
90 | 0 | Date refDt = discountCurveHandle->referenceDate(); |
91 | 0 | const YieldTermStructure& discountRef = **discountCurveHandle; |
92 | 0 | bool includeSettleDtFlows = true; |
93 | 0 | auto [npv, bps] = CashFlows::npvbps(iborLeg, discountRef, includeSettleDtFlows, refDt, refDt); |
94 | | // Include NPV of the notional exchange at start and maturity. |
95 | 0 | npv += (-1.0) * discountRef.discount(initialNotionalExchangeDate); |
96 | 0 | npv += discountRef.discount(finalNotionalExchangeDate); |
97 | 0 | bps /= basisPoint; |
98 | 0 | return { npv, bps }; |
99 | 0 | } |
100 | | |
101 | | class ResettingLegHelper { |
102 | | public: |
103 | | explicit ResettingLegHelper(const YieldTermStructure& discountCurve, |
104 | | const YieldTermStructure& foreignCurve) |
105 | 0 | : discountCurve_(discountCurve), foreignCurve_(foreignCurve) {} |
106 | 0 | DiscountFactor discount(const Date& d) const { |
107 | 0 | return discountCurve_.discount(d); |
108 | 0 | } |
109 | 0 | Real notionalAdjustment(const Date& d) const { |
110 | 0 | return foreignCurve_.discount(d) / discountCurve_.discount(d); |
111 | 0 | } |
112 | | |
113 | | private: |
114 | | const YieldTermStructure& discountCurve_; |
115 | | const YieldTermStructure& foreignCurve_; |
116 | | }; |
117 | | |
118 | | class ResettingLegCalculator : public AcyclicVisitor, public Visitor<Coupon> { |
119 | | public: |
120 | | explicit ResettingLegCalculator(const YieldTermStructure& discountCurve, |
121 | | const YieldTermStructure& foreignCurve, |
122 | | Integer paymentLag, |
123 | | Calendar paymentCalendar, |
124 | | BusinessDayConvention convention) |
125 | 0 | : helper_(discountCurve, foreignCurve), paymentLag_(paymentLag), |
126 | 0 | paymentCalendar_(std::move(paymentCalendar)), convention_(convention) {} |
127 | | |
128 | 0 | void visit(Coupon& c) override { |
129 | 0 | Date start = c.accrualStartDate(); |
130 | 0 | Date end = c.accrualEndDate(); |
131 | 0 | Time accrual = c.accrualPeriod(); |
132 | 0 | Real adjustedNotional = c.nominal() * helper_.notionalAdjustment(start); |
133 | |
|
134 | 0 | DiscountFactor discountStart, discountEnd; |
135 | |
|
136 | 0 | if (paymentLag_ == 0) { |
137 | 0 | discountStart = helper_.discount(start); |
138 | 0 | discountEnd = helper_.discount(end); |
139 | 0 | } else { |
140 | 0 | Date paymentStart = |
141 | 0 | paymentCalendar_.advance(start, paymentLag_, Days, convention_); |
142 | 0 | Date paymentEnd = paymentCalendar_.advance(end, paymentLag_, Days, convention_); |
143 | 0 | discountStart = helper_.discount(paymentStart); |
144 | 0 | discountEnd = helper_.discount(paymentEnd); |
145 | 0 | } |
146 | | |
147 | | // NPV of a resetting coupon consists of a redemption of borrowed amount occurring |
148 | | // at the end of the accrual period plus the accrued interest, minus the borrowed |
149 | | // amount at the start of the period. All amounts are corrected by an adjustment |
150 | | // corresponding to the implied forward exchange rate, which is estimated by |
151 | | // the ratio of foreign and domestic curves discount factors. |
152 | 0 | Real npvRedeemedAmount = |
153 | 0 | adjustedNotional * discountEnd * (1.0 + c.rate() * accrual); |
154 | 0 | Real npvBorrowedAmount = -adjustedNotional * discountStart; |
155 | |
|
156 | 0 | npv_ += (npvRedeemedAmount + npvBorrowedAmount); |
157 | 0 | bps_ += adjustedNotional * discountEnd * accrual; |
158 | 0 | } |
159 | 0 | Real NPV() const { return npv_; } |
160 | 0 | Real BPS() const { return bps_; } |
161 | | |
162 | | private: |
163 | | ResettingLegHelper helper_; |
164 | | Real npv_ = 0.0; |
165 | | Real bps_ = 0.0; |
166 | | Integer paymentLag_; |
167 | | Calendar paymentCalendar_; |
168 | | BusinessDayConvention convention_; |
169 | | }; |
170 | | |
171 | | std::pair<Real, Real> npvbpsResettingLeg(const Leg& iborLeg, |
172 | | Integer paymentLag, |
173 | | const Calendar& paymentCalendar, |
174 | | BusinessDayConvention convention, |
175 | | const Handle<YieldTermStructure>& discountCurveHandle, |
176 | 0 | const Handle<YieldTermStructure>& foreignCurveHandle) { |
177 | 0 | const YieldTermStructure& discountCurveRef = **discountCurveHandle; |
178 | 0 | const YieldTermStructure& foreignCurveRef = **foreignCurveHandle; |
179 | |
|
180 | 0 | ResettingLegCalculator calc(discountCurveRef, foreignCurveRef, paymentLag, |
181 | 0 | paymentCalendar, convention); |
182 | 0 | for (const auto& i : iborLeg) { |
183 | 0 | CashFlow& cf = *i; |
184 | 0 | cf.accept(calc); |
185 | 0 | } |
186 | 0 | return { calc.NPV(), calc.BPS() }; |
187 | 0 | } |
188 | | } |
189 | | |
190 | | CrossCurrencyBasisSwapRateHelperBase::CrossCurrencyBasisSwapRateHelperBase( |
191 | | const Handle<Quote>& basis, |
192 | | const Period& tenor, |
193 | | Natural fixingDays, |
194 | | Calendar calendar, |
195 | | BusinessDayConvention convention, |
196 | | bool endOfMonth, |
197 | | ext::shared_ptr<IborIndex> baseCurrencyIndex, |
198 | | ext::shared_ptr<IborIndex> quoteCurrencyIndex, |
199 | | Handle<YieldTermStructure> collateralCurve, |
200 | | bool isFxBaseCurrencyCollateralCurrency, |
201 | | bool isBasisOnFxBaseCurrencyLeg, |
202 | | Frequency paymentFrequency, |
203 | | Integer paymentLag) |
204 | 0 | : RelativeDateRateHelper(basis), tenor_(tenor), fixingDays_(fixingDays), |
205 | 0 | calendar_(std::move(calendar)), convention_(convention), endOfMonth_(endOfMonth), |
206 | 0 | baseCcyIdx_(std::move(baseCurrencyIndex)), quoteCcyIdx_(std::move(quoteCurrencyIndex)), |
207 | 0 | collateralHandle_(std::move(collateralCurve)), |
208 | 0 | isFxBaseCurrencyCollateralCurrency_(isFxBaseCurrencyCollateralCurrency), |
209 | 0 | isBasisOnFxBaseCurrencyLeg_(isBasisOnFxBaseCurrencyLeg), |
210 | 0 | paymentFrequency_(paymentFrequency), paymentLag_(paymentLag) { |
211 | 0 | registerWith(baseCcyIdx_); |
212 | 0 | registerWith(quoteCcyIdx_); |
213 | 0 | registerWith(collateralHandle_); |
214 | 0 | CrossCurrencyBasisSwapRateHelperBase::initializeDates(); |
215 | 0 | } |
216 | | |
217 | 0 | void CrossCurrencyBasisSwapRateHelperBase::initializeDates() { |
218 | 0 | baseCcyIborLeg_ = buildFloatingLeg(evaluationDate_, tenor_, fixingDays_, calendar_, convention_, |
219 | 0 | endOfMonth_, baseCcyIdx_, paymentFrequency_, paymentLag_); |
220 | 0 | quoteCcyIborLeg_ = buildFloatingLeg(evaluationDate_, tenor_, fixingDays_, calendar_, |
221 | 0 | convention_, endOfMonth_, quoteCcyIdx_, paymentFrequency_, paymentLag_); |
222 | 0 | earliestDate_ = std::min(CashFlows::startDate(baseCcyIborLeg_), |
223 | 0 | CashFlows::startDate(quoteCcyIborLeg_)); |
224 | 0 | maturityDate_ = std::max(CashFlows::maturityDate(baseCcyIborLeg_), |
225 | 0 | CashFlows::maturityDate(quoteCcyIborLeg_)); |
226 | 0 | if (paymentLag_ == 0) { |
227 | 0 | initialNotionalExchangeDate_ = earliestDate_; |
228 | 0 | finalNotionalExchangeDate_ = maturityDate_; |
229 | 0 | } else { |
230 | 0 | initialNotionalExchangeDate_ = calendar_.advance(earliestDate_, paymentLag_, Days, convention_); |
231 | 0 | finalNotionalExchangeDate_ = calendar_.advance(maturityDate_, paymentLag_, Days, convention_); |
232 | 0 | } |
233 | 0 | Date lastPaymentDate = std::max(baseCcyIborLeg_.back()->date(), quoteCcyIborLeg_.back()->date()); |
234 | 0 | latestRelevantDate_ = latestDate_ = std::max(maturityDate_, lastPaymentDate); |
235 | 0 | } |
236 | | |
237 | | const Handle<YieldTermStructure>& |
238 | 0 | CrossCurrencyBasisSwapRateHelperBase::baseCcyLegDiscountHandle() const { |
239 | 0 | QL_REQUIRE(!termStructureHandle_.empty(), "term structure not set"); |
240 | 0 | QL_REQUIRE(!collateralHandle_.empty(), "collateral term structure not set"); |
241 | 0 | return isFxBaseCurrencyCollateralCurrency_ ? collateralHandle_ : termStructureHandle_; |
242 | 0 | } |
243 | | |
244 | | const Handle<YieldTermStructure>& |
245 | 0 | CrossCurrencyBasisSwapRateHelperBase::quoteCcyLegDiscountHandle() const { |
246 | 0 | QL_REQUIRE(!termStructureHandle_.empty(), "term structure not set"); |
247 | 0 | QL_REQUIRE(!collateralHandle_.empty(), "collateral term structure not set"); |
248 | 0 | return isFxBaseCurrencyCollateralCurrency_ ? termStructureHandle_ : collateralHandle_; |
249 | 0 | } |
250 | | |
251 | 0 | void CrossCurrencyBasisSwapRateHelperBase::setTermStructure(YieldTermStructure* t) { |
252 | | // do not set the relinkable handle as an observer - |
253 | | // force recalculation when needed |
254 | 0 | bool observer = false; |
255 | |
|
256 | 0 | ext::shared_ptr<YieldTermStructure> temp(t, null_deleter()); |
257 | 0 | termStructureHandle_.linkTo(temp, observer); |
258 | |
|
259 | 0 | RelativeDateRateHelper::setTermStructure(t); |
260 | 0 | } |
261 | | |
262 | | ConstNotionalCrossCurrencyBasisSwapRateHelper::ConstNotionalCrossCurrencyBasisSwapRateHelper( |
263 | | const Handle<Quote>& basis, |
264 | | const Period& tenor, |
265 | | Natural fixingDays, |
266 | | const Calendar& calendar, |
267 | | BusinessDayConvention convention, |
268 | | bool endOfMonth, |
269 | | const ext::shared_ptr<IborIndex>& baseCurrencyIndex, |
270 | | const ext::shared_ptr<IborIndex>& quoteCurrencyIndex, |
271 | | const Handle<YieldTermStructure>& collateralCurve, |
272 | | bool isFxBaseCurrencyCollateralCurrency, |
273 | | bool isBasisOnFxBaseCurrencyLeg, |
274 | | Frequency paymentFrequency, |
275 | | Integer paymentLag) |
276 | 0 | : CrossCurrencyBasisSwapRateHelperBase(basis, |
277 | 0 | tenor, |
278 | 0 | fixingDays, |
279 | 0 | calendar, |
280 | 0 | convention, |
281 | 0 | endOfMonth, |
282 | 0 | baseCurrencyIndex, |
283 | 0 | quoteCurrencyIndex, |
284 | 0 | collateralCurve, |
285 | 0 | isFxBaseCurrencyCollateralCurrency, |
286 | 0 | isBasisOnFxBaseCurrencyLeg, |
287 | 0 | paymentFrequency, |
288 | 0 | paymentLag) {} |
289 | | |
290 | 0 | Real ConstNotionalCrossCurrencyBasisSwapRateHelper::impliedQuote() const { |
291 | 0 | auto [npvBaseCcy, bpsBaseCcy] = npvbpsConstNotionalLeg(baseCcyIborLeg_, initialNotionalExchangeDate_, finalNotionalExchangeDate_, baseCcyLegDiscountHandle()); |
292 | 0 | auto [npvQuoteCcy, bpsQuoteCcy] = npvbpsConstNotionalLeg(quoteCcyIborLeg_, initialNotionalExchangeDate_, finalNotionalExchangeDate_, quoteCcyLegDiscountHandle()); |
293 | 0 | Real bps = isBasisOnFxBaseCurrencyLeg_ ? -bpsBaseCcy : bpsQuoteCcy; |
294 | 0 | return -(npvQuoteCcy - npvBaseCcy) / bps; |
295 | 0 | } |
296 | | |
297 | 0 | void ConstNotionalCrossCurrencyBasisSwapRateHelper::accept(AcyclicVisitor& v) { |
298 | 0 | auto* v1 = dynamic_cast<Visitor<ConstNotionalCrossCurrencyBasisSwapRateHelper>*>(&v); |
299 | 0 | if (v1 != nullptr) |
300 | 0 | v1->visit(*this); |
301 | 0 | else |
302 | 0 | RateHelper::accept(v); |
303 | 0 | } |
304 | | |
305 | | MtMCrossCurrencyBasisSwapRateHelper::MtMCrossCurrencyBasisSwapRateHelper( |
306 | | const Handle<Quote>& basis, |
307 | | const Period& tenor, |
308 | | Natural fixingDays, |
309 | | const Calendar& calendar, |
310 | | BusinessDayConvention convention, |
311 | | bool endOfMonth, |
312 | | const ext::shared_ptr<IborIndex>& baseCurrencyIndex, |
313 | | const ext::shared_ptr<IborIndex>& quoteCurrencyIndex, |
314 | | const Handle<YieldTermStructure>& collateralCurve, |
315 | | bool isFxBaseCurrencyCollateralCurrency, |
316 | | bool isBasisOnFxBaseCurrencyLeg, |
317 | | bool isFxBaseCurrencyLegResettable, |
318 | | Frequency paymentFrequency, |
319 | | Integer paymentLag) |
320 | 0 | : CrossCurrencyBasisSwapRateHelperBase(basis, |
321 | 0 | tenor, |
322 | 0 | fixingDays, |
323 | 0 | calendar, |
324 | 0 | convention, |
325 | 0 | endOfMonth, |
326 | 0 | baseCurrencyIndex, |
327 | 0 | quoteCurrencyIndex, |
328 | 0 | collateralCurve, |
329 | 0 | isFxBaseCurrencyCollateralCurrency, |
330 | 0 | isBasisOnFxBaseCurrencyLeg, |
331 | 0 | paymentFrequency, |
332 | 0 | paymentLag), |
333 | 0 | isFxBaseCurrencyLegResettable_(isFxBaseCurrencyLegResettable) {} |
334 | | |
335 | 0 | Real MtMCrossCurrencyBasisSwapRateHelper::impliedQuote() const { |
336 | 0 | Real npvBaseCcy = 0.0, bpsBaseCcy = 0.0; |
337 | 0 | Real npvQuoteCcy = 0.0, bpsQuoteCcy = 0.0; |
338 | 0 | if (isFxBaseCurrencyLegResettable_) { |
339 | 0 | std::tie(npvBaseCcy, bpsBaseCcy) = |
340 | 0 | npvbpsResettingLeg(baseCcyIborLeg_, paymentLag_, calendar_, convention_, |
341 | 0 | baseCcyLegDiscountHandle(), quoteCcyLegDiscountHandle()); |
342 | 0 | std::tie(npvQuoteCcy, bpsQuoteCcy) = |
343 | 0 | npvbpsConstNotionalLeg(quoteCcyIborLeg_, initialNotionalExchangeDate_, |
344 | 0 | finalNotionalExchangeDate_, quoteCcyLegDiscountHandle()); |
345 | 0 | } else { |
346 | 0 | std::tie(npvBaseCcy, bpsBaseCcy) = |
347 | 0 | npvbpsConstNotionalLeg(baseCcyIborLeg_, initialNotionalExchangeDate_, |
348 | 0 | finalNotionalExchangeDate_, baseCcyLegDiscountHandle()); |
349 | 0 | std::tie(npvQuoteCcy, bpsQuoteCcy) = npvbpsResettingLeg( |
350 | 0 | quoteCcyIborLeg_, paymentLag_, calendar_, convention_, |
351 | 0 | quoteCcyLegDiscountHandle(), baseCcyLegDiscountHandle()); |
352 | 0 | } |
353 | |
|
354 | 0 | Real bps = isBasisOnFxBaseCurrencyLeg_ ? -bpsBaseCcy : bpsQuoteCcy; |
355 | |
|
356 | 0 | return -(npvQuoteCcy - npvBaseCcy) / bps; |
357 | 0 | } |
358 | | |
359 | 0 | void MtMCrossCurrencyBasisSwapRateHelper::accept(AcyclicVisitor& v) { |
360 | 0 | auto* v1 = dynamic_cast<Visitor<MtMCrossCurrencyBasisSwapRateHelper>*>(&v); |
361 | 0 | if (v1 != nullptr) |
362 | 0 | v1->visit(*this); |
363 | 0 | else |
364 | 0 | RateHelper::accept(v); |
365 | 0 | } |
366 | | } |