/src/quantlib/ql/cashflows/overnightindexedcouponpricer.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) 2016 Stefano Fondi |
8 | | Copyright (C) 2017 Joseph Jeisman |
9 | | Copyright (C) 2017 Fabrice Lecuyer |
10 | | |
11 | | This file is part of QuantLib, a free-software/open-source library |
12 | | for financial quantitative analysts and developers - http://quantlib.org/ |
13 | | |
14 | | QuantLib is free software: you can redistribute it and/or modify it |
15 | | under the terms of the QuantLib license. You should have received a |
16 | | copy of the license along with this program; if not, please email |
17 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
18 | | <https://www.quantlib.org/license.shtml>. |
19 | | |
20 | | This program is distributed in the hope that it will be useful, but WITHOUT |
21 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
22 | | FOR A PARTICULAR PURPOSE. See the license for more details. |
23 | | */ |
24 | | |
25 | | #include <ql/cashflows/overnightindexedcouponpricer.hpp> |
26 | | |
27 | | namespace QuantLib { |
28 | | |
29 | | namespace { |
30 | | |
31 | | Size determineNumberOfFixings(const std::vector<Date>& interestDates, |
32 | | const Date& date, |
33 | 0 | bool applyObservationShift) { |
34 | 0 | Size n = std::lower_bound(interestDates.begin(), interestDates.end(), date) - |
35 | 0 | interestDates.begin(); |
36 | | // When using the observation shift, it may happen that |
37 | | // that the end of accrual period will fall later than the last |
38 | | // interest date. In which case, n will be equal to the number of |
39 | | // interest dates, while we know that the number of fixing dates is |
40 | | // always one less than the number of interest dates. |
41 | 0 | return n == interestDates.size() && applyObservationShift ? n - 1 : n; |
42 | 0 | } |
43 | | } |
44 | | |
45 | 0 | void CompoundingOvernightIndexedCouponPricer::initialize(const FloatingRateCoupon& coupon) { |
46 | 0 | coupon_ = dynamic_cast<const OvernightIndexedCoupon*>(&coupon); |
47 | 0 | QL_ENSURE(coupon_, "wrong coupon type"); |
48 | 0 | } |
49 | | |
50 | 0 | Rate CompoundingOvernightIndexedCouponPricer::swapletRate() const { |
51 | 0 | return averageRate(coupon_->accrualEndDate()); |
52 | 0 | } |
53 | | |
54 | 0 | Rate CompoundingOvernightIndexedCouponPricer::averageRate(const Date& date) const { |
55 | 0 | const Date today = Settings::instance().evaluationDate(); |
56 | |
|
57 | 0 | const ext::shared_ptr<OvernightIndex> index = |
58 | 0 | ext::dynamic_pointer_cast<OvernightIndex>(coupon_->index()); |
59 | 0 | const auto& pastFixings = index->timeSeries(); |
60 | |
|
61 | 0 | const auto& fixingDates = coupon_->fixingDates(); |
62 | 0 | const auto& valueDates = coupon_->valueDates(); |
63 | 0 | const auto& interestDates = coupon_->interestDates(); |
64 | 0 | const auto& dt = coupon_->dt(); |
65 | 0 | const bool applyObservationShift = coupon_->applyObservationShift(); |
66 | |
|
67 | 0 | Size i = 0; |
68 | 0 | const Size n = determineNumberOfFixings(interestDates, date, applyObservationShift); |
69 | |
|
70 | 0 | Real compoundFactor = 1.0; |
71 | | |
72 | | // already fixed part |
73 | 0 | while (i < n && fixingDates[i] < today) { |
74 | | // rate must have been fixed |
75 | 0 | const Rate fixing = pastFixings[fixingDates[i]]; |
76 | 0 | QL_REQUIRE(fixing != Null<Real>(), |
77 | 0 | "Missing " << index->name() << " fixing for " << fixingDates[i]); |
78 | 0 | Time span = (date >= interestDates[i + 1] ? |
79 | 0 | dt[i] : |
80 | 0 | index->dayCounter().yearFraction(interestDates[i], date)); |
81 | 0 | compoundFactor *= (1.0 + fixing * span); |
82 | 0 | ++i; |
83 | 0 | } |
84 | | |
85 | | // today is a border case |
86 | 0 | if (i < n && fixingDates[i] == today) { |
87 | | // might have been fixed |
88 | 0 | try { |
89 | 0 | Rate fixing = pastFixings[fixingDates[i]]; |
90 | 0 | if (fixing != Null<Real>()) { |
91 | 0 | Time span = (date >= interestDates[i + 1] ? |
92 | 0 | dt[i] : |
93 | 0 | index->dayCounter().yearFraction(interestDates[i], date)); |
94 | 0 | compoundFactor *= (1.0 + fixing * span); |
95 | 0 | ++i; |
96 | 0 | } else { |
97 | 0 | ; // fall through and forecast |
98 | 0 | } |
99 | 0 | } catch (Error&) { |
100 | 0 | ; // fall through and forecast |
101 | 0 | } |
102 | 0 | } |
103 | | |
104 | | // forward part using telescopic property in order |
105 | | // to avoid the evaluation of multiple forward fixings |
106 | | // where possible. |
107 | 0 | if (i < n) { |
108 | 0 | const Handle<YieldTermStructure> curve = index->forwardingTermStructure(); |
109 | 0 | QL_REQUIRE(!curve.empty(), |
110 | 0 | "null term structure set to this instance of " << index->name()); |
111 | | |
112 | 0 | const auto effectiveRate = [&index, &fixingDates, &date, &interestDates, |
113 | 0 | &dt](Size position) { |
114 | 0 | Rate fixing = index->fixing(fixingDates[position]); |
115 | 0 | Time span = (date >= interestDates[position + 1] ? |
116 | 0 | dt[position] : |
117 | 0 | index->dayCounter().yearFraction(interestDates[position], date)); |
118 | 0 | return span * fixing; |
119 | 0 | }; |
120 | |
|
121 | 0 | if (!coupon_->canApplyTelescopicFormula()) { |
122 | | // With lookback applied, the telescopic formula cannot be used, |
123 | | // we need to project each fixing in the coupon. |
124 | | // Only in one particular case when observation shift is used and |
125 | | // no intrinsic index fixing delay is applied, the telescopic formula |
126 | | // holds, because regardless of the fixing delay in the coupon, |
127 | | // in such configuration value dates will be equal to interest dates. |
128 | | // A potential lockout, which may occur in tandem with a lookback |
129 | | // setting, will be handled automatically based on fixing dates. |
130 | | // Same applies to a case when accrual calculation date does or |
131 | | // does not occur on an interest date. |
132 | 0 | while (i < n) { |
133 | 0 | compoundFactor *= (1.0 + effectiveRate(i)); |
134 | 0 | ++i; |
135 | 0 | } |
136 | 0 | } else { |
137 | | // No lookback, we can partially apply the telescopic formula. |
138 | | // But we need to make a correction for a potential lockout. |
139 | 0 | const Size nLockout = n - coupon_->lockoutDays(); |
140 | 0 | const bool isLockoutApplied = coupon_->lockoutDays() > 0; |
141 | | |
142 | | // Lockout could already start at or before i. |
143 | | // In such case the ratio of discount factors will be equal to 1. |
144 | 0 | const DiscountFactor startDiscount = |
145 | 0 | curve->discount(valueDates[std::min<Size>(nLockout, i)]); |
146 | 0 | if (interestDates[n] == date || isLockoutApplied) { |
147 | | // telescopic formula up to potential lockout dates. |
148 | 0 | const DiscountFactor endDiscount = |
149 | 0 | curve->discount(valueDates[std::min<Size>(nLockout, n)]); |
150 | 0 | compoundFactor *= startDiscount / endDiscount; |
151 | | // For the lockout periods the telescopic formula does not apply. |
152 | | // The value dates (at which the projection is calculated) correspond |
153 | | // to the locked-out fixing, while the interest dates (at which the |
154 | | // interest over that fixing is accrued) are not fixed at lockout, |
155 | | // hence they do not cancel out. |
156 | 0 | i = std::max(nLockout, i); |
157 | | |
158 | | // With no lockout, the loop is skipped because i = n. |
159 | 0 | while (i < n) { |
160 | 0 | compoundFactor *= (1.0 + effectiveRate(i)); |
161 | 0 | ++i; |
162 | 0 | } |
163 | 0 | } else { |
164 | | // No lockout and date is different than last interest date. |
165 | | // The last fixing is not used for its full period (the date is between |
166 | | // its start and end date). We can use the telescopic formula until the |
167 | | // previous date, then we'll add the missing bit. |
168 | 0 | const DiscountFactor endDiscount = curve->discount(valueDates[n - 1]); |
169 | 0 | compoundFactor *= startDiscount / endDiscount; |
170 | 0 | compoundFactor *= (1.0 + effectiveRate(n - 1)); |
171 | 0 | } |
172 | 0 | } |
173 | 0 | } |
174 | | |
175 | 0 | const Rate rate = (compoundFactor - 1.0) / coupon_->accruedPeriod(date); |
176 | 0 | return coupon_->gearing() * rate + coupon_->spread(); |
177 | 0 | } |
178 | | |
179 | | void |
180 | 0 | ArithmeticAveragedOvernightIndexedCouponPricer::initialize(const FloatingRateCoupon& coupon) { |
181 | 0 | coupon_ = dynamic_cast<const OvernightIndexedCoupon*>(&coupon); |
182 | 0 | QL_ENSURE(coupon_, "wrong coupon type"); |
183 | 0 | } |
184 | | |
185 | 0 | Rate ArithmeticAveragedOvernightIndexedCouponPricer::swapletRate() const { |
186 | |
|
187 | 0 | ext::shared_ptr<OvernightIndex> index = |
188 | 0 | ext::dynamic_pointer_cast<OvernightIndex>(coupon_->index()); |
189 | |
|
190 | 0 | const auto& fixingDates = coupon_->fixingDates(); |
191 | 0 | const auto& dt = coupon_->dt(); |
192 | |
|
193 | 0 | Size n = dt.size(), i = 0; |
194 | |
|
195 | 0 | Real accumulatedRate = 0.0; |
196 | |
|
197 | 0 | const auto& pastFixings = index->timeSeries(); |
198 | | |
199 | | // already fixed part |
200 | 0 | Date today = Settings::instance().evaluationDate(); |
201 | 0 | while (i < n && fixingDates[i] < today) { |
202 | | // rate must have been fixed |
203 | 0 | Rate pastFixing = pastFixings[fixingDates[i]]; |
204 | 0 | QL_REQUIRE(pastFixing != Null<Real>(), |
205 | 0 | "Missing " << index->name() << " fixing for " << fixingDates[i]); |
206 | 0 | accumulatedRate += pastFixing * dt[i]; |
207 | 0 | ++i; |
208 | 0 | } |
209 | | |
210 | | // today is a border case |
211 | 0 | if (i < n && fixingDates[i] == today) { |
212 | | // might have been fixed |
213 | 0 | try { |
214 | 0 | Rate pastFixing = pastFixings[fixingDates[i]]; |
215 | 0 | if (pastFixing != Null<Real>()) { |
216 | 0 | accumulatedRate += pastFixing * dt[i]; |
217 | 0 | ++i; |
218 | 0 | } else { |
219 | 0 | ; // fall through and forecast |
220 | 0 | } |
221 | 0 | } catch (Error&) { |
222 | 0 | ; // fall through and forecast |
223 | 0 | } |
224 | 0 | } |
225 | | |
226 | | /* forward part using telescopic property in order |
227 | | to avoid the evaluation of multiple forward fixings |
228 | | (approximation proposed by Katsumi Takada)*/ |
229 | 0 | if (byApprox_ && i < n) { |
230 | 0 | Handle<YieldTermStructure> curve = index->forwardingTermStructure(); |
231 | 0 | QL_REQUIRE(!curve.empty(), |
232 | 0 | "null term structure set to this instance of " << index->name()); |
233 | | |
234 | 0 | const auto& dates = coupon_->valueDates(); |
235 | 0 | DiscountFactor startDiscount = curve->discount(dates[i]); |
236 | 0 | DiscountFactor endDiscount = curve->discount(dates[n]); |
237 | |
|
238 | 0 | accumulatedRate += |
239 | 0 | log(startDiscount / endDiscount) - |
240 | 0 | convAdj1(curve->timeFromReference(dates[i]), curve->timeFromReference(dates[n])) - |
241 | 0 | convAdj2(curve->timeFromReference(dates[i]), curve->timeFromReference(dates[n])); |
242 | 0 | } |
243 | | // otherwise |
244 | 0 | else if (i < n) { |
245 | 0 | Handle<YieldTermStructure> curve = index->forwardingTermStructure(); |
246 | 0 | QL_REQUIRE(!curve.empty(), |
247 | 0 | "null term structure set to this instance of " << index->name()); |
248 | | |
249 | 0 | const auto& dates = coupon_->valueDates(); |
250 | 0 | Time te = curve->timeFromReference(dates[n]); |
251 | 0 | while (i < n) { |
252 | | // forcast fixing |
253 | 0 | Rate forecastFixing = index->fixing(fixingDates[i]); |
254 | 0 | Time ti1 = curve->timeFromReference(dates[i]); |
255 | 0 | Time ti2 = curve->timeFromReference(dates[i + 1]); |
256 | | /*convexity adjustment due to payment dalay of each |
257 | | overnight fixing, supposing an Hull-White short rate model*/ |
258 | 0 | Real convAdj = exp( |
259 | 0 | 0.5 * pow(vol_, 2.0) / pow(mrs_, 3.0) * (exp(2 * mrs_ * ti1) - 1) * |
260 | 0 | (exp(-mrs_ * ti2) - exp(-mrs_ * te)) * (exp(-mrs_ * ti2) - exp(-mrs_ * ti1))); |
261 | 0 | accumulatedRate += convAdj * (1 + forecastFixing * dt[i]) - 1; |
262 | 0 | ++i; |
263 | 0 | } |
264 | 0 | } |
265 | | |
266 | 0 | Rate rate = accumulatedRate / coupon_->accrualPeriod(); |
267 | 0 | return coupon_->gearing() * rate + coupon_->spread(); |
268 | 0 | } |
269 | | |
270 | 0 | Real ArithmeticAveragedOvernightIndexedCouponPricer::convAdj1(Time ts, Time te) const { |
271 | 0 | return vol_ * vol_ / (4.0 * pow(mrs_, 3.0)) * (1.0 - exp(-2.0 * mrs_ * ts)) * |
272 | 0 | pow((1.0 - exp(-mrs_ * (te - ts))), 2.0); |
273 | 0 | } |
274 | | |
275 | 0 | Real ArithmeticAveragedOvernightIndexedCouponPricer::convAdj2(Time ts, Time te) const { |
276 | 0 | return vol_ * vol_ / (2.0 * pow(mrs_, 2.0)) * |
277 | 0 | ((te - ts) - pow(1.0 - exp(-mrs_ * (te - ts)), 2.0) / mrs_ - |
278 | 0 | (1.0 - exp(-2.0 * mrs_ * (te - ts))) / (2.0 * mrs_)); |
279 | 0 | } |
280 | | } |