Coverage Report

Created: 2026-01-25 06:59

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