• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lballabio / QuantLib / 30783683586

03 Aug 2026 04:11AM UTC coverage: 74.96% (+0.04%) from 74.923%
30783683586

Pull #2705

github

web-flow
Merge 10275255b into 60be62969
Pull Request #2705: Rfr caplet vol bootstrapping

153 of 156 new or added lines in 3 files covered. (98.08%)

9 existing lines in 2 files now uncovered.

60217 of 80332 relevant lines covered (74.96%)

8533134.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.5
/ql/termstructures/volatility/optionlet/optionletstripper.cpp
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2

3
/*
4
 Copyright (C) 2007 Ferdinando Ametrano
5
 Copyright (C) 2007 Giorgio Facchinetti
6
 Copyright (C) 2015 Peter Caspers
7
 Copyright (C) 2026 Kyrylo Protsenko
8

9
 This file is part of QuantLib, a free-software/open-source library
10
 for financial quantitative analysts and developers - http://quantlib.org/
11

12
 QuantLib is free software: you can redistribute it and/or modify it
13
 under the terms of the QuantLib license.  You should have received a
14
 copy of the license along with this program; if not, please email
15
 <quantlib-dev@lists.sf.net>. The license is also available online at
16
 <https://www.quantlib.org/license.shtml>.
17

18
 This program is distributed in the hope that it will be useful, but WITHOUT
19
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20
 FOR A PARTICULAR PURPOSE.  See the license for more details.
21
*/
22

23
#include <ql/cashflows/blackovernightindexedcouponpricer.hpp>
24
#include <ql/cashflows/iborcoupon.hpp>
25
#include <ql/cashflows/overnightindexedcoupon.hpp>
26
#include <ql/indexes/iborindex.hpp>
27
#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
28
#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
29
#include <ql/settings.hpp>
30
#include <ql/termstructures/volatility/optionlet/constantoptionletvol.hpp>
31
#include <ql/termstructures/volatility/optionlet/optionletstripper.hpp>
32
#include <utility>
33

34
using std::vector;
35

36
namespace QuantLib {
37

38
    OptionletStripper::OptionletStripper(
33✔
39
        const ext::shared_ptr<CapFloorTermVolSurface>& termVolSurface,
40
        ext::shared_ptr<IborIndex> iborIndex,
41
        Handle<YieldTermStructure> discount,
42
        const VolatilityType type,
43
        const Real displacement,
44
        std::optional<Period> optionletFrequency,
45
        Natural paymentLag
UNCOV
46
    )
×
47
    : termVolSurface_(termVolSurface), iborIndex_(std::move(iborIndex)),
48
      discount_(std::move(discount)), nStrikes_(termVolSurface->strikes().size()),
33✔
49
      volatilityType_(type), displacement_(displacement),
33✔
50
      optionletFrequency_(optionletFrequency), paymentLag_(paymentLag),
33✔
51
      isOvernightIndex_(static_cast<bool>(ext::dynamic_pointer_cast<OvernightIndex>(iborIndex_))) {
66✔
52

53
        if (volatilityType_ == Normal) {
33✔
54
            QL_REQUIRE(displacement_ == 0.0,
9✔
55
                       "non-null displacement is not allowed with Normal model");
56
        }
57

58
        if (isOvernightIndex_) {
33✔
59
            QL_REQUIRE(optionletFrequency_, 
8✔
60
                       "an optionlet frequency is required when using an overnight index");
61
        }
62

63
        registerWith(termVolSurface);
33✔
64
        registerWith(iborIndex_);
33✔
65
        registerWith(discount_);
33✔
66
        registerWith(Settings::instance().evaluationDate());
66✔
67

68
        Period indexTenor = optionletFrequency_ ? *optionletFrequency_ : iborIndex_->tenor();
33✔
69
        Period maxCapFloorTenor = termVolSurface->optionTenors().back();
33✔
70

71
        // optionlet tenors and capFloor lengths
72
        if (isOvernightIndex_) {
33✔
73
            Period nextCapFloorLength = indexTenor;
8✔
74
            QL_REQUIRE(maxCapFloorTenor >= nextCapFloorLength,
8✔
75
                       "too short (" << maxCapFloorTenor <<
76
                       ") capfloor term vol termVolSurface");
77
            while (nextCapFloorLength <= maxCapFloorTenor) {
328✔
78
                optionletTenors_.push_back(nextCapFloorLength);
320✔
79
                capFloorLengths_.push_back(nextCapFloorLength);
320✔
80
                nextCapFloorLength += indexTenor;
320✔
81
            }
82
        } else {
83
            optionletTenors_.push_back(indexTenor);
25✔
84
            capFloorLengths_.push_back(optionletTenors_.back()+indexTenor);
50✔
85
            QL_REQUIRE(maxCapFloorTenor>=capFloorLengths_.back(),
25✔
86
                       "too short (" << maxCapFloorTenor <<
87
                       ") capfloor term vol termVolSurface");
88
            Period nextCapFloorLength = capFloorLengths_.back()+indexTenor;
25✔
89
            while (nextCapFloorLength<=maxCapFloorTenor) {
915✔
90
                optionletTenors_.push_back(capFloorLengths_.back());
890✔
91
                capFloorLengths_.push_back(nextCapFloorLength);
890✔
92
                nextCapFloorLength += indexTenor;
890✔
93
            }
94
        }
95
        nOptionletTenors_ = optionletTenors_.size();
33✔
96
        
97
        optionletVolatilities_ =
98
            vector<vector<Volatility> >(nOptionletTenors_, 
66✔
99
                                        vector<Volatility>(nStrikes_));
99✔
100
        optionletStrikes_ = vector<vector<Rate> >(nOptionletTenors_,
66✔
101
                                                  termVolSurface->strikes());
66✔
102
        optionletDates_ = vector<Date>(nOptionletTenors_);
33✔
103
        optionletTimes_ = vector<Time>(nOptionletTenors_);
33✔
104
        atmOptionletRate_ = vector<Rate>(nOptionletTenors_);
33✔
105
        optionletPaymentDates_ = vector<Date>(nOptionletTenors_);
33✔
106
        optionletAccrualPeriods_ = vector<Time>(nOptionletTenors_);
33✔
107
    }
33✔
108

109
    const vector<Rate>& OptionletStripper::optionletStrikes(Size i) const {
4,171✔
110
        calculate();
4,171✔
111
        QL_REQUIRE(i<optionletStrikes_.size(),
4,171✔
112
                   "index (" << i <<
113
                   ") must be less than optionletStrikes size (" <<
114
                   optionletStrikes_.size() << ")");
115
        return optionletStrikes_[i];
4,171✔
116
    }   
117

118
    const vector<Volatility>&
119
    OptionletStripper::optionletVolatilities(Size i) const {
1,294✔
120
        calculate();
1,294✔
121
        QL_REQUIRE(i<optionletVolatilities_.size(),
1,294✔
122
                   "index (" << i <<
123
                   ") must be less than optionletVolatilities size (" <<
124
                   optionletVolatilities_.size() << ")");
125
        return optionletVolatilities_[i];
1,294✔
126
    }   
127

128
    const vector<Period>& OptionletStripper::optionletFixingTenors() const {
×
129
        return optionletTenors_;
×
130
    }
131

132
    const vector<Date>& OptionletStripper::optionletFixingDates() const {
1,438✔
133
        calculate();
1,438✔
134
        return optionletDates_;
1,438✔
135
    }
136
      
137
    const vector<Time>& OptionletStripper::optionletFixingTimes() const {
33,753✔
138
        calculate();
33,753✔
139
        return optionletTimes_;
33,753✔
140
    }
141
     
142
    Size OptionletStripper::optionletMaturities() const {
28✔
143
        return optionletTenors_.size();
28✔
144
    }
145

146
    const vector<Date>& OptionletStripper::optionletPaymentDates() const {
10✔
147
        calculate();
10✔
148
        return optionletPaymentDates_;
10✔
149
    }  
150

151
    const vector<Time>& OptionletStripper::optionletAccrualPeriods() const {
10✔
152
        calculate();
10✔
153
        return optionletAccrualPeriods_;
10✔
154
    }
155

156
    const vector<Rate>& OptionletStripper::atmOptionletRates() const {
10✔
157
        calculate();
10✔
158
        return atmOptionletRate_;
10✔
159
    }
160
    
161

162
    DayCounter OptionletStripper::dayCounter() const {
28✔
163
        return termVolSurface_->dayCounter();
28✔
164
    }
165

166
    Calendar OptionletStripper::calendar() const {
28✔
167
        return termVolSurface_->calendar();
28✔
168
    }
169

170
    Natural OptionletStripper::settlementDays() const {
28✔
171
        return termVolSurface_->settlementDays();
28✔
172
    }
173

174
    BusinessDayConvention OptionletStripper::businessDayConvention() const {
28✔
175
        return termVolSurface_->businessDayConvention();
28✔
176
    }
177

178
    ext::shared_ptr<CapFloorTermVolSurface>
179
    OptionletStripper::termVolSurface() const {
20✔
180
        return termVolSurface_;
20✔
181
    }
182

183
    ext::shared_ptr<IborIndex> OptionletStripper::iborIndex() const {
10✔
184
        return iborIndex_;
10✔
185
    }
186

187
    Real OptionletStripper::displacement() const {
95✔
188
        return displacement_;
95✔
189
    }
190

191
    VolatilityType OptionletStripper::volatilityType() const {
130✔
192
        return volatilityType_;
130✔
193
    }
194

195
    std::optional<Period> OptionletStripper::optionletFrequency() const {
10✔
196
        return optionletFrequency_;
10✔
197
    }
198

199
    Natural OptionletStripper::paymentLag() const {
10✔
200
        return paymentLag_;
10✔
201
    }
202

203
    bool OptionletStripper::isOvernightIndex() const {
10,781✔
204
        return isOvernightIndex_;
10,781✔
205
    }
206

207
    Leg OptionletStripper::makeCapFloorLeg(const Period& capFloorLength) const {
9,082✔
208
        if (isOvernightIndex_) {
9,082✔
209
            auto overnightIndex = ext::dynamic_pointer_cast<OvernightIndex>(iborIndex_);
35✔
210
            Date startDate = termVolSurface_->referenceDate();
35✔
211
            Date endDate = termVolSurface_->calendar().advance(
70✔
212
                startDate, capFloorLength, termVolSurface_->businessDayConvention());
35✔
213
            Schedule schedule(startDate, endDate, *optionletFrequency_,
214
                              termVolSurface_->calendar(),
35✔
215
                              termVolSurface_->businessDayConvention(),
35✔
216
                              termVolSurface_->businessDayConvention(),
35✔
217
                              DateGeneration::Forward, false);
140✔
218
            return OvernightLeg(schedule, overnightIndex)
70✔
219
                .withNotionals(1.0)
35✔
220
                .withPaymentCalendar(termVolSurface_->calendar())
70✔
221
                .withPaymentAdjustment(termVolSurface_->businessDayConvention())
35✔
222
                .withPaymentLag(paymentLag_)
35✔
223
                .withCouponPricer(ext::make_shared<CompoundingOvernightIndexedCouponPricer>());
70✔
224
        }
35✔
225

226
        Date startDate = iborIndex_->valueDate(
18,094✔
227
            iborIndex_->fixingCalendar().adjust(Settings::instance().evaluationDate()));
27,141✔
228
        Date endDate = startDate + capFloorLength;
9,047✔
229
        Schedule schedule(startDate, endDate, iborIndex_->tenor(),
18,094✔
230
                          iborIndex_->fixingCalendar(), iborIndex_->businessDayConvention(),
9,047✔
231
                          iborIndex_->businessDayConvention(), DateGeneration::Backward, false);
27,141✔
232
        Leg leg = IborLeg(schedule, iborIndex_)
18,094✔
233
                      .withNotionals(1.0)
9,047✔
234
                      .withPaymentDayCounter(iborIndex_->dayCounter())
9,047✔
235
                      .withPaymentAdjustment(iborIndex_->businessDayConvention())
9,047✔
236
                      .withPaymentLag(paymentLag_);
9,047✔
237
        // Spot-starting Ibor caps exclude the first, already-fixed caplet.
238
        leg.erase(leg.begin());
239
        QL_REQUIRE(!leg.empty(), "cap/floor length " << capFloorLength
9,049✔
240
                                                      << " does not contain any optionlets");
241
        return leg;
242
    }
9,048✔
243

244
    Handle<OptionletVolatilityStructure>
245
    OptionletStripper::constantOptionletVolatility(const Handle<Quote>& volatility) const {
35✔
246
        return Handle<OptionletVolatilityStructure>(
247
            ext::make_shared<ConstantOptionletVolatility>(
35✔
248
                termVolSurface_->referenceDate(), termVolSurface_->calendar(),
35✔
249
                termVolSurface_->businessDayConvention(), volatility,
70✔
250
                termVolSurface_->dayCounter(), volatilityType_, displacement_));
105✔
251
    }
252

253
    ext::shared_ptr<PricingEngine> OptionletStripper::makeCapFloorPricingEngine(
111✔
254
        const Handle<YieldTermStructure>& discountCurve,
255
        const Handle<Quote>& volatility) const {
256
        if (isOvernightIndex_)
111✔
257
            return makeCapFloorPricingEngine(
258
                discountCurve, constantOptionletVolatility(volatility));
70✔
259
        // Preserve the quote-based engines' zero-settlement reference date for Ibor.
260
        if (volatilityType_ == ShiftedLognormal)
76✔
261
            return ext::make_shared<BlackCapFloorEngine>(
75✔
262
                discountCurve, volatility, termVolSurface_->dayCounter(), displacement_);
150✔
263
        if (volatilityType_ == Normal)
1✔
264
            return ext::make_shared<BachelierCapFloorEngine>(
1✔
265
                discountCurve, volatility, termVolSurface_->dayCounter());
2✔
NEW
266
        QL_FAIL("unknown volatility type: " << volatilityType_);
×
267
    }
268

269
    ext::shared_ptr<PricingEngine> OptionletStripper::makeCapFloorPricingEngine(
123✔
270
        const Handle<YieldTermStructure>& discountCurve,
271
        const Handle<OptionletVolatilityStructure>& volatility) const {
272
        if (volatilityType_ == ShiftedLognormal)
123✔
273
            return ext::make_shared<BlackCapFloorEngine>(
58✔
274
                discountCurve, volatility, displacement_);
58✔
275
        if (volatilityType_ == Normal)
65✔
276
            return ext::make_shared<BachelierCapFloorEngine>(discountCurve, volatility);
65✔
NEW
277
        QL_FAIL("unknown volatility type: " << volatilityType_);
×
278
    }
279

280
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc