Coverage Report

Created: 2025-08-11 06:28

/src/quantlib/ql/instruments/capfloor.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2006, 2014 Ferdinando Ametrano
5
 Copyright (C) 2006 François du Vignaud
6
 Copyright (C) 2001, 2002, 2003 Sadruddin Rejeb
7
 Copyright (C) 2006, 2007 StatPro Italia srl
8
 Copyright (C) 2016 Paolo Mazzocchi
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
 <http://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/any.hpp>
25
#include <ql/cashflows/cashflows.hpp>
26
#include <ql/instruments/capfloor.hpp>
27
#include <ql/math/solvers1d/newtonsafe.hpp>
28
#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
29
#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
30
#include <ql/quotes/simplequote.hpp>
31
#include <ql/termstructures/yieldtermstructure.hpp>
32
#include <ql/utilities/dataformatters.hpp>
33
#include <utility>
34
35
namespace QuantLib {
36
37
    namespace {
38
39
        class ImpliedCapVolHelper {
40
          public:
41
            ImpliedCapVolHelper(const CapFloor&,
42
                                Handle<YieldTermStructure> discountCurve,
43
                                Real targetValue,
44
                                Real displacement,
45
                                VolatilityType type);
46
            Real operator()(Volatility x) const;
47
            Real derivative(Volatility x) const;
48
          private:
49
            ext::shared_ptr<PricingEngine> engine_;
50
            Handle<YieldTermStructure> discountCurve_;
51
            Real targetValue_;
52
            ext::shared_ptr<SimpleQuote> vol_;
53
            const Instrument::results* results_;
54
        };
55
56
        ImpliedCapVolHelper::ImpliedCapVolHelper(const CapFloor& cap,
57
                                                 Handle<YieldTermStructure> discountCurve,
58
                                                 Real targetValue,
59
                                                 Real displacement,
60
                                                 VolatilityType type)
61
0
        : discountCurve_(std::move(discountCurve)), targetValue_(targetValue),
62
0
          vol_(ext::make_shared<SimpleQuote>(-1.0)) {
63
64
            // vol_ is set an implausible value, so that calculation is forced
65
            // at first ImpliedCapVolHelper::operator()(Volatility x) call
66
0
            Handle<Quote> h(vol_);
67
68
0
            switch (type) {
69
0
            case ShiftedLognormal:
70
0
                engine_ = ext::shared_ptr<PricingEngine>(new
71
0
                    BlackCapFloorEngine(discountCurve_, h, Actual365Fixed(),
72
0
                                                                displacement));
73
0
                break;
74
0
            case Normal:
75
0
                engine_ = ext::shared_ptr<PricingEngine>(new
76
0
                    BachelierCapFloorEngine(discountCurve_, h, 
77
0
                                                            Actual365Fixed()));
78
0
                break;
79
0
            default:
80
0
                QL_FAIL("unknown VolatilityType (" << type << ")");
81
0
                break;
82
0
            }
83
84
0
            cap.setupArguments(engine_->getArguments());
85
86
0
            results_ =
87
0
                dynamic_cast<const Instrument::results*>(engine_->getResults());
88
0
        }
89
90
0
        Real ImpliedCapVolHelper::operator()(Volatility x) const {
91
0
            if (x!=vol_->value()) {
92
0
                vol_->setValue(x);
93
0
                engine_->calculate();
94
0
            }
95
0
            return results_->value-targetValue_;
96
0
        }
97
98
0
        Real ImpliedCapVolHelper::derivative(Volatility x) const {
99
0
            if (x!=vol_->value()) {
100
0
                vol_->setValue(x);
101
0
                engine_->calculate();
102
0
            }
103
0
            auto vega_ = results_->additionalResults.find("vega");
104
0
            QL_REQUIRE(vega_ != results_->additionalResults.end(),
105
0
                       "vega not provided");
106
0
            return ext::any_cast<Real>(vega_->second);
107
0
        }
108
    }
109
110
    std::ostream& operator<<(std::ostream& out,
111
0
                             CapFloor::Type t) {
112
0
        switch (t) {
113
0
          case CapFloor::Cap:
114
0
            return out << "Cap";
115
0
          case CapFloor::Floor:
116
0
            return out << "Floor";
117
0
          case CapFloor::Collar:
118
0
            return out << "Collar";
119
0
          default:
120
0
            QL_FAIL("unknown CapFloor::Type (" << Integer(t) << ")");
121
0
        }
122
0
    }
123
124
    CapFloor::CapFloor(CapFloor::Type type,
125
                       Leg floatingLeg,
126
                       std::vector<Rate> capRates,
127
                       std::vector<Rate> floorRates)
128
0
    : type_(type), floatingLeg_(std::move(floatingLeg)), capRates_(std::move(capRates)),
129
0
      floorRates_(std::move(floorRates)) {
130
0
        if (type_ == Cap || type_ == Collar) {
131
0
            QL_REQUIRE(!capRates_.empty(), "no cap rates given");
132
0
            capRates_.reserve(floatingLeg_.size());
133
0
            while (capRates_.size() < floatingLeg_.size())
134
0
                capRates_.push_back(capRates_.back());
135
0
        }
136
0
        if (type_ == Floor || type_ == Collar) {
137
0
            QL_REQUIRE(!floorRates_.empty(), "no floor rates given");
138
0
            floorRates_.reserve(floatingLeg_.size());
139
0
            while (floorRates_.size() < floatingLeg_.size())
140
0
                floorRates_.push_back(floorRates_.back());
141
0
        }
142
0
        Leg::const_iterator i;
143
0
        for (i = floatingLeg_.begin(); i != floatingLeg_.end(); ++i)
144
0
            registerWith(*i);
145
146
0
        registerWith(Settings::instance().evaluationDate());
147
0
    }
Unexecuted instantiation: QuantLib::CapFloor::CapFloor(QuantLib::CapFloor::Type, std::__1::vector<boost::shared_ptr<QuantLib::CashFlow>, std::__1::allocator<boost::shared_ptr<QuantLib::CashFlow> > >, std::__1::vector<double, std::__1::allocator<double> >, std::__1::vector<double, std::__1::allocator<double> >)
Unexecuted instantiation: QuantLib::CapFloor::CapFloor(QuantLib::CapFloor::Type, std::__1::vector<boost::shared_ptr<QuantLib::CashFlow>, std::__1::allocator<boost::shared_ptr<QuantLib::CashFlow> > >, std::__1::vector<double, std::__1::allocator<double> >, std::__1::vector<double, std::__1::allocator<double> >)
148
149
    CapFloor::CapFloor(CapFloor::Type type, Leg floatingLeg, const std::vector<Rate>& strikes)
150
0
    : type_(type), floatingLeg_(std::move(floatingLeg)) {
151
0
        QL_REQUIRE(!strikes.empty(), "no strikes given");
152
0
        if (type_ == Cap) {
153
0
            capRates_ = strikes;
154
0
            capRates_.reserve(floatingLeg_.size());
155
0
            while (capRates_.size() < floatingLeg_.size())
156
0
                capRates_.push_back(capRates_.back());
157
0
        } else if (type_ == Floor) {
158
0
            floorRates_ = strikes;
159
0
            floorRates_.reserve(floatingLeg_.size());
160
0
            while (floorRates_.size() < floatingLeg_.size())
161
0
                floorRates_.push_back(floorRates_.back());
162
0
        } else
163
0
            QL_FAIL("only Cap/Floor types allowed in this constructor");
164
165
0
        Leg::const_iterator i;
166
0
        for (i = floatingLeg_.begin(); i != floatingLeg_.end(); ++i)
167
0
            registerWith(*i);
168
169
0
        registerWith(Settings::instance().evaluationDate());
170
0
    }
Unexecuted instantiation: QuantLib::CapFloor::CapFloor(QuantLib::CapFloor::Type, std::__1::vector<boost::shared_ptr<QuantLib::CashFlow>, std::__1::allocator<boost::shared_ptr<QuantLib::CashFlow> > >, std::__1::vector<double, std::__1::allocator<double> > const&)
Unexecuted instantiation: QuantLib::CapFloor::CapFloor(QuantLib::CapFloor::Type, std::__1::vector<boost::shared_ptr<QuantLib::CashFlow>, std::__1::allocator<boost::shared_ptr<QuantLib::CashFlow> > >, std::__1::vector<double, std::__1::allocator<double> > const&)
171
172
0
    bool CapFloor::isExpired() const {
173
0
        for (Size i=floatingLeg_.size(); i>0; --i)
174
0
            if (!floatingLeg_[i-1]->hasOccurred())
175
0
                return false;
176
0
        return true;
177
0
    }
178
179
0
    Date CapFloor::startDate() const {
180
0
        return CashFlows::startDate(floatingLeg_);
181
0
    }
182
183
0
    Date CapFloor::maturityDate() const {
184
0
        return CashFlows::maturityDate(floatingLeg_);
185
0
    }
186
187
    ext::shared_ptr<FloatingRateCoupon>
188
0
    CapFloor::lastFloatingRateCoupon() const {
189
0
        ext::shared_ptr<CashFlow> lastCF(floatingLeg_.back());
190
0
        ext::shared_ptr<FloatingRateCoupon> lastFloatingCoupon =
191
0
            ext::dynamic_pointer_cast<FloatingRateCoupon>(lastCF);
192
0
        return lastFloatingCoupon;
193
0
    }
194
195
0
    ext::shared_ptr<CapFloor> CapFloor::optionlet(const Size i) const {
196
0
        QL_REQUIRE(i < floatingLeg().size(),
197
0
                   io::ordinal(i+1) << " optionlet does not exist, only " <<
198
0
                   floatingLeg().size());
199
0
        Leg cf(1, floatingLeg()[i]);
200
201
0
        std::vector<Rate> cap, floor;
202
0
        if (type() == Cap || type() == Collar)
203
0
            cap.push_back(capRates()[i]);
204
0
        if (type() == Floor || type() == Collar)
205
0
            floor.push_back(floorRates()[i]);
206
207
0
        return ext::make_shared<CapFloor>(type(), cf, cap, floor);
208
0
    }
209
210
0
    void CapFloor::setupArguments(PricingEngine::arguments* args) const {
211
0
        auto* arguments = dynamic_cast<CapFloor::arguments*>(args);
212
0
        QL_REQUIRE(arguments != nullptr, "wrong argument type");
213
214
0
        Size n = floatingLeg_.size();
215
216
0
        arguments->startDates.resize(n);
217
0
        arguments->fixingDates.resize(n);
218
0
        arguments->endDates.resize(n);
219
0
        arguments->accrualTimes.resize(n);
220
0
        arguments->forwards.resize(n);
221
0
        arguments->nominals.resize(n);
222
0
        arguments->gearings.resize(n);
223
0
        arguments->capRates.resize(n);
224
0
        arguments->floorRates.resize(n);
225
0
        arguments->spreads.resize(n);
226
0
        arguments->indexes.resize(n);
227
228
0
        arguments->type = type_;
229
230
0
        Date today = Settings::instance().evaluationDate();
231
232
0
        for (Size i=0; i<n; ++i) {
233
0
            ext::shared_ptr<FloatingRateCoupon> coupon =
234
0
                ext::dynamic_pointer_cast<FloatingRateCoupon>(
235
0
                                                             floatingLeg_[i]);
236
0
            QL_REQUIRE(coupon, "non-FloatingRateCoupon given");
237
0
            arguments->startDates[i] = coupon->accrualStartDate();
238
0
            arguments->fixingDates[i] = coupon->fixingDate();
239
0
            arguments->endDates[i] = coupon->date();
240
241
            // this is passed explicitly for precision
242
0
            arguments->accrualTimes[i] = coupon->accrualPeriod();
243
244
            // this is passed explicitly for precision...
245
0
            if (arguments->endDates[i] >= today) { // ...but only if needed
246
0
                arguments->forwards[i] = coupon->adjustedFixing();
247
0
            } else {
248
0
                arguments->forwards[i] = Null<Rate>();
249
0
            }
250
251
0
            arguments->nominals[i] = coupon->nominal();
252
0
            Spread spread = coupon->spread();
253
0
            Real gearing = coupon->gearing();
254
0
            arguments->gearings[i] = gearing;
255
0
            arguments->spreads[i] = spread;
256
257
0
            if (type_ == Cap || type_ == Collar)
258
0
                arguments->capRates[i] = (capRates_[i]-spread)/gearing;
259
0
            else
260
0
                arguments->capRates[i] = Null<Rate>();
261
262
0
            if (type_ == Floor || type_ == Collar)
263
0
                arguments->floorRates[i] = (floorRates_[i]-spread)/gearing;
264
0
            else
265
0
                arguments->floorRates[i] = Null<Rate>();
266
267
0
            arguments->indexes[i] = coupon->index();
268
0
        }
269
0
    }
270
271
0
    void CapFloor::deepUpdate() {
272
0
        for (auto& i : floatingLeg_) {
273
0
            i->deepUpdate();
274
0
        }
275
0
        update();
276
0
    }
277
278
0
    void CapFloor::arguments::validate() const {
279
0
        QL_REQUIRE(endDates.size() == startDates.size(),
280
0
                   "number of start dates (" << startDates.size()
281
0
                   << ") different from that of end dates ("
282
0
                   << endDates.size() << ")");
283
0
        QL_REQUIRE(accrualTimes.size() == startDates.size(),
284
0
                   "number of start dates (" << startDates.size()
285
0
                   << ") different from that of accrual times ("
286
0
                   << accrualTimes.size() << ")");
287
0
        QL_REQUIRE(type == CapFloor::Floor ||
288
0
                   capRates.size() == startDates.size(),
289
0
                   "number of start dates (" << startDates.size()
290
0
                   << ") different from that of cap rates ("
291
0
                   << capRates.size() << ")");
292
0
        QL_REQUIRE(type == CapFloor::Cap ||
293
0
                   floorRates.size() == startDates.size(),
294
0
                   "number of start dates (" << startDates.size()
295
0
                   << ") different from that of floor rates ("
296
0
                   << floorRates.size() << ")");
297
0
        QL_REQUIRE(gearings.size() == startDates.size(),
298
0
                   "number of start dates (" << startDates.size()
299
0
                   << ") different from that of gearings ("
300
0
                   << gearings.size() << ")");
301
0
        QL_REQUIRE(spreads.size() == startDates.size(),
302
0
                   "number of start dates (" << startDates.size()
303
0
                   << ") different from that of spreads ("
304
0
                   << spreads.size() << ")");
305
0
        QL_REQUIRE(nominals.size() == startDates.size(),
306
0
                   "number of start dates (" << startDates.size()
307
0
                   << ") different from that of nominals ("
308
0
                   << nominals.size() << ")");
309
0
        QL_REQUIRE(forwards.size() == startDates.size(),
310
0
                   "number of start dates (" << startDates.size()
311
0
                   << ") different from that of forwards ("
312
0
                   << forwards.size() << ")");
313
0
    }
314
315
0
    Rate CapFloor::atmRate(const YieldTermStructure& discountCurve) const {
316
0
        bool includeSettlementDateFlows = false;
317
0
        Date settlementDate = discountCurve.referenceDate();
318
0
        return CashFlows::atmRate(floatingLeg_, discountCurve,
319
0
                                  includeSettlementDateFlows,
320
0
                                  settlementDate);
321
0
    }
322
323
    Volatility CapFloor::impliedVolatility(Real targetValue,
324
                                           const Handle<YieldTermStructure>& d,
325
                                           Volatility guess,
326
                                           Real accuracy,
327
                                           Natural maxEvaluations,
328
                                           Volatility minVol,
329
                                           Volatility maxVol,
330
                                           VolatilityType type,
331
0
                                           Real displacement) const {
332
        //calculate();
333
0
        QL_REQUIRE(!isExpired(), "instrument expired");
334
335
0
        ImpliedCapVolHelper f(*this, d, targetValue, displacement, type);
336
        //Brent solver;
337
0
        NewtonSafe solver;
338
0
        solver.setMaxEvaluations(maxEvaluations);
339
0
        return solver.solve(f, accuracy, guess, minVol, maxVol);
340
0
    }
341
342
}