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