Coverage Report

Created: 2026-03-11 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}