/src/quantlib/ql/cashflows/overnightindexedcoupon.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2009 Roland Lichters |
5 | | Copyright (C) 2009 Ferdinando Ametrano |
6 | | Copyright (C) 2014 Peter Caspers |
7 | | Copyright (C) 2017 Joseph Jeisman |
8 | | Copyright (C) 2017 Fabrice Lecuyer |
9 | | |
10 | | This file is part of QuantLib, a free-software/open-source library |
11 | | for financial quantitative analysts and developers - http://quantlib.org/ |
12 | | |
13 | | QuantLib is free software: you can redistribute it and/or modify it |
14 | | under the terms of the QuantLib license. You should have received a |
15 | | copy of the license along with this program; if not, please email |
16 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
17 | | <https://www.quantlib.org/license.shtml>. |
18 | | |
19 | | This program is distributed in the hope that it will be useful, but WITHOUT |
20 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
21 | | FOR A PARTICULAR PURPOSE. See the license for more details. |
22 | | */ |
23 | | |
24 | | #include <ql/cashflows/couponpricer.hpp> |
25 | | #include <ql/cashflows/cashflowvectors.hpp> |
26 | | #include <ql/cashflows/overnightindexedcouponpricer.hpp> |
27 | | #include <ql/cashflows/blackovernightindexedcouponpricer.hpp> |
28 | | #include <ql/cashflows/overnightindexedcoupon.hpp> |
29 | | #include <ql/cashflows/fixedratecoupon.hpp> |
30 | | #include <ql/termstructures/yieldtermstructure.hpp> |
31 | | #include <ql/time/calendars/weekendsonly.hpp> |
32 | | #include <ql/utilities/vectors.hpp> |
33 | | #include <utility> |
34 | | #include <algorithm> |
35 | | #include <type_traits> |
36 | | |
37 | | using std::vector; |
38 | | |
39 | | namespace QuantLib { |
40 | | |
41 | | namespace { |
42 | | Date applyLookbackPeriod(const ext::shared_ptr<InterestRateIndex>& index, |
43 | | const Date& valueDate, |
44 | 0 | Natural lookbackDays) { |
45 | 0 | return index->fixingCalendar().advance(valueDate, -static_cast<Integer>(lookbackDays), |
46 | 0 | Days); |
47 | 0 | } |
48 | | } |
49 | | |
50 | | OvernightIndexedCoupon::OvernightIndexedCoupon( |
51 | | const Date& paymentDate, |
52 | | Real nominal, |
53 | | const Date& startDate, |
54 | | const Date& endDate, |
55 | | const ext::shared_ptr<OvernightIndex>& overnightIndex, |
56 | | Real gearing, |
57 | | Spread spread, |
58 | | const Date& refPeriodStart, |
59 | | const Date& refPeriodEnd, |
60 | | const DayCounter& dayCounter, |
61 | | bool telescopicValueDates, |
62 | | RateAveraging::Type averagingMethod, |
63 | | Natural lookbackDays, |
64 | | Natural lockoutDays, |
65 | | bool applyObservationShift, |
66 | | bool compoundSpreadDaily, |
67 | | const Date& rateComputationStartDate, |
68 | | const Date& rateComputationEndDate) |
69 | 0 | : FloatingRateCoupon(paymentDate, nominal, startDate, endDate, |
70 | 0 | lookbackDays, |
71 | 0 | overnightIndex, |
72 | 0 | gearing, spread, |
73 | 0 | refPeriodStart, refPeriodEnd, |
74 | 0 | dayCounter, false), |
75 | 0 | averagingMethod_(averagingMethod), lockoutDays_(lockoutDays), |
76 | 0 | applyObservationShift_(applyObservationShift), |
77 | 0 | compoundSpreadDaily_(compoundSpreadDaily), |
78 | 0 | rateComputationStartDate_(rateComputationStartDate), |
79 | 0 | rateComputationEndDate_(rateComputationEndDate) { |
80 | | |
81 | | // ctor guard prevents construction of an object with illogically ordered dates. |
82 | 0 | QL_REQUIRE(paymentDate >= endDate, |
83 | 0 | "Payment date cannot be earlier than accrual end date"); |
84 | | |
85 | 0 | Date valueStart = rateComputationStartDate_ == Null<Date>() ? startDate : rateComputationStartDate_; |
86 | 0 | Date valueEnd = rateComputationEndDate_ == Null<Date>() ? endDate : rateComputationEndDate_; |
87 | 0 | if (lookbackDays != Null<Natural>()) { |
88 | 0 | BusinessDayConvention bdc = lookbackDays > 0 ? Preceding : Following; |
89 | 0 | valueStart = overnightIndex->fixingCalendar().advance(valueStart, -static_cast<Integer>(lookbackDays), Days, bdc); |
90 | 0 | valueEnd = overnightIndex->fixingCalendar().advance(valueEnd, -static_cast<Integer>(lookbackDays), Days, bdc); |
91 | 0 | } |
92 | | |
93 | | // value dates |
94 | 0 | Date tmpEndDate = endDate; |
95 | | |
96 | | /* For the coupon's valuation only the first and last future valuation |
97 | | dates matter, therefore we can avoid to construct the whole series |
98 | | of valuation dates, a front and back stub will do. However notice |
99 | | that if the global evaluation date moves forward it might run past |
100 | | the front stub of valuation dates we build here (which incorporates |
101 | | a grace period of 7 business after the evaluation date). This will |
102 | | lead to false coupon projections (see the warning the class header). */ |
103 | |
|
104 | 0 | QL_REQUIRE(canApplyTelescopicFormula() || !telescopicValueDates, |
105 | 0 | "Telescopic formula cannot be applied for a coupon with lookback."); |
106 | | |
107 | 0 | if (telescopicValueDates) { |
108 | | // build optimised value dates schedule: front stub goes |
109 | | // from start date to max(evalDate,startDate) + 7bd |
110 | 0 | Date evalDate = Settings::instance().evaluationDate(); |
111 | 0 | tmpEndDate = overnightIndex->fixingCalendar().advance( |
112 | 0 | std::max(startDate, evalDate), 7, Days, Following); |
113 | 0 | tmpEndDate = std::min(tmpEndDate, endDate); |
114 | 0 | } |
115 | 0 | Schedule sch = |
116 | 0 | MakeSchedule() |
117 | 0 | .from(startDate) |
118 | | // .to(endDate) |
119 | 0 | .to(tmpEndDate) |
120 | 0 | .withTenor(1 * Days) |
121 | 0 | .withCalendar(overnightIndex->fixingCalendar()) |
122 | 0 | .withConvention(overnightIndex->businessDayConvention()) |
123 | 0 | .backwards(); |
124 | 0 | valueDates_ = sch.dates(); |
125 | |
|
126 | 0 | if (telescopicValueDates) { |
127 | | // if lockout days are defined, we need to ensure that |
128 | | // the lockout period is covered by the value dates |
129 | 0 | tmpEndDate = overnightIndex->fixingCalendar().adjust( |
130 | 0 | endDate, overnightIndex->businessDayConvention()); |
131 | 0 | Date tmpLockoutDate = overnightIndex->fixingCalendar().advance( |
132 | 0 | endDate, -std::max<Integer>(lockoutDays_, 1), Days, Preceding); |
133 | 0 | while (tmpLockoutDate <= tmpEndDate) |
134 | 0 | { |
135 | 0 | if (tmpLockoutDate > valueDates_.back()) |
136 | 0 | valueDates_.push_back(tmpLockoutDate); |
137 | 0 | tmpLockoutDate = |
138 | 0 | overnightIndex->fixingCalendar().advance(tmpLockoutDate, 1, Days, Following); |
139 | 0 | } |
140 | 0 | } |
141 | |
|
142 | 0 | QL_ENSURE(valueDates_.size()>=2, "degenerate schedule"); |
143 | | |
144 | 0 | n_ = valueDates_.size() - 1; |
145 | |
|
146 | 0 | interestDates_ = vector<Date>(valueDates_.begin(), valueDates_.end()); |
147 | |
|
148 | 0 | if (fixingDays_ == overnightIndex->fixingDays() && fixingDays_ == 0) { |
149 | 0 | fixingDates_ = vector<Date>(valueDates_.begin(), valueDates_.end() - 1); |
150 | 0 | } else { |
151 | | // Lookback (fixing days) without observation shift: |
152 | | // The date that the fixing rate is pulled from (the observation date) is k |
153 | | // business days before the date that interest is applied (the interest date) |
154 | | // and is applied for the number of calendar days until the next business |
155 | | // day following the interest date. |
156 | 0 | fixingDates_.resize(n_); |
157 | 0 | for (Size i = 0; i <= n_; ++i) { |
158 | 0 | Date tmp = applyLookbackPeriod(overnightIndex, valueDates_[i], fixingDays_); |
159 | 0 | if (i < n_) |
160 | 0 | fixingDates_[i] = tmp; |
161 | 0 | if (applyObservationShift_) |
162 | | // Lookback (fixing days) with observation shift: |
163 | | // The date that the fixing rate is pulled from (the observation date) |
164 | | // is k business days before the date that interest is applied |
165 | | // (the interest date) and is applied for the number of calendar |
166 | | // days until the next business day following the observation date. |
167 | | // This means that the fixing dates periods align with value dates. |
168 | 0 | interestDates_[i] = tmp; |
169 | 0 | if (fixingDays_ != overnightIndex->fixingDays()) |
170 | | // If fixing dates of the coupon deviate from fixing days in the index |
171 | | // we need to correct the value dates such that they reflect dates |
172 | | // corresponding to a deposit instrument linked to the index. |
173 | | // This is to ensure that future projections (which are computed |
174 | | // based on the value dates) of the index do not |
175 | | // yield any convexity corrections. |
176 | 0 | valueDates_[i] = overnightIndex->valueDate(tmp); |
177 | 0 | } |
178 | 0 | } |
179 | | // When lockout is used the fixing rate applied for the last k days of the |
180 | | // interest period is frozen at the rate observed k days before the period ends. |
181 | 0 | if (lockoutDays_ != 0) { |
182 | 0 | QL_REQUIRE(lockoutDays_ > 0 && lockoutDays_ < n_, |
183 | 0 | "Lockout period cannot be negative or exceed the number of fixing days."); |
184 | 0 | Date lockoutDate = fixingDates_[n_ - 1 - lockoutDays_]; |
185 | 0 | for (Size i = n_ - 1; i > n_ - 1 - lockoutDays_; --i) |
186 | 0 | fixingDates_[i] = lockoutDate; |
187 | 0 | } |
188 | | |
189 | | // accrual (compounding) periods |
190 | 0 | dt_.resize(n_); |
191 | 0 | const DayCounter& dc = overnightIndex->dayCounter(); |
192 | 0 | for (Size i=0; i<n_; ++i) |
193 | 0 | dt_[i] = dc.yearFraction(interestDates_[i], interestDates_[i + 1]); |
194 | |
|
195 | 0 | switch (averagingMethod) { |
196 | 0 | case RateAveraging::Simple: |
197 | 0 | QL_REQUIRE( |
198 | 0 | fixingDays_ == overnightIndex->fixingDays() && !applyObservationShift_ && lockoutDays_ == 0, |
199 | 0 | "Cannot price an overnight coupon with simple averaging with lookback or lockout."); |
200 | 0 | setPricer(ext::make_shared<ArithmeticAveragedOvernightIndexedCouponPricer>(telescopicValueDates)); |
201 | 0 | break; |
202 | 0 | case RateAveraging::Compound: |
203 | 0 | setPricer(ext::make_shared<CompoundingOvernightIndexedCouponPricer>()); |
204 | 0 | break; |
205 | 0 | default: |
206 | 0 | QL_FAIL("unknown compounding convention (" << Integer(averagingMethod) << ")"); |
207 | 0 | } |
208 | 0 | } Unexecuted instantiation: QuantLib::OvernightIndexedCoupon::OvernightIndexedCoupon(QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::OvernightIndex> const&, double, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::DayCounter const&, bool, QuantLib::RateAveraging::Type, unsigned int, unsigned int, bool, bool, QuantLib::Date const&, QuantLib::Date const&) Unexecuted instantiation: QuantLib::OvernightIndexedCoupon::OvernightIndexedCoupon(QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::OvernightIndex> const&, double, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::DayCounter const&, bool, QuantLib::RateAveraging::Type, unsigned int, unsigned int, bool, bool, QuantLib::Date const&, QuantLib::Date const&) |
209 | | |
210 | 0 | Real OvernightIndexedCoupon::accruedAmount(const Date& d) const { |
211 | 0 | if (d <= accrualStartDate_ || d > paymentDate_) { |
212 | | // out of coupon range |
213 | 0 | return 0.0; |
214 | 0 | } else if (tradingExCoupon(d)) { |
215 | 0 | return nominal() * averageRate(d) * accruedPeriod(d); |
216 | 0 | } else { |
217 | | // usual case |
218 | 0 | return nominal() * averageRate(std::min(d, accrualEndDate_)) * accruedPeriod(d); |
219 | 0 | } |
220 | 0 | } |
221 | | |
222 | 0 | Rate OvernightIndexedCoupon::averageRate(const Date& d) const { |
223 | 0 | QL_REQUIRE(pricer_, "pricer not set"); |
224 | 0 | pricer_->initialize(*this); |
225 | 0 | if (const auto overnightIndexedPricer = |
226 | 0 | ext::dynamic_pointer_cast<OvernightIndexedCouponPricer>(pricer_)) { |
227 | 0 | return overnightIndexedPricer->averageRate(d); |
228 | 0 | } |
229 | 0 | return pricer_->swapletRate(); |
230 | 0 | } |
231 | | |
232 | 0 | const vector<Rate>& OvernightIndexedCoupon::indexFixings() const { |
233 | 0 | fixings_.resize(n_); |
234 | 0 | for (Size i=0; i<n_; ++i) |
235 | 0 | fixings_[i] = index_->fixing(fixingDates_[i]); |
236 | 0 | return fixings_; |
237 | 0 | } |
238 | | |
239 | 0 | void OvernightIndexedCoupon::accept(AcyclicVisitor& v) { |
240 | 0 | auto* v1 = dynamic_cast<Visitor<OvernightIndexedCoupon>*>(&v); |
241 | 0 | if (v1 != nullptr) { |
242 | 0 | v1->visit(*this); |
243 | 0 | } else { |
244 | 0 | FloatingRateCoupon::accept(v); |
245 | 0 | } |
246 | 0 | } |
247 | | |
248 | 0 | Real OvernightIndexedCoupon::effectiveSpread() const { |
249 | 0 | if (!compoundSpreadDaily_) |
250 | 0 | return spread(); |
251 | | |
252 | 0 | if (averagingMethod_ == RateAveraging::Simple) |
253 | 0 | return spread(); |
254 | | |
255 | 0 | auto p = ext::dynamic_pointer_cast<CompoundingOvernightIndexedCouponPricer>(pricer()); |
256 | 0 | QL_REQUIRE(p, "OvernightIndexedCoupon::effectiveSpread(): expected OvernightIndexedCouponPricer"); |
257 | 0 | p->initialize(*this); |
258 | 0 | return p->effectiveSpread(); |
259 | 0 | } |
260 | | |
261 | 0 | Real OvernightIndexedCoupon::effectiveIndexFixing() const { |
262 | 0 | auto p = ext::dynamic_pointer_cast<CompoundingOvernightIndexedCouponPricer>(pricer()); |
263 | | |
264 | 0 | if (averagingMethod_ == RateAveraging::Simple) |
265 | 0 | QL_FAIL("Average OIS Coupon does not have an effectiveIndexFixing"); // FIXME: better error message |
266 | | |
267 | 0 | QL_REQUIRE(p, "OvernightIndexedCoupon::effectiveSpread(): expected OvernightIndexedCouponPricer"); |
268 | 0 | p->initialize(*this); |
269 | 0 | return p->effectiveIndexFixing(); |
270 | 0 | } |
271 | | |
272 | | // CappedFlooredOvernightIndexedCoupon implementation |
273 | | |
274 | | CappedFlooredOvernightIndexedCoupon::CappedFlooredOvernightIndexedCoupon( |
275 | | const ext::shared_ptr<OvernightIndexedCoupon>& underlying, Real cap, Real floor, bool nakedOption, |
276 | | bool dailyCapFloor) |
277 | 0 | : FloatingRateCoupon(underlying->date(), underlying->nominal(), underlying->accrualStartDate(), |
278 | 0 | underlying->accrualEndDate(), underlying->fixingDays(), underlying->index(), |
279 | 0 | underlying->gearing(), underlying->spread(), underlying->referencePeriodStart(), |
280 | 0 | underlying->referencePeriodEnd(), underlying->dayCounter(), false), |
281 | 0 | underlying_(underlying), nakedOption_(nakedOption), dailyCapFloor_(dailyCapFloor) { |
282 | |
|
283 | 0 | QL_REQUIRE(!underlying_->compoundSpreadDaily() || close_enough(underlying_->gearing(), 1.0), |
284 | 0 | "CappedFlooredOvernightIndexedCoupon: if include spread = true, only a gearing 1.0 is allowed - scale " |
285 | 0 | "the notional in this case instead."); |
286 | | |
287 | 0 | if (!dailyCapFloor) { |
288 | 0 | if (gearing_ > 0.0) { |
289 | 0 | cap_ = cap; |
290 | 0 | floor_ = floor; |
291 | 0 | } else { |
292 | 0 | cap_ = floor; |
293 | 0 | floor_ = cap; |
294 | 0 | } |
295 | 0 | } else { |
296 | 0 | cap_ = cap; |
297 | 0 | floor_ = floor; |
298 | 0 | } |
299 | 0 | if (cap_ != Null<Real>() && floor_ != Null<Real>()) { |
300 | 0 | QL_REQUIRE(cap_ >= floor, "cap level (" << cap_ << ") less than floor level (" << floor_ << ")"); |
301 | 0 | } |
302 | 0 | registerWith(underlying_); |
303 | 0 | if (nakedOption_) |
304 | 0 | underlying_->alwaysForwardNotifications(); |
305 | 0 | } Unexecuted instantiation: QuantLib::CappedFlooredOvernightIndexedCoupon::CappedFlooredOvernightIndexedCoupon(boost::shared_ptr<QuantLib::OvernightIndexedCoupon> const&, double, double, bool, bool) Unexecuted instantiation: QuantLib::CappedFlooredOvernightIndexedCoupon::CappedFlooredOvernightIndexedCoupon(boost::shared_ptr<QuantLib::OvernightIndexedCoupon> const&, double, double, bool, bool) |
306 | | |
307 | 0 | void CappedFlooredOvernightIndexedCoupon::alwaysForwardNotifications() { |
308 | 0 | LazyObject::alwaysForwardNotifications(); |
309 | 0 | underlying_->alwaysForwardNotifications(); |
310 | 0 | } |
311 | | |
312 | 0 | void CappedFlooredOvernightIndexedCoupon::deepUpdate() { |
313 | 0 | update(); |
314 | 0 | underlying_->deepUpdate(); |
315 | 0 | } |
316 | | |
317 | 0 | void CappedFlooredOvernightIndexedCoupon::performCalculations() const { |
318 | 0 | QL_REQUIRE(underlying_->pricer(), "underlying coupon pricer not set"); |
319 | 0 | Rate swapletRate = nakedOption_ ? 0.0 : underlying_->rate(); |
320 | 0 | auto cfONPricer = ext::dynamic_pointer_cast<OvernightIndexedCouponPricer>(pricer()); |
321 | 0 | QL_REQUIRE(cfONPricer, "coupon pricer not an instance of OvernightIndexedCouponPricer"); |
322 | | |
323 | 0 | if (floor_ != Null<Real>() || cap_ != Null<Real>()) |
324 | 0 | cfONPricer->initialize(*this); |
325 | 0 | Rate floorletRate = 0.; |
326 | 0 | if (floor_ != Null<Real>()) |
327 | 0 | floorletRate = cfONPricer->floorletRate(effectiveFloor(), dailyCapFloor()); |
328 | 0 | Rate capletRate = 0.; |
329 | 0 | if (cap_ != Null<Real>()) |
330 | 0 | capletRate = (nakedOption_ && floor_ == Null<Real>() ? -1.0 : 1.0) * cfONPricer->capletRate(effectiveCap(), dailyCapFloor()); |
331 | 0 | rate_ = swapletRate + floorletRate - capletRate; |
332 | |
|
333 | 0 | effectiveCapletVolatility_ = cfONPricer->effectiveCapletVolatility(); |
334 | 0 | effectiveFloorletVolatility_ = cfONPricer->effectiveFloorletVolatility(); |
335 | 0 | } |
336 | | |
337 | 0 | Rate CappedFlooredOvernightIndexedCoupon::cap() const { return gearing_ > 0.0 ? cap_ : floor_; } |
338 | | |
339 | 0 | Rate CappedFlooredOvernightIndexedCoupon::floor() const { return gearing_ > 0.0 ? floor_ : cap_; } |
340 | | |
341 | 0 | Rate CappedFlooredOvernightIndexedCoupon::rate() const { |
342 | 0 | calculate(); |
343 | 0 | return rate_; |
344 | 0 | } |
345 | | |
346 | 0 | Rate CappedFlooredOvernightIndexedCoupon::convexityAdjustment() const { return underlying_->convexityAdjustment(); } |
347 | | |
348 | 0 | Rate CappedFlooredOvernightIndexedCoupon::effectiveCap() const { |
349 | 0 | if (cap_ == Null<Real>()) |
350 | 0 | return Null<Real>(); |
351 | | /* We have four cases dependent on dailyCapFloor_ and compoundSpreadDaily. Notation in the formulas: |
352 | | g gearing, |
353 | | s spread, |
354 | | A coupon amount, |
355 | | f_i daily fixings, |
356 | | \tau_i daily accrual fractions, |
357 | | \tau coupon accrual fraction, |
358 | | C cap rate |
359 | | F floor rate |
360 | | */ |
361 | 0 | if (dailyCapFloor_) { |
362 | 0 | if (underlying_->compoundSpreadDaily()) { |
363 | | // A = g \cdot \frac{\prod (1 + \tau_i \min ( \max ( f_i + s , F), C)) - 1}{\tau} |
364 | 0 | return cap_ - underlying_->spread(); |
365 | 0 | } else { |
366 | | // A = g \cdot \frac{\prod (1 + \tau_i \min ( \max ( f_i , F), C)) - 1}{\tau} + s |
367 | 0 | return cap_; |
368 | 0 | } |
369 | 0 | } else { |
370 | 0 | if (underlying_->compoundSpreadDaily()) { |
371 | | // A = \min \left( \max \left( g \cdot \frac{\prod (1 + \tau_i(f_i + s)) - 1}{\tau}, F \right), C \right) |
372 | 0 | return (cap_ / gearing() - underlying_->effectiveSpread()); |
373 | 0 | } else { |
374 | | // A = \min \left( \max \left( g \cdot \frac{\prod (1 + \tau_i f_i) - 1}{\tau} + s, F \right), C \right) |
375 | 0 | return (cap_ - underlying_->effectiveSpread()) / gearing(); |
376 | 0 | } |
377 | 0 | } |
378 | 0 | } |
379 | | |
380 | 0 | Rate CappedFlooredOvernightIndexedCoupon::effectiveFloor() const { |
381 | 0 | if (floor_ == Null<Real>()) |
382 | 0 | return Null<Real>(); |
383 | 0 | if (dailyCapFloor_) { |
384 | 0 | if (underlying_->compoundSpreadDaily()) { |
385 | 0 | return floor_ - underlying_->spread(); |
386 | 0 | } else { |
387 | 0 | return floor_; |
388 | 0 | } |
389 | 0 | } else { |
390 | 0 | if (underlying_->compoundSpreadDaily()) { |
391 | 0 | return (floor_ - underlying_->effectiveSpread()); |
392 | 0 | } else { |
393 | 0 | return (floor_ - underlying_->effectiveSpread()) / gearing(); |
394 | 0 | } |
395 | 0 | } |
396 | 0 | } |
397 | | |
398 | 0 | Real CappedFlooredOvernightIndexedCoupon::effectiveCapletVolatility() const { |
399 | 0 | calculate(); |
400 | 0 | return effectiveCapletVolatility_; |
401 | 0 | } |
402 | | |
403 | 0 | Real CappedFlooredOvernightIndexedCoupon::effectiveFloorletVolatility() const { |
404 | 0 | calculate(); |
405 | 0 | return effectiveFloorletVolatility_; |
406 | 0 | } |
407 | | |
408 | 0 | void CappedFlooredOvernightIndexedCoupon::accept(AcyclicVisitor& v) { |
409 | 0 | auto* v1 = dynamic_cast<Visitor<CappedFlooredOvernightIndexedCoupon>*>(&v); |
410 | 0 | if (v1 != nullptr) |
411 | 0 | v1->visit(*this); |
412 | 0 | else |
413 | 0 | FloatingRateCoupon::accept(v); |
414 | 0 | } |
415 | | |
416 | 0 | void CappedFlooredOvernightIndexedCoupon::setPricer(const ext::shared_ptr<FloatingRateCouponPricer>& pricer){ |
417 | 0 | auto p = ext::dynamic_pointer_cast<OvernightIndexedCouponPricer>(pricer); |
418 | 0 | QL_REQUIRE(p, "The pricer is required to be an instance of OvernightIndexedCouponPricer"); |
419 | 0 | FloatingRateCoupon::setPricer(p); |
420 | 0 | } |
421 | | |
422 | | // OvernightLeg implementation |
423 | | |
424 | | OvernightLeg::OvernightLeg(Schedule schedule, const ext::shared_ptr<OvernightIndex>& i) |
425 | 0 | : schedule_(std::move(schedule)), overnightIndex_(i), paymentCalendar_(schedule_.calendar()) { |
426 | 0 | QL_REQUIRE(overnightIndex_, "no index provided"); |
427 | 0 | } |
428 | | |
429 | 0 | OvernightLeg& OvernightLeg::withNotionals(Real notional) { |
430 | 0 | notionals_ = vector<Real>(1, notional); |
431 | 0 | return *this; |
432 | 0 | } |
433 | | |
434 | 0 | OvernightLeg& OvernightLeg::withNotionals(const vector<Real>& notionals) { |
435 | 0 | notionals_ = notionals; |
436 | 0 | return *this; |
437 | 0 | } |
438 | | |
439 | 0 | OvernightLeg& OvernightLeg::withPaymentDayCounter(const DayCounter& dc) { |
440 | 0 | paymentDayCounter_ = dc; |
441 | 0 | return *this; |
442 | 0 | } |
443 | | |
444 | | OvernightLeg& |
445 | 0 | OvernightLeg::withPaymentAdjustment(BusinessDayConvention convention) { |
446 | 0 | paymentAdjustment_ = convention; |
447 | 0 | return *this; |
448 | 0 | } |
449 | | |
450 | 0 | OvernightLeg& OvernightLeg::withPaymentCalendar(const Calendar& cal) { |
451 | 0 | paymentCalendar_ = cal; |
452 | 0 | return *this; |
453 | 0 | } |
454 | | |
455 | 0 | OvernightLeg& OvernightLeg::withPaymentLag(Integer lag) { |
456 | 0 | paymentLag_ = lag; |
457 | 0 | return *this; |
458 | 0 | } |
459 | | |
460 | 0 | OvernightLeg& OvernightLeg::withGearings(Real gearing) { |
461 | 0 | gearings_ = vector<Real>(1,gearing); |
462 | 0 | return *this; |
463 | 0 | } |
464 | | |
465 | 0 | OvernightLeg& OvernightLeg::withGearings(const vector<Real>& gearings) { |
466 | 0 | gearings_ = gearings; |
467 | 0 | return *this; |
468 | 0 | } |
469 | | |
470 | 0 | OvernightLeg& OvernightLeg::withSpreads(Spread spread) { |
471 | 0 | spreads_ = vector<Spread>(1,spread); |
472 | 0 | return *this; |
473 | 0 | } |
474 | | |
475 | 0 | OvernightLeg& OvernightLeg::withSpreads(const vector<Spread>& spreads) { |
476 | 0 | spreads_ = spreads; |
477 | 0 | return *this; |
478 | 0 | } |
479 | | |
480 | 0 | OvernightLeg& OvernightLeg::withTelescopicValueDates(bool telescopicValueDates) { |
481 | 0 | telescopicValueDates_ = telescopicValueDates; |
482 | 0 | return *this; |
483 | 0 | } |
484 | | |
485 | 0 | OvernightLeg& OvernightLeg::withAveragingMethod(RateAveraging::Type averagingMethod) { |
486 | 0 | averagingMethod_ = averagingMethod; |
487 | 0 | return *this; |
488 | 0 | } |
489 | | |
490 | 0 | OvernightLeg& OvernightLeg::withLookbackDays(Natural lookbackDays) { |
491 | 0 | lookbackDays_ = lookbackDays; |
492 | 0 | return *this; |
493 | 0 | } |
494 | 0 | OvernightLeg& OvernightLeg::withLockoutDays(Natural lockoutDays) { |
495 | 0 | lockoutDays_ = lockoutDays; |
496 | 0 | return *this; |
497 | 0 | } |
498 | 0 | OvernightLeg& OvernightLeg::withObservationShift(bool applyObservationShift) { |
499 | 0 | applyObservationShift_ = applyObservationShift; |
500 | 0 | return *this; |
501 | 0 | } |
502 | | |
503 | 0 | OvernightLeg& OvernightLeg::compoundingSpreadDaily(bool compoundSpreadDaily) { |
504 | 0 | compoundSpreadDaily_ = compoundSpreadDaily; |
505 | 0 | return *this; |
506 | 0 | } |
507 | | |
508 | 0 | OvernightLeg& OvernightLeg::withCaps(Rate cap) { |
509 | 0 | caps_ = std::vector<Rate>(1, cap); |
510 | 0 | return *this; |
511 | 0 | } |
512 | | |
513 | 0 | OvernightLeg& OvernightLeg::withCaps(const std::vector<Rate>& caps) { |
514 | 0 | caps_ = caps; |
515 | 0 | return *this; |
516 | 0 | } |
517 | | |
518 | 0 | OvernightLeg& OvernightLeg::withFloors(Rate floor) { |
519 | 0 | floors_ = std::vector<Rate>(1, floor); |
520 | 0 | return *this; |
521 | 0 | } |
522 | | |
523 | 0 | OvernightLeg& OvernightLeg::withFloors(const std::vector<Rate>& floors) { |
524 | 0 | floors_ = floors; |
525 | 0 | return *this; |
526 | 0 | } |
527 | | |
528 | 0 | OvernightLeg& OvernightLeg::withNakedOption(const bool nakedOption) { |
529 | 0 | nakedOption_ = nakedOption; |
530 | 0 | return *this; |
531 | 0 | } |
532 | | |
533 | 0 | OvernightLeg& OvernightLeg::withDailyCapFloor(const bool dailyCapFloor) { |
534 | 0 | dailyCapFloor_ = dailyCapFloor; |
535 | 0 | return *this; |
536 | 0 | } |
537 | | |
538 | 0 | OvernightLeg& OvernightLeg::inArrears(const bool inArrears) { |
539 | 0 | inArrears_ = inArrears; |
540 | 0 | return *this; |
541 | 0 | } |
542 | | |
543 | 0 | OvernightLeg& OvernightLeg::withLastRecentPeriod(const ext::optional<Period>& lastRecentPeriod) { |
544 | 0 | lastRecentPeriod_ = lastRecentPeriod; |
545 | 0 | return *this; |
546 | 0 | } |
547 | | |
548 | 0 | OvernightLeg& OvernightLeg::withLastRecentPeriodCalendar(const Calendar& lastRecentPeriodCalendar) { |
549 | 0 | lastRecentPeriodCalendar_ = lastRecentPeriodCalendar; |
550 | 0 | return *this; |
551 | 0 | } |
552 | | |
553 | 0 | OvernightLeg& OvernightLeg::withPaymentDates(const std::vector<Date>& paymentDates) { |
554 | 0 | paymentDates_ = paymentDates; |
555 | 0 | return *this; |
556 | 0 | } |
557 | | |
558 | 0 | OvernightLeg& OvernightLeg::withCouponPricer(const ext::shared_ptr<OvernightIndexedCouponPricer>& couponPricer) { |
559 | 0 | couponPricer_ = couponPricer; |
560 | 0 | return *this; |
561 | 0 | } |
562 | | |
563 | 0 | OvernightLeg::operator Leg() const { |
564 | |
|
565 | 0 | QL_REQUIRE(!notionals_.empty(), "no notional given"); |
566 | | |
567 | 0 | if (couponPricer_ != nullptr) { |
568 | 0 | if (averagingMethod_ == RateAveraging::Compound) |
569 | 0 | QL_REQUIRE(ext::dynamic_pointer_cast<CompoundingOvernightIndexedCouponPricer>(couponPricer_), |
570 | 0 | "Wrong coupon pricer provided, provide a CompoundingOvernightIndexedCouponPricer"); |
571 | 0 | else |
572 | 0 | QL_REQUIRE(ext::dynamic_pointer_cast<ArithmeticAveragedOvernightIndexedCouponPricer>(couponPricer_), |
573 | 0 | "Wrong coupon pricer provided, provide a ArithmeticAveragedOvernightIndexedCouponPricer"); |
574 | 0 | } |
575 | | |
576 | 0 | Leg cashflows; |
577 | | |
578 | | // the following is not always correct |
579 | 0 | Calendar calendar = schedule_.calendar(); |
580 | 0 | Calendar paymentCalendar = paymentCalendar_; |
581 | |
|
582 | 0 | if (calendar.empty()) |
583 | 0 | calendar = paymentCalendar; |
584 | 0 | if (calendar.empty()) |
585 | 0 | calendar = WeekendsOnly(); |
586 | 0 | if (paymentCalendar.empty()) |
587 | 0 | paymentCalendar = calendar; |
588 | |
|
589 | 0 | Date refStart, start, refEnd, end; |
590 | 0 | Date paymentDate; |
591 | |
|
592 | 0 | Size n = schedule_.size()-1; |
593 | | |
594 | | // Initial consistency checks |
595 | 0 | if (!paymentDates_.empty()) { |
596 | 0 | QL_REQUIRE(paymentDates_.size() == n, "Expected the number of explicit payment dates (" |
597 | 0 | << paymentDates_.size() |
598 | 0 | << ") to equal the number of calculation periods (" |
599 | 0 | << n << ")"); |
600 | 0 | } |
601 | | |
602 | 0 | for (Size i=0; i<n; ++i) { |
603 | 0 | refStart = start = schedule_.date(i); |
604 | 0 | refEnd = end = schedule_.date(i+1); |
605 | | |
606 | | // If explicit payment dates provided, use them. |
607 | 0 | if (!paymentDates_.empty()) { |
608 | 0 | paymentDate = paymentDates_[i]; |
609 | 0 | } else { |
610 | 0 | paymentDate = paymentCalendar.advance(end, paymentLag_, Days, paymentAdjustment_); |
611 | 0 | } |
612 | | |
613 | | // determine refStart and refEnd |
614 | 0 | if (i == 0 && schedule_.hasIsRegular() && !schedule_.isRegular(i+1)) |
615 | 0 | refStart = calendar.adjust(end - schedule_.tenor(), |
616 | 0 | paymentAdjustment_); |
617 | 0 | if (i == n-1 && schedule_.hasIsRegular() && !schedule_.isRegular(i+1)) |
618 | 0 | refEnd = calendar.adjust(start + schedule_.tenor(), |
619 | 0 | paymentAdjustment_); |
620 | | |
621 | | // Determine the rate computation start and end date as |
622 | | // - the coupon start and end date, if in arrears, and |
623 | | // - the previous coupon start and end date, if in advance. |
624 | | // In addition, adjust the start date, if a last recent period is given. |
625 | |
|
626 | 0 | Date rateComputationStartDate, rateComputationEndDate; |
627 | 0 | if (inArrears_) { |
628 | | // in arrears fixing (i.e. the "classic" case) |
629 | 0 | rateComputationStartDate = start; |
630 | 0 | rateComputationEndDate = end; |
631 | 0 | } else { |
632 | | // handle in advance fixing |
633 | 0 | if (i > 0) { |
634 | | // if there is a previous period, we take that |
635 | 0 | rateComputationStartDate = schedule_.date(i - 1); |
636 | 0 | rateComputationEndDate = schedule_.date(i); |
637 | 0 | } else { |
638 | | // otherwise we construct the previous period |
639 | 0 | rateComputationEndDate = start; |
640 | 0 | if (schedule_.hasTenor() && schedule_.tenor() != 0 * Days) |
641 | 0 | rateComputationStartDate = calendar.adjust(start - schedule_.tenor(), Preceding); |
642 | 0 | else |
643 | 0 | rateComputationStartDate = calendar.adjust(start - (end - start), Preceding); |
644 | 0 | } |
645 | 0 | } |
646 | |
|
647 | 0 | if (lastRecentPeriod_) { |
648 | 0 | rateComputationStartDate = (lastRecentPeriodCalendar_.empty() ? calendar : lastRecentPeriodCalendar_) |
649 | 0 | .advance(rateComputationEndDate, -*lastRecentPeriod_); |
650 | 0 | } |
651 | | |
652 | | // build coupon |
653 | |
|
654 | 0 | if (close_enough(detail::get(gearings_, i, 1.0), 0.0)) { |
655 | | // fixed coupon |
656 | 0 | cashflows.push_back(QuantLib::ext::make_shared<FixedRateCoupon>( |
657 | 0 | paymentDate, detail::get(notionals_, i, 1.0), detail::effectiveFixedRate(spreads_, caps_, floors_, i), |
658 | 0 | paymentDayCounter_, start, end, refStart, refEnd)); |
659 | 0 | } else { |
660 | | // floating coupon |
661 | 0 | auto cpn = ext::make_shared<OvernightIndexedCoupon>( |
662 | 0 | paymentDate, detail::get(notionals_, i, 1.0), start, end, overnightIndex_, |
663 | 0 | detail::get(gearings_, i, 1.0), detail::get(spreads_, i, 0.0), refStart, refEnd, paymentDayCounter_, |
664 | 0 | telescopicValueDates_, averagingMethod_, lookbackDays_, lockoutDays_, applyObservationShift_, |
665 | 0 | compoundSpreadDaily_, rateComputationStartDate, rateComputationEndDate); |
666 | 0 | if (couponPricer_) { |
667 | 0 | cpn->setPricer(couponPricer_); |
668 | 0 | } |
669 | 0 | Real cap = detail::get(caps_, i, Null<Real>()); |
670 | 0 | Real floor = detail::get(floors_, i, Null<Real>()); |
671 | 0 | if (cap == Null<Real>() && floor == Null<Real>()) { |
672 | 0 | cashflows.push_back(cpn); |
673 | 0 | } else { |
674 | 0 | auto cfCpn = ext::make_shared<CappedFlooredOvernightIndexedCoupon>(cpn, cap, floor, nakedOption_, |
675 | 0 | dailyCapFloor_); |
676 | 0 | if (couponPricer_) { |
677 | 0 | cfCpn->setPricer(couponPricer_); |
678 | 0 | } |
679 | 0 | cashflows.push_back(cfCpn); |
680 | 0 | } |
681 | 0 | } |
682 | 0 | } |
683 | 0 | return cashflows; |
684 | 0 | } |
685 | | |
686 | | } |