/src/quantlib/ql/cashflows/blackovernightindexedcouponpricer.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2020 Quaternion Risk Management Ltd |
5 | | |
6 | | This file is part of QuantLib, a free-software/open-source library |
7 | | for financial quantitative analysts and developers - http://quantlib.org/ |
8 | | |
9 | | QuantLib is free software: you can redistribute it and/or modify it |
10 | | under the terms of the QuantLib license. You should have received a |
11 | | copy of the license along with this program; if not, please email |
12 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
13 | | <https://www.quantlib.org/license.shtml>. |
14 | | |
15 | | |
16 | | This program is distributed in the hope that it will be useful, but |
17 | | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
18 | | or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details. |
19 | | */ |
20 | | #include <ql/cashflows/blackovernightindexedcouponpricer.hpp> |
21 | | |
22 | | #include <ql/pricingengines/blackformula.hpp> |
23 | | #include <ql/termstructures/volatility/optionlet/optionletvolatilitystructure.hpp> |
24 | | #include <utility> |
25 | | |
26 | | namespace QuantLib { |
27 | | |
28 | | BlackCompoundingOvernightIndexedCouponPricer::BlackCompoundingOvernightIndexedCouponPricer( |
29 | | Handle<OptionletVolatilityStructure> v, |
30 | | const bool effectiveVolatilityInput) |
31 | 0 | : CompoundingOvernightIndexedCouponPricer(std::move(v), effectiveVolatilityInput) {}Unexecuted instantiation: QuantLib::BlackCompoundingOvernightIndexedCouponPricer::BlackCompoundingOvernightIndexedCouponPricer(QuantLib::Handle<QuantLib::OptionletVolatilityStructure>, bool) Unexecuted instantiation: QuantLib::BlackCompoundingOvernightIndexedCouponPricer::BlackCompoundingOvernightIndexedCouponPricer(QuantLib::Handle<QuantLib::OptionletVolatilityStructure>, bool) |
32 | | |
33 | 0 | void BlackCompoundingOvernightIndexedCouponPricer::initialize(const FloatingRateCoupon& coupon) { |
34 | 0 | OvernightIndexedCouponPricer::initialize(coupon); |
35 | |
|
36 | 0 | gearing_ = coupon.gearing(); |
37 | 0 | std::tie(swapletRate_, effectiveSpread_, effectiveIndexFixing_) = CompoundingOvernightIndexedCouponPricer::compute(coupon_->accrualEndDate()); |
38 | 0 | effectiveCapletVolatility_ = effectiveFloorletVolatility_ = Null<Real>(); |
39 | 0 | } |
40 | | |
41 | 0 | Real BlackCompoundingOvernightIndexedCouponPricer::optionletRateGlobal(Option::Type optionType, Real effStrike) const { |
42 | 0 | Date lastRelevantFixingDate = coupon_->fixingDate(); |
43 | 0 | if (lastRelevantFixingDate <= Settings::instance().evaluationDate()) { |
44 | | // the amount is determined |
45 | 0 | Real a, b; |
46 | 0 | if (optionType == Option::Call) { |
47 | 0 | a = effectiveIndexFixing_; |
48 | 0 | b = effStrike; |
49 | 0 | } else { |
50 | 0 | a = effStrike; |
51 | 0 | b = effectiveIndexFixing_; |
52 | 0 | } |
53 | 0 | return gearing_ * std::max(a - b, 0.0); |
54 | 0 | } else { |
55 | | // not yet determined, use Black model |
56 | 0 | QL_REQUIRE(!capletVolatility().empty(), "BlackCompoundingOvernightIndexedCouponPricer: missing optionlet volatility"); |
57 | 0 | std::vector<Date> fixingDates = coupon_->fixingDates(); |
58 | 0 | QL_REQUIRE(!fixingDates.empty(), "BlackCompoundingOvernightIndexedCouponPricer: empty fixing dates"); |
59 | 0 | bool shiftedLn = capletVolatility()->volatilityType() == ShiftedLognormal; |
60 | 0 | Real shift = capletVolatility()->displacement(); |
61 | 0 | Real stdDev; |
62 | 0 | Real effectiveTime = capletVolatility()->timeFromReference(fixingDates.back()); |
63 | 0 | if (effectiveVolatilityInput()) { |
64 | | // vol input is effective, i.e. we use a plain black model |
65 | 0 | stdDev = capletVolatility()->volatility(fixingDates.back(), effStrike) * std::sqrt(effectiveTime); |
66 | 0 | } else { |
67 | | // vol input is not effective: |
68 | | // for the standard deviation see Lyashenko, Mercurio, Looking forward to backward looking rates, |
69 | | // section 6.3. the idea is to dampen the average volatility sigma between the fixing start and fixing end |
70 | | // date by a linear function going from (fixing start, 1) to (fixing end, 0) |
71 | 0 | Real fixingStartTime = capletVolatility()->timeFromReference(fixingDates.front()); |
72 | 0 | Real fixingEndTime = capletVolatility()->timeFromReference(fixingDates.back()); |
73 | 0 | Real sigma = capletVolatility()->volatility( |
74 | 0 | std::max(fixingDates.front(), capletVolatility()->referenceDate() + 1), effStrike); |
75 | 0 | Real T = std::max(fixingStartTime, 0.0); |
76 | 0 | if (!close_enough(fixingEndTime, T)) |
77 | 0 | T += std::pow(fixingEndTime - T, 3.0) / std::pow(fixingEndTime - fixingStartTime, 2.0) / 3.0; |
78 | 0 | stdDev = sigma * std::sqrt(T); |
79 | 0 | } |
80 | 0 | if (optionType == Option::Type::Call) |
81 | 0 | effectiveCapletVolatility_ = stdDev / std::sqrt(effectiveTime); |
82 | 0 | else |
83 | 0 | effectiveFloorletVolatility_ = stdDev / std::sqrt(effectiveTime); |
84 | 0 | Real fixing = shiftedLn ? blackFormula(optionType, effStrike, effectiveIndexFixing_, stdDev, 1.0, shift) |
85 | 0 | : bachelierBlackFormula(optionType, effStrike, effectiveIndexFixing_, stdDev, 1.0); |
86 | 0 | return gearing_ * fixing; |
87 | 0 | } |
88 | 0 | } |
89 | | |
90 | | namespace { |
91 | 0 | Real cappedFlooredRate(Real r, Option::Type optionType, Real k) { |
92 | 0 | if (optionType == Option::Call) { |
93 | 0 | return std::min(r, k); |
94 | 0 | } else { |
95 | 0 | return std::max(r, k); |
96 | 0 | } |
97 | 0 | } |
98 | | } // namespace |
99 | | |
100 | 0 | Real BlackCompoundingOvernightIndexedCouponPricer::optionletRateLocal(Option::Type optionType, Real effStrike) const { |
101 | |
|
102 | 0 | QL_REQUIRE(!effectiveVolatilityInput(), |
103 | 0 | "BlackAverageONIndexedCouponPricer::optionletRateLocal() does not support effective volatility input."); |
104 | | |
105 | | // We compute a rate and a rawRate such that |
106 | | // rate * tau * nominal is the amount of the coupon with daily capped / floored rates |
107 | | // rawRate * tau * nominal is the amount of the coupon without capping / flooring the rate |
108 | | // We will then return the difference between rate and rawRate (with the correct sign, see below) |
109 | | // as the option component of the coupon. |
110 | | |
111 | | // See CappedFlooredOvernightIndexedCoupon::effectiveCap(), Floor() for what is passed in as effStrike. |
112 | | // From this we back out the absolute strike at which the |
113 | | // - daily rate + spread (spread included) or the |
114 | | // - daily rate (spread excluded) |
115 | | // is capped / floored. |
116 | | |
117 | 0 | Real absStrike = coupon_->compoundSpreadDaily() ? effStrike + coupon_->spread() : effStrike; |
118 | | |
119 | | // This following code is inevitably quite similar to the plain ON coupon pricer code, possibly we can refactor |
120 | | // this, but as a first step it seems safer to add the full modified code explicitly here and leave the original |
121 | | // code alone. |
122 | |
|
123 | 0 | ext::shared_ptr<OvernightIndex> index = ext::dynamic_pointer_cast<OvernightIndex>(coupon_->index()); |
124 | |
|
125 | 0 | const std::vector<Date>& fixingDates = coupon_->fixingDates(); |
126 | 0 | const std::vector<Time>& dt = coupon_->dt(); |
127 | |
|
128 | 0 | Size n = dt.size(); |
129 | 0 | Size i = 0; |
130 | 0 | QL_REQUIRE(coupon_->lockoutDays() < n, |
131 | 0 | "rate cutoff (" << coupon_->lockoutDays() |
132 | 0 | << ") must be less than number of fixings in period (" << n << ")"); |
133 | 0 | Size nCutoff = n - coupon_->lockoutDays(); |
134 | |
|
135 | 0 | Real compoundFactor = 1.0, compoundFactorRaw = 1.0; |
136 | | |
137 | | // already fixed part |
138 | 0 | Date today = Settings::instance().evaluationDate(); |
139 | 0 | while (i < n && fixingDates[std::min(i, nCutoff)] < today) { |
140 | | // rate must have been fixed |
141 | 0 | Rate pastFixing = index->pastFixing(fixingDates[std::min(i, nCutoff)]); |
142 | 0 | QL_REQUIRE(pastFixing != Null<Real>(), |
143 | 0 | "Missing " << index->name() << " fixing for " << fixingDates[std::min(i, nCutoff)]); |
144 | 0 | if (coupon_->compoundSpreadDaily()) { |
145 | 0 | pastFixing += coupon_->spread(); |
146 | 0 | } |
147 | 0 | compoundFactor *= 1.0 + cappedFlooredRate(pastFixing, optionType, absStrike) * dt[i]; |
148 | 0 | compoundFactorRaw *= 1.0 + pastFixing * dt[i]; |
149 | 0 | ++i; |
150 | 0 | } |
151 | | |
152 | | // today is a border case |
153 | 0 | if (i < n && fixingDates[std::min(i, nCutoff)] == today) { |
154 | | // might have been fixed |
155 | 0 | try { |
156 | 0 | Rate pastFixing = index->pastFixing(today); |
157 | 0 | if (pastFixing != Null<Real>()) { |
158 | 0 | if (coupon_->compoundSpreadDaily()) { |
159 | 0 | pastFixing += coupon_->spread(); |
160 | 0 | } |
161 | 0 | compoundFactor *= 1.0 + cappedFlooredRate(pastFixing, optionType, absStrike) * dt[i]; |
162 | 0 | compoundFactorRaw *= 1.0 + pastFixing * dt[i]; |
163 | 0 | ++i; |
164 | 0 | } else { |
165 | 0 | ; // fall through and forecast |
166 | 0 | } |
167 | 0 | } catch (Error&) { |
168 | 0 | ; // fall through and forecast |
169 | 0 | } |
170 | 0 | } |
171 | | |
172 | | // forward part, approximation by pricing a cap / floor in the middle of the future period |
173 | 0 | const std::vector<Date>& dates = coupon_->valueDates(); |
174 | 0 | if (i < n) { |
175 | 0 | Handle<YieldTermStructure> curve = index->forwardingTermStructure(); |
176 | 0 | QL_REQUIRE(!curve.empty(), "null term structure set to this instance of " << index->name()); |
177 | | |
178 | 0 | DiscountFactor startDiscount = curve->discount(dates[i]); |
179 | 0 | DiscountFactor endDiscount = curve->discount(dates[std::max(nCutoff, i)]); |
180 | | |
181 | | // handle the rate cutoff period (if there is any, i.e. if nCutoff < n) |
182 | 0 | if (nCutoff < n) { |
183 | | // forward discount factor for one calendar day on the cutoff date |
184 | 0 | DiscountFactor discountCutoffDate = curve->discount(dates[nCutoff] + 1) / curve->discount(dates[nCutoff]); |
185 | | // keep the above forward discount factor constant during the cutoff period |
186 | 0 | endDiscount *= std::pow(discountCutoffDate, dates[n] - dates[nCutoff]); |
187 | 0 | } |
188 | | |
189 | | // estimate the average daily rate over the future period (approximate the continuously compounded rate) |
190 | 0 | Real tau = coupon_->dayCounter().yearFraction(dates[i], dates.back()); |
191 | 0 | Real averageRate = -std::log(endDiscount / startDiscount) / tau; |
192 | | |
193 | | // compute the value of a cap or floor with fixing in the middle of the future period |
194 | | // (but accounting for the rate cutoff here) |
195 | 0 | Time midPoint = |
196 | 0 | (capletVolatility()->timeFromReference(dates[i]) + capletVolatility()->timeFromReference(dates[nCutoff])) / |
197 | 0 | 2.0; |
198 | 0 | Real stdDev = capletVolatility()->volatility(midPoint, effStrike) * std::sqrt(midPoint); |
199 | 0 | Real shift = capletVolatility()->displacement(); |
200 | 0 | bool shiftedLn = capletVolatility()->volatilityType() == ShiftedLognormal; |
201 | 0 | Rate cfValue = shiftedLn ? blackFormula(optionType, effStrike, averageRate, stdDev, 1.0, shift) |
202 | 0 | : bachelierBlackFormula(optionType, effStrike, averageRate, stdDev, 1.0); |
203 | 0 | Real effectiveTime = capletVolatility()->timeFromReference(fixingDates.back()); |
204 | 0 | if (optionType == Option::Type::Call) |
205 | 0 | effectiveCapletVolatility_ = stdDev / std::sqrt(effectiveTime); |
206 | 0 | else |
207 | 0 | effectiveFloorletVolatility_ = stdDev / std::sqrt(effectiveTime); |
208 | | |
209 | | // add spread to average rate |
210 | 0 | if (coupon_->compoundSpreadDaily()) { |
211 | 0 | averageRate += coupon_->spread(); |
212 | 0 | } |
213 | | |
214 | | // incorporate cap/floor into average rate |
215 | 0 | Real averageRateRaw = averageRate; |
216 | 0 | averageRate += optionType == Option::Call ? (-cfValue) : cfValue; |
217 | | |
218 | | // now assume the averageRate is the effective rate over the future period and update the compoundFactor |
219 | | // this is an approximation, see "Ester / Daily Spread Curve Setup in ORE": set tau to avg value |
220 | 0 | Real dailyTau = |
221 | 0 | coupon_->dayCounter().yearFraction(dates[i], dates.back()) / (dates.back() - dates[i]); |
222 | | // now use formula (4) from the paper |
223 | 0 | compoundFactor *= std::pow(1.0 + dailyTau * averageRate, static_cast<int>(dates.back() - dates[i])); |
224 | 0 | compoundFactorRaw *= std::pow(1.0 + dailyTau * averageRateRaw, static_cast<int>(dates.back() - dates[i])); |
225 | 0 | } |
226 | | |
227 | 0 | Real tau = coupon_->lockoutDays() == 0 |
228 | 0 | ? coupon_->accrualPeriod() |
229 | 0 | : coupon_->dayCounter().yearFraction(dates.front(), dates.back()); |
230 | 0 | Rate rate = (compoundFactor - 1.0) / tau; |
231 | 0 | Rate rawRate = (compoundFactorRaw - 1.0) / tau; |
232 | |
|
233 | 0 | rate *= coupon_->gearing(); |
234 | 0 | rawRate *= coupon_->gearing(); |
235 | |
|
236 | 0 | if (!coupon_->compoundSpreadDaily()) { |
237 | 0 | rate += coupon_->spread(); |
238 | 0 | rawRate += coupon_->spread(); |
239 | 0 | } |
240 | | |
241 | | // return optionletRate := r - rawRate, i.e. the option component only |
242 | | // (see CappedFlooredOvernightIndexedCoupon::rate() for the signs of the capletRate / flooredRate) |
243 | |
|
244 | 0 | return (optionType == Option::Call ? -1.0 : 1.0) * (rate - rawRate); |
245 | 0 | } |
246 | | |
247 | 0 | Rate BlackCompoundingOvernightIndexedCouponPricer::swapletRate() const { return swapletRate_; } |
248 | | |
249 | 0 | Rate BlackCompoundingOvernightIndexedCouponPricer::capletRate(Rate effectiveCap) const { |
250 | 0 | return capletRate(effectiveCap, false); |
251 | 0 | } |
252 | | |
253 | 0 | Rate BlackCompoundingOvernightIndexedCouponPricer::floorletRate(Rate effectiveFloor) const { |
254 | 0 | return floorletRate(effectiveFloor, false); |
255 | 0 | } |
256 | | |
257 | 0 | Rate BlackCompoundingOvernightIndexedCouponPricer::capletRate(Rate effectiveCap, bool dailyCapFloor) const { |
258 | 0 | return dailyCapFloor ? optionletRateLocal(Option::Call, effectiveCap) |
259 | 0 | : optionletRateGlobal(Option::Call, effectiveCap); |
260 | 0 | } |
261 | | |
262 | 0 | Rate BlackCompoundingOvernightIndexedCouponPricer::floorletRate(Rate effectiveFloor, bool dailyCapFloor) const { |
263 | 0 | return dailyCapFloor ? optionletRateLocal(Option::Put, effectiveFloor) |
264 | 0 | : optionletRateGlobal(Option::Put, effectiveFloor); |
265 | 0 | } |
266 | | |
267 | 0 | Real BlackCompoundingOvernightIndexedCouponPricer::swapletPrice() const { |
268 | 0 | QL_FAIL("BlackCompoundingOvernightIndexedCouponPricer::swapletPrice() not provided"); |
269 | 0 | } |
270 | 0 | Real BlackCompoundingOvernightIndexedCouponPricer::capletPrice(Rate effectiveCap) const { |
271 | 0 | QL_FAIL("BlackCompoundingOvernightIndexedCouponPricer::capletPrice() not provided"); |
272 | 0 | } |
273 | 0 | Real BlackCompoundingOvernightIndexedCouponPricer::floorletPrice(Rate effectiveFloor) const { |
274 | 0 | QL_FAIL("BlackCompoundingOvernightIndexedCouponPricer::floorletPrice() not provided"); |
275 | 0 | } |
276 | | |
277 | | BlackAveragingOvernightIndexedCouponPricer::BlackAveragingOvernightIndexedCouponPricer( |
278 | | Handle<OptionletVolatilityStructure> v, |
279 | | const bool effectiveVolatilityInput) |
280 | 0 | : ArithmeticAveragedOvernightIndexedCouponPricer(0.03, 0.0, false, std::move(v), effectiveVolatilityInput) {}Unexecuted instantiation: QuantLib::BlackAveragingOvernightIndexedCouponPricer::BlackAveragingOvernightIndexedCouponPricer(QuantLib::Handle<QuantLib::OptionletVolatilityStructure>, bool) Unexecuted instantiation: QuantLib::BlackAveragingOvernightIndexedCouponPricer::BlackAveragingOvernightIndexedCouponPricer(QuantLib::Handle<QuantLib::OptionletVolatilityStructure>, bool) |
281 | | |
282 | 0 | void BlackAveragingOvernightIndexedCouponPricer::initialize(const FloatingRateCoupon& coupon) { |
283 | 0 | OvernightIndexedCouponPricer::initialize(coupon); |
284 | |
|
285 | 0 | if (coupon_->averagingMethod() == RateAveraging::Compound) |
286 | 0 | QL_FAIL("Averaging method required to be simple for BlackAveragingOvernightIndexedCouponPricer"); |
287 | | |
288 | 0 | gearing_ = coupon.gearing(); |
289 | 0 | swapletRate_ = ArithmeticAveragedOvernightIndexedCouponPricer::swapletRate(); |
290 | 0 | forwardRate_ = (swapletRate_ - coupon_->spread()) / coupon_->gearing(); |
291 | 0 | effectiveCapletVolatility_ = effectiveFloorletVolatility_ = Null<Real>(); |
292 | 0 | } |
293 | | |
294 | 0 | Real BlackAveragingOvernightIndexedCouponPricer::optionletRateGlobal(Option::Type optionType, Real effStrike) const { |
295 | 0 | Date lastRelevantFixingDate = coupon_->fixingDate(); |
296 | 0 | if (lastRelevantFixingDate <= Settings::instance().evaluationDate()) { |
297 | | // the amount is determined |
298 | 0 | Real a, b; |
299 | 0 | if (optionType == Option::Call) { |
300 | 0 | a = forwardRate_; |
301 | 0 | b = effStrike; |
302 | 0 | } else { |
303 | 0 | a = effStrike; |
304 | 0 | b = forwardRate_; |
305 | 0 | } |
306 | 0 | return gearing_ * std::max(a - b, 0.0); |
307 | 0 | } else { |
308 | | // not yet determined, use Black model |
309 | 0 | QL_REQUIRE(!capletVolatility().empty(), "BlackAveragingOvernightIndexedCouponPricer: missing optionlet volatility"); |
310 | 0 | std::vector<Date> fixingDates = coupon_->fixingDates(); |
311 | 0 | QL_REQUIRE(!fixingDates.empty(), "BlackAveragingOvernightIndexedCouponPricer: empty fixing dates"); |
312 | 0 | bool shiftedLn = capletVolatility()->volatilityType() == ShiftedLognormal; |
313 | 0 | Real shift = capletVolatility()->displacement(); |
314 | 0 | Real stdDev; |
315 | 0 | Real effectiveTime = capletVolatility()->timeFromReference(fixingDates.back()); |
316 | 0 | if (effectiveVolatilityInput()) { |
317 | | // vol input is effective, i.e. we use a plain black model |
318 | 0 | stdDev = capletVolatility()->volatility(fixingDates.back(), effStrike) * std::sqrt(effectiveTime); |
319 | 0 | } else { |
320 | | // vol input is not effective: |
321 | | // for the standard deviation see Lyashenko, Mercurio, Looking forward to backward looking rates, |
322 | | // section 6.3. the idea is to dampen the average volatility sigma between the fixing start and fixing end |
323 | | // date by a linear function going from (fixing start, 1) to (fixing end, 0) |
324 | 0 | Real fixingStartTime = capletVolatility()->timeFromReference(fixingDates.front()); |
325 | 0 | Real fixingEndTime = capletVolatility()->timeFromReference(fixingDates.back()); |
326 | 0 | Real sigma = capletVolatility()->volatility( |
327 | 0 | std::max(fixingDates.front(), capletVolatility()->referenceDate() + 1), effStrike); |
328 | 0 | Real T = std::max(fixingStartTime, 0.0); |
329 | 0 | if (!close_enough(fixingEndTime, T)) |
330 | 0 | T += std::pow(fixingEndTime - T, 3.0) / std::pow(fixingEndTime - fixingStartTime, 2.0) / 3.0; |
331 | 0 | stdDev = sigma * std::sqrt(T); |
332 | 0 | } |
333 | 0 | if (optionType == Option::Type::Call) |
334 | 0 | effectiveCapletVolatility_ = stdDev / std::sqrt(effectiveTime); |
335 | 0 | else |
336 | 0 | effectiveFloorletVolatility_ = stdDev / std::sqrt(effectiveTime); |
337 | 0 | Real fixing = shiftedLn ? blackFormula(optionType, effStrike, forwardRate_, stdDev, 1.0, shift) |
338 | 0 | : bachelierBlackFormula(optionType, effStrike, forwardRate_, stdDev, 1.0); |
339 | 0 | return gearing_ * fixing; |
340 | 0 | } |
341 | 0 | } |
342 | | |
343 | 0 | Real BlackAveragingOvernightIndexedCouponPricer::optionletRateLocal(Option::Type optionType, Real effStrike) const { |
344 | |
|
345 | 0 | QL_REQUIRE(!effectiveVolatilityInput(), |
346 | 0 | "BlackAveragingOvernightIndexedCouponPricer::optionletRateLocal() does not support effective volatility input."); |
347 | | |
348 | | // We compute a rate and a rawRate such that |
349 | | // rate * tau * nominal is the amount of the coupon with daily capped / floored rates |
350 | | // rawRate * tau * nominal is the amount of the coupon without capping / flooring the rate |
351 | | // We will then return the difference between rate and rawRate (with the correct sign, see below) |
352 | | // as the option component of the coupon. |
353 | | |
354 | | // See CappedFlooredOvernightIndexedCoupon::effectiveCap(), Floor() for what is passed in as effStrike. |
355 | | // From this we back out the absolute strike at which the |
356 | | // - daily rate + spread (spread included) or the |
357 | | // - daily rate (spread excluded) |
358 | | // is capped / floored. |
359 | | |
360 | 0 | Real absStrike = coupon_->compoundSpreadDaily() ? effStrike + coupon_->spread() : effStrike; |
361 | | |
362 | | // This following code is inevitably quite similar to the plain ON coupon pricer code, possibly we can refactor |
363 | | // this, but as a first step it seems safer to add the full modified code explicitly here and leave the original |
364 | | // code alone. |
365 | |
|
366 | 0 | ext::shared_ptr<OvernightIndex> index = ext::dynamic_pointer_cast<OvernightIndex>(coupon_->index()); |
367 | |
|
368 | 0 | const std::vector<Date>& fixingDates = coupon_->fixingDates(); |
369 | 0 | const std::vector<Time>& dt = coupon_->dt(); |
370 | |
|
371 | 0 | Size n = dt.size(); |
372 | 0 | Size i = 0; |
373 | 0 | QL_REQUIRE(coupon_->lockoutDays() < n, |
374 | 0 | "rate cutoff (" << coupon_->lockoutDays() |
375 | 0 | << ") must be less than number of fixings in period (" << n << ")"); |
376 | 0 | Size nCutoff = n - coupon_->lockoutDays(); |
377 | |
|
378 | 0 | Real accumulatedRate = 0.0, accumulatedRateRaw = 0.0; |
379 | | |
380 | | // already fixed part |
381 | 0 | Date today = Settings::instance().evaluationDate(); |
382 | 0 | while (i < n && fixingDates[std::min(i, nCutoff)] < today) { |
383 | | // rate must have been fixed |
384 | 0 | Rate pastFixing = index->pastFixing(fixingDates[std::min(i, nCutoff)]); |
385 | 0 | QL_REQUIRE(pastFixing != Null<Real>(), |
386 | 0 | "Missing " << index->name() << " fixing for " << fixingDates[std::min(i, nCutoff)]); |
387 | 0 | if (coupon_->compoundSpreadDaily()) { |
388 | 0 | pastFixing += coupon_->spread(); |
389 | 0 | } |
390 | 0 | accumulatedRate += cappedFlooredRate(pastFixing, optionType, absStrike) * dt[i]; |
391 | 0 | accumulatedRateRaw += pastFixing * dt[i]; |
392 | 0 | ++i; |
393 | 0 | } |
394 | | |
395 | | // today is a border case |
396 | 0 | if (i < n && fixingDates[std::min(i, nCutoff)] == today) { |
397 | | // might have been fixed |
398 | 0 | try { |
399 | 0 | Rate pastFixing = index->pastFixing(today); |
400 | 0 | if (pastFixing != Null<Real>()) { |
401 | 0 | if (coupon_->compoundSpreadDaily()) { |
402 | 0 | pastFixing += coupon_->spread(); |
403 | 0 | } |
404 | 0 | accumulatedRate += cappedFlooredRate(pastFixing, optionType, absStrike) * dt[i]; |
405 | 0 | accumulatedRateRaw += pastFixing * dt[i]; |
406 | 0 | ++i; |
407 | 0 | } else { |
408 | 0 | ; // fall through and forecast |
409 | 0 | } |
410 | 0 | } catch (Error&) { |
411 | 0 | ; // fall through and forecast |
412 | 0 | } |
413 | 0 | } |
414 | | |
415 | | // forward part, approximation by pricing a cap / floor in the middle of the future period |
416 | 0 | const std::vector<Date>& dates = coupon_->valueDates(); |
417 | 0 | if (i < n) { |
418 | 0 | Handle<YieldTermStructure> curve = index->forwardingTermStructure(); |
419 | 0 | QL_REQUIRE(!curve.empty(), "null term structure set to this instance of " << index->name()); |
420 | | |
421 | 0 | DiscountFactor startDiscount = curve->discount(dates[i]); |
422 | 0 | DiscountFactor endDiscount = curve->discount(dates[std::max(nCutoff, i)]); |
423 | | |
424 | | // handle the rate cutoff period (if there is any, i.e. if nCutoff < n) |
425 | 0 | if (nCutoff < n) { |
426 | | // forward discount factor for one calendar day on the cutoff date |
427 | 0 | DiscountFactor discountCutoffDate = curve->discount(dates[nCutoff] + 1) / curve->discount(dates[nCutoff]); |
428 | | // keep the above forward discount factor constant during the cutoff period |
429 | 0 | endDiscount *= std::pow(discountCutoffDate, dates[n] - dates[nCutoff]); |
430 | 0 | } |
431 | | |
432 | | // estimate the average daily rate over the future period (approximate the continuously compounded rate) |
433 | 0 | Real tau = coupon_->dayCounter().yearFraction(dates[i], dates.back()); |
434 | 0 | Real averageRate = -std::log(endDiscount / startDiscount) / tau; |
435 | | |
436 | | // compute the value of a cap or floor with fixing in the middle of the future period |
437 | | // (but accounting for the rate cutoff here) |
438 | 0 | Time midPoint = |
439 | 0 | (capletVolatility()->timeFromReference(dates[i]) + capletVolatility()->timeFromReference(dates[nCutoff])) / |
440 | 0 | 2.0; |
441 | 0 | Real stdDev = capletVolatility()->volatility(midPoint, effStrike) * std::sqrt(midPoint); |
442 | 0 | Real shift = capletVolatility()->displacement(); |
443 | 0 | bool shiftedLn = capletVolatility()->volatilityType() == ShiftedLognormal; |
444 | 0 | Rate cfValue = shiftedLn ? blackFormula(optionType, effStrike, averageRate, stdDev, 1.0, shift) |
445 | 0 | : bachelierBlackFormula(optionType, effStrike, averageRate, stdDev, 1.0); |
446 | |
|
447 | 0 | Real effectiveTime = capletVolatility()->timeFromReference(fixingDates.back()); |
448 | 0 | if (optionType == Option::Type::Call) |
449 | 0 | effectiveCapletVolatility_ = stdDev / std::sqrt(effectiveTime); |
450 | 0 | else |
451 | 0 | effectiveFloorletVolatility_ = stdDev / std::sqrt(effectiveTime); |
452 | | |
453 | | // add spread to average rate |
454 | 0 | if (coupon_->compoundSpreadDaily()) { |
455 | 0 | averageRate += coupon_->spread(); |
456 | 0 | } |
457 | | |
458 | | // incorporate cap/floor into average rate |
459 | 0 | Real averageRateRaw = averageRate; |
460 | 0 | averageRate += optionType == Option::Call ? (-cfValue) : cfValue; |
461 | | |
462 | | // now assume the averageRate is the effective rate over the future period and update the average rate |
463 | | // this is an approximation, see "Ester / Daily Spread Curve Setup in ORE": set tau to avg value |
464 | 0 | Real dailyTau = |
465 | 0 | coupon_->dayCounter().yearFraction(dates[i], dates.back()) / (dates.back() - dates[i]); |
466 | 0 | accumulatedRate += dailyTau * averageRate * static_cast<Real>(dates.back() - dates[i]); |
467 | 0 | accumulatedRateRaw += dailyTau * averageRateRaw * static_cast<Real>(dates.back() - dates[i]); |
468 | 0 | } |
469 | | |
470 | 0 | Rate tau = coupon_->fixingDays() == 0 |
471 | 0 | ? coupon_->accrualPeriod() |
472 | 0 | : coupon_->dayCounter().yearFraction(dates.front(), dates.back()); |
473 | 0 | Rate rate = accumulatedRate / tau; |
474 | 0 | Rate rawRate = accumulatedRateRaw / tau; |
475 | |
|
476 | 0 | rate *= coupon_->gearing(); |
477 | 0 | rawRate *= coupon_->gearing(); |
478 | |
|
479 | 0 | if (!coupon_->compoundSpreadDaily()) { |
480 | 0 | rate += coupon_->spread(); |
481 | 0 | rawRate += coupon_->spread(); |
482 | 0 | } |
483 | | |
484 | | // return optionletRate := r - rawRate, i.e. the option component only |
485 | | // (see CappedFlooredAverageONIndexedCoupon::rate() for the signs of the capletRate / flooredRate) |
486 | |
|
487 | 0 | return (optionType == Option::Call ? -1.0 : 1.0) * (rate - rawRate); |
488 | 0 | } |
489 | | |
490 | 0 | Rate BlackAveragingOvernightIndexedCouponPricer::swapletRate() const { return swapletRate_; } |
491 | | |
492 | 0 | Rate BlackAveragingOvernightIndexedCouponPricer::capletRate(Rate effectiveCap) const { |
493 | 0 | return capletRate(effectiveCap, false); |
494 | 0 | } |
495 | | |
496 | 0 | Rate BlackAveragingOvernightIndexedCouponPricer::floorletRate(Rate effectiveFloor) const { |
497 | 0 | return floorletRate(effectiveFloor, false); |
498 | 0 | } |
499 | | |
500 | 0 | Rate BlackAveragingOvernightIndexedCouponPricer::capletRate(Rate effectiveCap, bool dailyCapFloor) const { |
501 | 0 | return dailyCapFloor ? optionletRateLocal(Option::Call, effectiveCap) |
502 | 0 | : optionletRateGlobal(Option::Call, effectiveCap); |
503 | 0 | } |
504 | | |
505 | 0 | Rate BlackAveragingOvernightIndexedCouponPricer::floorletRate(Rate effectiveFloor, bool dailyCapFloor) const { |
506 | 0 | return dailyCapFloor ? optionletRateLocal(Option::Put, effectiveFloor) |
507 | 0 | : optionletRateGlobal(Option::Put, effectiveFloor); |
508 | 0 | } |
509 | | |
510 | 0 | Real BlackAveragingOvernightIndexedCouponPricer::swapletPrice() const { |
511 | 0 | QL_FAIL("BlackAveragingOvernightIndexedCouponPricer::swapletPrice() not provided"); |
512 | 0 | } |
513 | | |
514 | 0 | Real BlackAveragingOvernightIndexedCouponPricer::capletPrice(Rate effectiveCap) const { |
515 | 0 | QL_FAIL("BlackAveragingOvernightIndexedCouponPricer::capletPrice() not provided"); |
516 | 0 | } |
517 | | |
518 | 0 | Real BlackAveragingOvernightIndexedCouponPricer::floorletPrice(Rate effectiveFloor) const { |
519 | | QL_FAIL("BlackAveragingOvernightIndexedCouponPricer::floorletPrice() not provided"); |
520 | 0 | } |
521 | | |
522 | | } |