Coverage Report

Created: 2026-06-23 06:40

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