/src/quantlib/ql/experimental/termstructures/crosscurrencyratehelpers.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2021 Marcin Rybacki |
5 | | Copyright (C) 2025 Uzair Beg |
6 | | |
7 | | This file is part of QuantLib, a free-software/open-source library |
8 | | for financial quantitative analysts and developers - http://quantlib.org/ |
9 | | |
10 | | QuantLib is free software: you can redistribute it and/or modify it |
11 | | under the terms of the QuantLib license. You should have received a |
12 | | copy of the license along with this program; if not, please email |
13 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
14 | | <https://www.quantlib.org/license.shtml>. |
15 | | |
16 | | This program is distributed in the hope that it will be useful, but WITHOUT |
17 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
18 | | FOR A PARTICULAR PURPOSE. See the license for more details. |
19 | | */ |
20 | | |
21 | | #include <ql/cashflows/overnightindexedcoupon.hpp> |
22 | | #include <ql/cashflows/iborcoupon.hpp> |
23 | | #include <ql/cashflows/cashflows.hpp> |
24 | | #include <ql/cashflows/simplecashflow.hpp> |
25 | | #include <ql/cashflows/fixedratecoupon.hpp> |
26 | | #include <ql/experimental/termstructures/crosscurrencyratehelpers.hpp> |
27 | | #include <ql/pricingengines/swap/discountingconstnotionalcrosscurrencyswapengine.hpp> |
28 | | #include <ql/utilities/null_deleter.hpp> |
29 | | #include <utility> |
30 | | |
31 | | namespace QuantLib { |
32 | | |
33 | | namespace { |
34 | | |
35 | | constexpr double sample_fixed_rate = 0.01; |
36 | | |
37 | | // Treat an explicitly-passed NoFrequency the same as an unset (nullopt) |
38 | | // payment frequency. Before these parameters were migrated to |
39 | | // ext::optional<Frequency>, NoFrequency was the sentinel meaning "derive |
40 | | // the schedule from the index tenor". Normalizing it here preserves that |
41 | | // behavior and keeps the stored optional either empty or holding an |
42 | | // actual frequency, so the rest of the code can treat the two cases |
43 | | // identically. |
44 | 0 | ext::optional<Frequency> normalizedPaymentFrequency(ext::optional<Frequency> frequency) { |
45 | 0 | if (frequency && *frequency == NoFrequency) |
46 | 0 | return ext::nullopt; |
47 | 0 | return frequency; |
48 | 0 | } |
49 | | |
50 | | Schedule legSchedule(const Date& evaluationDate, |
51 | | const Period& tenor, |
52 | | const Period& frequency, |
53 | | Natural fixingDays, |
54 | | const Calendar& calendar, |
55 | | BusinessDayConvention convention, |
56 | 0 | bool endOfMonth) { |
57 | 0 | QL_REQUIRE(tenor >= frequency, |
58 | 0 | "XCCY instrument tenor should not be smaller than coupon frequency."); |
59 | | |
60 | 0 | Date referenceDate = calendar.adjust(evaluationDate); |
61 | 0 | Date earliestDate = calendar.advance(referenceDate, fixingDays * Days, convention); |
62 | 0 | Date maturity = earliestDate + tenor; |
63 | 0 | return MakeSchedule() |
64 | 0 | .from(earliestDate) |
65 | 0 | .to(maturity) |
66 | 0 | .withTenor(frequency) |
67 | 0 | .withCalendar(calendar) |
68 | 0 | .withConvention(convention) |
69 | 0 | .endOfMonth(endOfMonth) |
70 | 0 | .backwards(); |
71 | 0 | } |
72 | | |
73 | | Leg buildFloatingLeg(const Date& evaluationDate, |
74 | | const Period& tenor, |
75 | | Natural fixingDays, |
76 | | const Calendar& calendar, |
77 | | BusinessDayConvention convention, |
78 | | bool endOfMonth, |
79 | | const ext::shared_ptr<IborIndex>& idx, |
80 | | ext::optional<Frequency> paymentFrequency, |
81 | 0 | Integer paymentLag) { |
82 | 0 | auto overnightIndex = ext::dynamic_pointer_cast<OvernightIndex>(idx); |
83 | |
|
84 | 0 | Period freqPeriod; |
85 | 0 | if (!paymentFrequency) { |
86 | 0 | QL_REQUIRE(!overnightIndex, "Require payment frequency for overnight indices."); |
87 | 0 | freqPeriod = idx->tenor(); |
88 | 0 | } else { |
89 | 0 | freqPeriod = Period(*paymentFrequency); |
90 | 0 | } |
91 | | |
92 | 0 | Schedule sch = legSchedule(evaluationDate, tenor, freqPeriod, fixingDays, calendar, |
93 | 0 | convention, endOfMonth); |
94 | 0 | if (overnightIndex != nullptr) { |
95 | 0 | return OvernightLeg(sch, overnightIndex) |
96 | 0 | .withNotionals(1.0) |
97 | 0 | .withPaymentLag(paymentLag); |
98 | 0 | } |
99 | 0 | return IborLeg(sch, idx).withNotionals(1.0).withPaymentLag(paymentLag); |
100 | 0 | } |
101 | | |
102 | | std::pair<Real, Real> |
103 | | npvbpsConstNotionalLeg(const Leg& leg, |
104 | | const Date& initialNotionalExchangeDate, |
105 | | const Date& finalNotionalExchangeDate, |
106 | 0 | const Handle<YieldTermStructure>& discountCurveHandle) { |
107 | 0 | const Spread basisPoint = 1.0e-4; |
108 | 0 | Date refDt = discountCurveHandle->referenceDate(); |
109 | 0 | const YieldTermStructure& discountRef = **discountCurveHandle; |
110 | 0 | bool includeSettleDtFlows = true; |
111 | 0 | auto [npv, bps] = CashFlows::npvbps(leg, discountRef, includeSettleDtFlows, refDt, refDt); |
112 | | // Include NPV of the notional exchange at start and maturity. |
113 | 0 | npv += (-1.0) * discountRef.discount(initialNotionalExchangeDate); |
114 | 0 | npv += discountRef.discount(finalNotionalExchangeDate); |
115 | 0 | bps /= basisPoint; |
116 | 0 | return { npv, bps }; |
117 | 0 | } |
118 | | |
119 | | class ResettingLegHelper { |
120 | | public: |
121 | | explicit ResettingLegHelper(const YieldTermStructure& discountCurve, |
122 | | const YieldTermStructure& foreignCurve) |
123 | 0 | : discountCurve_(discountCurve), foreignCurve_(foreignCurve) {} |
124 | 0 | DiscountFactor discount(const Date& d) const { |
125 | 0 | return discountCurve_.discount(d); |
126 | 0 | } |
127 | 0 | Real notionalAdjustment(const Date& d) const { |
128 | 0 | return foreignCurve_.discount(d) / discountCurve_.discount(d); |
129 | 0 | } |
130 | | |
131 | | private: |
132 | | const YieldTermStructure& discountCurve_; |
133 | | const YieldTermStructure& foreignCurve_; |
134 | | }; |
135 | | |
136 | | class ResettingLegCalculator : public AcyclicVisitor, public Visitor<Coupon> { |
137 | | public: |
138 | | explicit ResettingLegCalculator(const YieldTermStructure& discountCurve, |
139 | | const YieldTermStructure& foreignCurve, |
140 | | Integer paymentLag, |
141 | | Calendar paymentCalendar, |
142 | | BusinessDayConvention convention) |
143 | 0 | : helper_(discountCurve, foreignCurve), paymentLag_(paymentLag), |
144 | 0 | paymentCalendar_(std::move(paymentCalendar)), convention_(convention) {} |
145 | | |
146 | 0 | void visit(Coupon& c) override { |
147 | 0 | Date start = c.accrualStartDate(); |
148 | 0 | Date end = c.accrualEndDate(); |
149 | 0 | Time accrual = c.accrualPeriod(); |
150 | 0 | Real adjustedNotional = c.nominal() * helper_.notionalAdjustment(start); |
151 | |
|
152 | 0 | DiscountFactor discountStart, discountEnd; |
153 | |
|
154 | 0 | if (paymentLag_ == 0) { |
155 | 0 | discountStart = helper_.discount(start); |
156 | 0 | discountEnd = helper_.discount(end); |
157 | 0 | } else { |
158 | 0 | Date paymentStart = |
159 | 0 | paymentCalendar_.advance(start, paymentLag_, Days, convention_); |
160 | 0 | Date paymentEnd = paymentCalendar_.advance(end, paymentLag_, Days, convention_); |
161 | 0 | discountStart = helper_.discount(paymentStart); |
162 | 0 | discountEnd = helper_.discount(paymentEnd); |
163 | 0 | } |
164 | | |
165 | | // NPV of a resetting coupon consists of a redemption of borrowed amount occurring |
166 | | // at the end of the accrual period plus the accrued interest, minus the borrowed |
167 | | // amount at the start of the period. All amounts are corrected by an adjustment |
168 | | // corresponding to the implied forward exchange rate, which is estimated by |
169 | | // the ratio of foreign and domestic curves discount factors. |
170 | 0 | Real npvRedeemedAmount = |
171 | 0 | adjustedNotional * discountEnd * (1.0 + c.rate() * accrual); |
172 | 0 | Real npvBorrowedAmount = -adjustedNotional * discountStart; |
173 | |
|
174 | 0 | npv_ += (npvRedeemedAmount + npvBorrowedAmount); |
175 | 0 | bps_ += adjustedNotional * discountEnd * accrual; |
176 | 0 | } |
177 | 0 | Real NPV() const { return npv_; } |
178 | 0 | Real BPS() const { return bps_; } |
179 | | |
180 | | private: |
181 | | ResettingLegHelper helper_; |
182 | | Real npv_ = 0.0; |
183 | | Real bps_ = 0.0; |
184 | | Integer paymentLag_; |
185 | | Calendar paymentCalendar_; |
186 | | BusinessDayConvention convention_; |
187 | | }; |
188 | | |
189 | | std::pair<Real, Real> npvbpsResettingLeg(const Leg& iborLeg, |
190 | | Integer paymentLag, |
191 | | const Calendar& paymentCalendar, |
192 | | BusinessDayConvention convention, |
193 | | const Handle<YieldTermStructure>& discountCurveHandle, |
194 | 0 | const Handle<YieldTermStructure>& foreignCurveHandle) { |
195 | 0 | const YieldTermStructure& discountCurveRef = **discountCurveHandle; |
196 | 0 | const YieldTermStructure& foreignCurveRef = **foreignCurveHandle; |
197 | |
|
198 | 0 | ResettingLegCalculator calc(discountCurveRef, foreignCurveRef, paymentLag, |
199 | 0 | paymentCalendar, convention); |
200 | 0 | for (const auto& i : iborLeg) { |
201 | 0 | CashFlow& cf = *i; |
202 | 0 | cf.accept(calc); |
203 | 0 | } |
204 | 0 | return { calc.NPV(), calc.BPS() }; |
205 | 0 | } |
206 | | } |
207 | | |
208 | | |
209 | | CrossCurrencySwapRateHelperBase::CrossCurrencySwapRateHelperBase( |
210 | | const Handle<Quote>& quote, |
211 | | const Period& tenor, |
212 | | Natural fixingDays, |
213 | | Calendar calendar, |
214 | | BusinessDayConvention convention, |
215 | | bool endOfMonth, |
216 | | Handle<YieldTermStructure> collateralCurve, |
217 | | Integer paymentLag) |
218 | 0 | : RelativeDateRateHelper(quote), tenor_(tenor), fixingDays_(fixingDays), |
219 | 0 | calendar_(std::move(calendar)), convention_(convention), endOfMonth_(endOfMonth), |
220 | 0 | paymentLag_(paymentLag), collateralHandle_(std::move(collateralCurve)) { |
221 | 0 | registerWith(collateralHandle_); |
222 | 0 | } |
223 | | |
224 | 0 | void CrossCurrencySwapRateHelperBase::setTermStructure(YieldTermStructure* t) { |
225 | | // do not set the relinkable handle as an observer - |
226 | | // force recalculation when needed |
227 | 0 | bool observer = false; |
228 | |
|
229 | 0 | ext::shared_ptr<YieldTermStructure> temp(t, null_deleter()); |
230 | 0 | termStructureHandle_.linkTo(temp, observer); |
231 | |
|
232 | 0 | RelativeDateRateHelper::setTermStructure(t); |
233 | 0 | } |
234 | | |
235 | | void CrossCurrencySwapRateHelperBase::initializeDatesFromLegs(const Leg& firstLeg, |
236 | 0 | const Leg& secondLeg) { |
237 | 0 | earliestDate_ = std::min(CashFlows::startDate(firstLeg), |
238 | 0 | CashFlows::startDate(secondLeg)); |
239 | |
|
240 | 0 | maturityDate_ = std::max(CashFlows::maturityDate(firstLeg), |
241 | 0 | CashFlows::maturityDate(secondLeg)); |
242 | |
|
243 | 0 | if (paymentLag_ == 0) { |
244 | 0 | initialNotionalExchangeDate_ = earliestDate_; |
245 | 0 | finalNotionalExchangeDate_ = maturityDate_; |
246 | 0 | } else { |
247 | 0 | initialNotionalExchangeDate_ = calendar_.advance(earliestDate_, paymentLag_, Days, convention_); |
248 | 0 | finalNotionalExchangeDate_ = calendar_.advance(maturityDate_, paymentLag_, Days, convention_); |
249 | 0 | } |
250 | |
|
251 | 0 | Date lastPaymentDate = |
252 | 0 | std::max(firstLeg.back()->date(), |
253 | 0 | secondLeg.back()->date()); |
254 | |
|
255 | 0 | latestRelevantDate_ = latestDate_ = std::max(maturityDate_, lastPaymentDate); |
256 | 0 | } |
257 | | |
258 | | |
259 | | |
260 | | CrossCurrencyBasisSwapRateHelperBase::CrossCurrencyBasisSwapRateHelperBase( |
261 | | const Handle<Quote>& basis, |
262 | | const Period& tenor, |
263 | | Natural fixingDays, |
264 | | Calendar calendar, |
265 | | BusinessDayConvention convention, |
266 | | bool endOfMonth, |
267 | | ext::shared_ptr<IborIndex> baseCurrencyIndex, |
268 | | ext::shared_ptr<IborIndex> quoteCurrencyIndex, |
269 | | Handle<YieldTermStructure> collateralCurve, |
270 | | bool isFxBaseCurrencyCollateralCurrency, |
271 | | bool isBasisOnFxBaseCurrencyLeg, |
272 | | ext::optional<Frequency> paymentFrequency, |
273 | | Integer paymentLag, |
274 | | ext::optional<Frequency> quoteCurrencyPaymentFrequency) |
275 | 0 | : CrossCurrencySwapRateHelperBase(basis, tenor, fixingDays, std::move(calendar), convention, endOfMonth, |
276 | 0 | std::move(collateralCurve), paymentLag), |
277 | 0 | baseCcyIdx_(std::move(baseCurrencyIndex)), quoteCcyIdx_(std::move(quoteCurrencyIndex)), |
278 | 0 | isFxBaseCurrencyCollateralCurrency_(isFxBaseCurrencyCollateralCurrency), |
279 | 0 | isBasisOnFxBaseCurrencyLeg_(isBasisOnFxBaseCurrencyLeg), |
280 | 0 | paymentFrequency_(normalizedPaymentFrequency(paymentFrequency)), |
281 | 0 | quoteCcyPaymentFrequency_(normalizedPaymentFrequency(quoteCurrencyPaymentFrequency)) { |
282 | 0 | registerWith(baseCcyIdx_); |
283 | 0 | registerWith(quoteCcyIdx_); |
284 | |
|
285 | 0 | CrossCurrencyBasisSwapRateHelperBase::initializeDates(); |
286 | 0 | } |
287 | | |
288 | 0 | void CrossCurrencyBasisSwapRateHelperBase::initializeDates() { |
289 | 0 | baseCcyIborLeg_ = buildFloatingLeg(evaluationDate_, tenor_, fixingDays_, calendar_, convention_, |
290 | 0 | endOfMonth_, baseCcyIdx_, paymentFrequency_, paymentLag_); |
291 | | |
292 | | // If no quote-currency payment frequency was given, fall back to the |
293 | | // base-currency payment frequency (which may itself be unset, in which |
294 | | // case the quote-currency leg uses its own index tenor). |
295 | 0 | ext::optional<Frequency> effectiveQuoteCcyFreq = |
296 | 0 | quoteCcyPaymentFrequency_ ? quoteCcyPaymentFrequency_ : paymentFrequency_; |
297 | 0 | quoteCcyIborLeg_ = buildFloatingLeg(evaluationDate_, tenor_, fixingDays_, calendar_, |
298 | 0 | convention_, endOfMonth_, quoteCcyIdx_, effectiveQuoteCcyFreq, paymentLag_); |
299 | |
|
300 | 0 | initializeDatesFromLegs(baseCcyIborLeg_, quoteCcyIborLeg_); |
301 | 0 | } |
302 | | |
303 | | const Handle<YieldTermStructure>& |
304 | 0 | CrossCurrencyBasisSwapRateHelperBase::baseCcyLegDiscountHandle() const { |
305 | 0 | return isFxBaseCurrencyCollateralCurrency_ ? collateralHandle_ : termStructureHandle_; |
306 | 0 | } |
307 | | |
308 | | const Handle<YieldTermStructure>& |
309 | 0 | CrossCurrencyBasisSwapRateHelperBase::quoteCcyLegDiscountHandle() const { |
310 | 0 | return isFxBaseCurrencyCollateralCurrency_ ? termStructureHandle_ : collateralHandle_; |
311 | 0 | } |
312 | | |
313 | | ConstNotionalCrossCurrencyBasisSwapRateHelper::ConstNotionalCrossCurrencyBasisSwapRateHelper( |
314 | | const Handle<Quote>& basis, |
315 | | const Period& tenor, |
316 | | Natural fixingDays, |
317 | | const Calendar& calendar, |
318 | | BusinessDayConvention convention, |
319 | | bool endOfMonth, |
320 | | const ext::shared_ptr<IborIndex>& baseCurrencyIndex, |
321 | | const ext::shared_ptr<IborIndex>& quoteCurrencyIndex, |
322 | | const Handle<YieldTermStructure>& collateralCurve, |
323 | | bool isFxBaseCurrencyCollateralCurrency, |
324 | | bool isBasisOnFxBaseCurrencyLeg, |
325 | | ext::optional<Frequency> paymentFrequency, |
326 | | Integer paymentLag, |
327 | | ext::optional<Frequency> quoteCurrencyPaymentFrequency) |
328 | 0 | : CrossCurrencyBasisSwapRateHelperBase(basis, |
329 | 0 | tenor, |
330 | 0 | fixingDays, |
331 | 0 | calendar, |
332 | 0 | convention, |
333 | 0 | endOfMonth, |
334 | 0 | baseCurrencyIndex, |
335 | 0 | quoteCurrencyIndex, |
336 | 0 | collateralCurve, |
337 | 0 | isFxBaseCurrencyCollateralCurrency, |
338 | 0 | isBasisOnFxBaseCurrencyLeg, |
339 | 0 | paymentFrequency, |
340 | 0 | paymentLag, |
341 | 0 | quoteCurrencyPaymentFrequency) {} |
342 | | |
343 | 0 | Real ConstNotionalCrossCurrencyBasisSwapRateHelper::impliedQuote() const { |
344 | 0 | QL_REQUIRE(!termStructureHandle_.empty(), "term structure not set"); |
345 | 0 | QL_REQUIRE(!collateralHandle_.empty(), "collateral term structure not set"); |
346 | | |
347 | 0 | auto [npvBaseCcy, bpsBaseCcy] = npvbpsConstNotionalLeg(baseCcyIborLeg_, initialNotionalExchangeDate_, finalNotionalExchangeDate_, baseCcyLegDiscountHandle()); |
348 | 0 | auto [npvQuoteCcy, bpsQuoteCcy] = npvbpsConstNotionalLeg(quoteCcyIborLeg_, initialNotionalExchangeDate_, finalNotionalExchangeDate_, quoteCcyLegDiscountHandle()); |
349 | |
|
350 | 0 | Real bps = isBasisOnFxBaseCurrencyLeg_ ? -bpsBaseCcy : bpsQuoteCcy; |
351 | |
|
352 | 0 | QL_REQUIRE(std::fabs(bps) > 0.0, "null BPS"); |
353 | | |
354 | 0 | return -(npvQuoteCcy - npvBaseCcy) / bps; |
355 | 0 | } |
356 | | |
357 | 0 | void ConstNotionalCrossCurrencyBasisSwapRateHelper::accept(AcyclicVisitor& v) { |
358 | 0 | auto* v1 = dynamic_cast<Visitor<ConstNotionalCrossCurrencyBasisSwapRateHelper>*>(&v); |
359 | 0 | if (v1 != nullptr) |
360 | 0 | v1->visit(*this); |
361 | 0 | else |
362 | 0 | RateHelper::accept(v); |
363 | 0 | } |
364 | | |
365 | | |
366 | | MtMCrossCurrencyBasisSwapRateHelper::MtMCrossCurrencyBasisSwapRateHelper( |
367 | | const Handle<Quote>& basis, |
368 | | const Period& tenor, |
369 | | Natural fixingDays, |
370 | | const Calendar& calendar, |
371 | | BusinessDayConvention convention, |
372 | | bool endOfMonth, |
373 | | const ext::shared_ptr<IborIndex>& baseCurrencyIndex, |
374 | | const ext::shared_ptr<IborIndex>& quoteCurrencyIndex, |
375 | | const Handle<YieldTermStructure>& collateralCurve, |
376 | | bool isFxBaseCurrencyCollateralCurrency, |
377 | | bool isBasisOnFxBaseCurrencyLeg, |
378 | | bool isFxBaseCurrencyLegResettable, |
379 | | ext::optional<Frequency> paymentFrequency, |
380 | | Integer paymentLag, |
381 | | ext::optional<Frequency> quoteCurrencyPaymentFrequency) |
382 | 0 | : CrossCurrencyBasisSwapRateHelperBase(basis, |
383 | 0 | tenor, |
384 | 0 | fixingDays, |
385 | 0 | calendar, |
386 | 0 | convention, |
387 | 0 | endOfMonth, |
388 | 0 | baseCurrencyIndex, |
389 | 0 | quoteCurrencyIndex, |
390 | 0 | collateralCurve, |
391 | 0 | isFxBaseCurrencyCollateralCurrency, |
392 | 0 | isBasisOnFxBaseCurrencyLeg, |
393 | 0 | paymentFrequency, |
394 | 0 | paymentLag, |
395 | 0 | quoteCurrencyPaymentFrequency), |
396 | 0 | isFxBaseCurrencyLegResettable_(isFxBaseCurrencyLegResettable) {} |
397 | | |
398 | 0 | Real MtMCrossCurrencyBasisSwapRateHelper::impliedQuote() const { |
399 | 0 | QL_REQUIRE(!termStructureHandle_.empty(), "term structure not set"); |
400 | 0 | QL_REQUIRE(!collateralHandle_.empty(), "collateral term structure not set"); |
401 | | |
402 | 0 | auto [npvBaseCcy, bpsBaseCcy] = |
403 | 0 | isFxBaseCurrencyLegResettable_ ? |
404 | 0 | npvbpsResettingLeg(baseCcyIborLeg_, paymentLag_, calendar_, convention_, |
405 | 0 | baseCcyLegDiscountHandle(), quoteCcyLegDiscountHandle()) : |
406 | 0 | npvbpsConstNotionalLeg(baseCcyIborLeg_, initialNotionalExchangeDate_, |
407 | 0 | finalNotionalExchangeDate_, baseCcyLegDiscountHandle()); |
408 | |
|
409 | 0 | auto [npvQuoteCcy, bpsQuoteCcy] = |
410 | 0 | isFxBaseCurrencyLegResettable_ ? |
411 | 0 | npvbpsConstNotionalLeg(quoteCcyIborLeg_, initialNotionalExchangeDate_, |
412 | 0 | finalNotionalExchangeDate_, quoteCcyLegDiscountHandle()) : |
413 | 0 | npvbpsResettingLeg(quoteCcyIborLeg_, paymentLag_, calendar_, convention_, |
414 | 0 | quoteCcyLegDiscountHandle(), baseCcyLegDiscountHandle()); |
415 | |
|
416 | 0 | Real bps = isBasisOnFxBaseCurrencyLeg_ ? -bpsBaseCcy : bpsQuoteCcy; |
417 | |
|
418 | 0 | QL_REQUIRE(std::fabs(bps) > 0.0, "null BPS"); |
419 | | |
420 | 0 | return -(npvQuoteCcy - npvBaseCcy) / bps; |
421 | 0 | } |
422 | | |
423 | 0 | void MtMCrossCurrencyBasisSwapRateHelper::accept(AcyclicVisitor& v) { |
424 | 0 | auto* v1 = dynamic_cast<Visitor<MtMCrossCurrencyBasisSwapRateHelper>*>(&v); |
425 | 0 | if (v1 != nullptr) |
426 | 0 | v1->visit(*this); |
427 | 0 | else |
428 | 0 | RateHelper::accept(v); |
429 | 0 | } |
430 | | |
431 | | |
432 | | ConstNotionalCrossCurrencySwapRateHelper::ConstNotionalCrossCurrencySwapRateHelper( |
433 | | const Handle<Quote>& fixedRate, |
434 | | const Period& tenor, |
435 | | Natural fixingDays, |
436 | | const Calendar& calendar, |
437 | | BusinessDayConvention convention, |
438 | | bool endOfMonth, |
439 | | Frequency fixedFrequency, |
440 | | DayCounter fixedDayCount, |
441 | | const ext::shared_ptr<IborIndex>& floatIndex, |
442 | | const Handle<YieldTermStructure>& collateralCurve, |
443 | | bool collateralOnFixedLeg, |
444 | | Integer paymentLag) |
445 | 0 | : CrossCurrencySwapRateHelperBase(fixedRate, tenor, fixingDays, calendar, convention, endOfMonth, |
446 | 0 | collateralCurve, paymentLag), |
447 | 0 | fixedFrequency_(fixedFrequency), |
448 | 0 | fixedDayCount_(std::move(fixedDayCount)), |
449 | 0 | floatIndex_(floatIndex), |
450 | 0 | collateralOnFixedLeg_(collateralOnFixedLeg) { |
451 | |
|
452 | 0 | QL_REQUIRE(floatIndex_, "floating index required"); |
453 | 0 | registerWith(floatIndex_); |
454 | |
|
455 | 0 | initializeDates(); |
456 | 0 | } |
457 | | |
458 | 0 | void ConstNotionalCrossCurrencySwapRateHelper::initializeDates() { |
459 | 0 | auto overnightIndex = ext::dynamic_pointer_cast<OvernightIndex>(floatIndex_); |
460 | |
|
461 | 0 | Period floatFreqPeriod; |
462 | 0 | if (floatIndex_->tenor().frequency() == NoFrequency) { |
463 | 0 | QL_REQUIRE(!overnightIndex, "Require payment frequency for overnight indices."); |
464 | 0 | floatFreqPeriod = floatIndex_->tenor(); |
465 | 0 | } else { |
466 | 0 | floatFreqPeriod = Period(floatIndex_->tenor().frequency()); |
467 | 0 | } |
468 | | |
469 | 0 | Real nominal = 1.0; |
470 | 0 | Schedule fixedSch = legSchedule(evaluationDate_, tenor_, Period(fixedFrequency_), fixingDays_, calendar_, |
471 | 0 | convention_, endOfMonth_); |
472 | 0 | Schedule floatSch = legSchedule(evaluationDate_, tenor_, floatFreqPeriod, fixingDays_, floatIndex_->fixingCalendar(), |
473 | 0 | floatIndex_->businessDayConvention(), endOfMonth_); |
474 | |
|
475 | 0 | xccySwap_ = ext::make_shared<ConstNotionalCrossCurrencyFixedVsFloatingSwap>( |
476 | 0 | Swap::Payer, |
477 | 0 | nominal, |
478 | 0 | Currency(), |
479 | 0 | fixedSch, |
480 | 0 | sample_fixed_rate, |
481 | 0 | fixedDayCount_, |
482 | 0 | convention_, |
483 | 0 | paymentLag_, |
484 | 0 | calendar_, |
485 | 0 | nominal, |
486 | 0 | floatIndex_->currency(), |
487 | 0 | floatSch, |
488 | 0 | floatIndex_, |
489 | 0 | Spread(0.0), |
490 | 0 | floatIndex_->businessDayConvention(), |
491 | 0 | paymentLag_, |
492 | 0 | calendar_ |
493 | 0 | ); |
494 | 0 | auto engine = ext::make_shared<DiscountingConstNotionalCrossCurrencySwapEngine>( |
495 | 0 | floatIndex_->currency(), floatingLegDiscountHandle(), |
496 | 0 | Currency(), fixedLegDiscountHandle(), |
497 | 0 | makeQuoteHandle(1.0), true); |
498 | 0 | xccySwap_->setPricingEngine(engine); |
499 | |
|
500 | 0 | initializeDatesFromLegs(xccySwap_->leg(0), xccySwap_->leg(1)); |
501 | 0 | } |
502 | | |
503 | | const Handle<YieldTermStructure>& |
504 | 0 | ConstNotionalCrossCurrencySwapRateHelper::fixedLegDiscountHandle() const { |
505 | 0 | return collateralOnFixedLeg_ ? collateralHandle_ : termStructureHandle_; |
506 | 0 | } |
507 | | |
508 | | const Handle<YieldTermStructure>& |
509 | 0 | ConstNotionalCrossCurrencySwapRateHelper::floatingLegDiscountHandle() const { |
510 | 0 | return collateralOnFixedLeg_ ? termStructureHandle_ : collateralHandle_; |
511 | 0 | } |
512 | | |
513 | 0 | Real ConstNotionalCrossCurrencySwapRateHelper::impliedQuote() const { |
514 | 0 | QL_REQUIRE(!termStructureHandle_.empty(), "term structure not set"); |
515 | 0 | QL_REQUIRE(!collateralHandle_.empty(), "collateral term structure not set"); |
516 | 0 | xccySwap_->deepUpdate(); |
517 | |
|
518 | 0 | return xccySwap_->fairRate(); |
519 | 0 | } |
520 | | |
521 | 0 | void ConstNotionalCrossCurrencySwapRateHelper::accept(AcyclicVisitor& v) { |
522 | 0 | auto* v1 = dynamic_cast<Visitor<ConstNotionalCrossCurrencySwapRateHelper>*>(&v); |
523 | 0 | if (v1 != nullptr) |
524 | 0 | v1->visit(*this); |
525 | 0 | else |
526 | 0 | RateHelper::accept(v); |
527 | 0 | } |
528 | | |
529 | | } |