Coverage Report

Created: 2026-02-03 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/cashflows/lineartsrpricer.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
  Copyright (C) 2014, 2016 Peter Caspers
5
6
  This file is part of QuantLib, a free-software/open-source library
7
  for financial quantitative analysts and developers - http://quantlib.org/
8
9
  QuantLib is free software: you can redistribute it and/or modify it
10
  under the terms of the QuantLib license.  You should have received a
11
  copy of the license along with this program; if not, please email
12
  <quantlib-dev@lists.sf.net>. The license is also available online at
13
  <https://www.quantlib.org/license.shtml>.
14
15
16
  This program is distributed in the hope that it will be useful, but
17
  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18
  or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
19
*/
20
21
/*! \file lineartsrpricer.cpp
22
*/
23
24
#include <ql/cashflows/cmscoupon.hpp>
25
#include <ql/cashflows/fixedratecoupon.hpp>
26
#include <ql/cashflows/iborcoupon.hpp>
27
#include <ql/cashflows/lineartsrpricer.hpp>
28
#include <ql/indexes/iborindex.hpp>
29
#include <ql/instruments/vanillaswap.hpp>
30
#include <ql/instruments/overnightindexedswap.hpp>
31
#include <ql/math/integrals/kronrodintegral.hpp>
32
#include <ql/math/solvers1d/brent.hpp>
33
#include <ql/pricingengines/blackformula.hpp>
34
#include <ql/quotes/simplequote.hpp>
35
#include <ql/termstructures/volatility/atmsmilesection.hpp>
36
#include <ql/termstructures/yieldtermstructure.hpp>
37
#include <ql/time/schedule.hpp>
38
#include <utility>
39
40
namespace QuantLib {
41
42
    class LinearTsrPricer::integrand_f {
43
        const LinearTsrPricer* pricer;
44
      public:
45
0
        explicit integrand_f(const LinearTsrPricer* pricer) : pricer(pricer) {}
46
0
        Real operator()(Real x) const {
47
0
            return pricer->integrand(x);
48
0
        }
49
    };
50
51
    const Real LinearTsrPricer::defaultLowerBound = 0.0001,
52
             LinearTsrPricer::defaultUpperBound = 2.0000;
53
54
    LinearTsrPricer::LinearTsrPricer(const Handle<SwaptionVolatilityStructure>& swaptionVol,
55
                                     Handle<Quote> meanReversion,
56
                                     Handle<YieldTermStructure> couponDiscountCurve,
57
                                     const Settings& settings,
58
                                     ext::shared_ptr<Integrator> integrator)
59
0
    : CmsCouponPricer(swaptionVol), meanReversion_(std::move(meanReversion)),
60
0
      couponDiscountCurve_(std::move(couponDiscountCurve)), settings_(settings),
61
0
      volDayCounter_(swaptionVol->dayCounter()), integrator_(std::move(integrator)) {
62
63
0
        if (!couponDiscountCurve_.empty())
64
0
            registerWith(couponDiscountCurve_);
65
66
0
        if (integrator_ == nullptr)
67
0
            integrator_ =
68
0
                ext::make_shared<GaussKronrodNonAdaptive>(1E-10, 5000, 1E-10);
69
0
    }
Unexecuted instantiation: QuantLib::LinearTsrPricer::LinearTsrPricer(QuantLib::Handle<QuantLib::SwaptionVolatilityStructure> const&, QuantLib::Handle<QuantLib::Quote>, QuantLib::Handle<QuantLib::YieldTermStructure>, QuantLib::LinearTsrPricer::Settings const&, boost::shared_ptr<QuantLib::Integrator>)
Unexecuted instantiation: QuantLib::LinearTsrPricer::LinearTsrPricer(QuantLib::Handle<QuantLib::SwaptionVolatilityStructure> const&, QuantLib::Handle<QuantLib::Quote>, QuantLib::Handle<QuantLib::YieldTermStructure>, QuantLib::LinearTsrPricer::Settings const&, boost::shared_ptr<QuantLib::Integrator>)
70
71
0
    Real LinearTsrPricer::GsrG(const Date &d) const {
72
73
0
        Real yf = volDayCounter_.yearFraction(fixingDate_, d);
74
0
        if (std::fabs(meanReversion_->value()) < 1.0E-4)
75
0
            return yf;
76
0
        else
77
0
            return (1.0 - std::exp(-meanReversion_->value() * yf)) /
78
0
                   meanReversion_->value();
79
0
    }
80
81
    Real LinearTsrPricer::singularTerms(const Option::Type type,
82
0
                                        const Real strike) const {
83
84
0
        Real omega = (type == Option::Call ? 1.0 : -1.0);
85
0
        Real s1 = std::max(omega * (swapRateValue_ - strike), 0.0) *
86
0
                  (a_ * swapRateValue_ + b_);
87
0
        Real s2 = (a_ * strike + b_) *
88
0
                  smileSection_->optionPrice(strike, strike < swapRateValue_
89
0
                                                         ? Option::Put
90
0
                                                         : Option::Call);
91
0
        return s1 + s2;
92
0
    }
93
94
0
    Real LinearTsrPricer::integrand(const Real strike) const {
95
0
        return 2.0 * a_ * smileSection_->optionPrice(
96
0
                              strike, strike < swapRateValue_ ? Option::Put
97
0
                                                              : Option::Call);
98
0
    }
99
100
0
    void LinearTsrPricer::initialize(const FloatingRateCoupon &coupon) {
101
102
0
        coupon_ = dynamic_cast<const CmsCoupon *>(&coupon);
103
0
        QL_REQUIRE(coupon_, "CMS coupon needed");
104
0
        gearing_ = coupon_->gearing();
105
0
        spread_ = coupon_->spread();
106
107
0
        fixingDate_ = coupon_->fixingDate();
108
0
        paymentDate_ = coupon_->date();
109
0
        swapIndex_ = coupon_->swapIndex();
110
111
0
        forwardCurve_ = swapIndex_->forwardingTermStructure();
112
0
        if (swapIndex_->exogenousDiscount())
113
0
            discountCurve_ = swapIndex_->discountingTermStructure();
114
0
        else
115
0
            discountCurve_ = forwardCurve_;
116
117
        // if no coupon discount curve is given just use the discounting curve
118
        // from the swap index. for rate calculation this curve cancels out in
119
        // the computation, so e.g. the discounting swap engine will produce
120
        // correct results, even if the couponDiscountCurve is not set here.
121
        // only the price member function in this class will be dependent on the
122
        // coupon discount curve.
123
124
0
        today_ = QuantLib::Settings::instance().evaluationDate();
125
126
0
        Real couponCurvePaymentDiscount;
127
0
        if (!couponDiscountCurve_.empty() && paymentDate_ > couponDiscountCurve_->referenceDate()) {
128
0
            couponCurvePaymentDiscount = couponDiscountCurve_->discount(paymentDate_);
129
0
        } else {
130
0
            couponCurvePaymentDiscount = 1.0;
131
0
        }
132
133
0
        if (paymentDate_ > discountCurve_->referenceDate()) {
134
0
            discountCurvePaymentDiscount_ = discountCurve_->discount(paymentDate_);
135
0
        } else {
136
0
            discountCurvePaymentDiscount_ = 1.0;
137
0
        }
138
139
140
0
        couponDiscountRatio_ = couponCurvePaymentDiscount / discountCurvePaymentDiscount_;
141
142
0
        spreadLegValue_ = spread_ * coupon_->accrualPeriod() * discountCurvePaymentDiscount_ *
143
0
                          couponDiscountRatio_;
144
145
0
        if (fixingDate_ > today_) {
146
147
0
            swapTenor_ = swapIndex_->tenor();
148
149
0
            if (auto on = ext::dynamic_pointer_cast<OvernightIndexedSwapIndex>(swapIndex_)) {
150
0
                swap_ = on->underlyingSwap(fixingDate_);
151
0
            } else {
152
0
                swap_ = swapIndex_->underlyingSwap(fixingDate_);
153
0
            }
154
0
            swapRateValue_ = swap_->fairRate();
155
0
            annuity_ = 1.0E4 * std::fabs(swap_->fixedLegBPS());
156
0
            Leg swapFixedLeg = swap_->fixedLeg();
157
158
0
            ext::shared_ptr<SmileSection> sectionTmp =
159
0
                swaptionVolatility()->smileSection(fixingDate_, swapTenor_);
160
161
0
            adjustedLowerBound_ = settings_.lowerRateBound_;
162
0
            adjustedUpperBound_ = settings_.upperRateBound_;
163
164
0
            if(sectionTmp->volatilityType() == Normal) {
165
                // adjust lower bound if it was not set explicitly
166
0
                if(settings_.defaultBounds_)
167
0
                    adjustedLowerBound_ = std::min(adjustedLowerBound_, -adjustedUpperBound_);
168
0
            } else {
169
                // adjust bounds by section's shift
170
0
                adjustedLowerBound_ -= sectionTmp->shift();
171
0
                adjustedUpperBound_ -= sectionTmp->shift();
172
0
            }
173
174
            // if the section does not provide an atm level, we enhance it to
175
            // have one, no need to exit with an exception ...
176
177
0
            if (sectionTmp->atmLevel() == Null<Real>())
178
0
                smileSection_ = ext::make_shared<AtmSmileSection>(
179
0
                    sectionTmp, swapRateValue_);
180
0
            else
181
0
                smileSection_ = sectionTmp;
182
183
            // compute linear model's parameters
184
185
0
            Real gx = 0.0, gy = 0.0;
186
0
            for (const auto& i : swapFixedLeg) {
187
0
                ext::shared_ptr<Coupon> c = ext::dynamic_pointer_cast<Coupon>(i);
188
0
                Real yf = c->accrualPeriod();
189
0
                Date d = c->date();
190
0
                Real pv = yf * discountCurve_->discount(d);
191
0
                gx += pv * GsrG(d);
192
0
                gy += pv;
193
0
            }
194
195
0
            Real gamma = gx / gy;
196
0
            Date lastd = swapFixedLeg.back()->date();
197
198
0
            a_ = discountCurve_->discount(paymentDate_) *
199
0
                 (gamma - GsrG(paymentDate_)) /
200
0
                 (discountCurve_->discount(lastd) * GsrG(lastd) +
201
0
                  swapRateValue_ * gy * gamma);
202
203
0
            b_ = discountCurve_->discount(paymentDate_) / gy -
204
0
                 a_ * swapRateValue_;
205
0
        }
206
0
    }
207
208
    Real LinearTsrPricer::strikeFromVegaRatio(Real ratio,
209
                                              Option::Type optionType,
210
0
                                              Real referenceStrike) const {
211
212
0
        Real a, b, min, max, k;
213
0
        if (optionType == Option::Call) {
214
0
            a = swapRateValue_;
215
0
            min = referenceStrike;
216
            // NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
217
0
            b = max = k =
218
0
                std::min(smileSection_->maxStrike(), adjustedUpperBound_);
219
0
        } else {
220
            // NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
221
0
            a = min = k =
222
0
                std::max(smileSection_->minStrike(), adjustedLowerBound_);
223
0
            b = swapRateValue_;
224
0
            max = referenceStrike;
225
0
        }
226
227
0
        VegaRatioHelper h(&*smileSection_,
228
0
                          smileSection_->vega(swapRateValue_) * ratio);
229
0
        Brent solver;
230
231
0
        try {
232
0
            k = solver.solve(h, 1.0E-5, (a + b) / 2.0, a, b);
233
0
        }
234
0
        catch (...) {
235
            // use default value set above
236
0
        }
237
238
0
        return std::min(std::max(k, min), max);
239
0
    }
240
241
    Real LinearTsrPricer::strikeFromPrice(Real price, Option::Type optionType,
242
0
                                          Real referenceStrike) const {
243
244
0
        Real a, b, min, max, k;
245
0
        if (optionType == Option::Call) {
246
0
            a = swapRateValue_;
247
0
            min = referenceStrike;
248
            // NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
249
0
            b = max = k =
250
0
                std::min(smileSection_->maxStrike(), adjustedUpperBound_);
251
0
        } else {
252
            // NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
253
0
            a = min = k =
254
0
                std::max(smileSection_->minStrike(), adjustedLowerBound_);
255
0
            b = swapRateValue_;
256
0
            max = referenceStrike;
257
0
        }
258
259
0
        PriceHelper h(&*smileSection_, optionType, price);
260
0
        Brent solver;
261
262
0
        try {
263
0
            k = solver.solve(h, 1.0E-5, swapRateValue_, a, b);
264
0
        }
265
0
        catch (...) {
266
            // use default value set above
267
0
        }
268
269
0
        return std::min(std::max(k, min), max);
270
0
    }
271
272
    Real LinearTsrPricer::optionletPrice(Option::Type optionType,
273
0
                                         Real strike) const {
274
275
0
        if (optionType == Option::Call && strike >= adjustedUpperBound_)
276
0
            return 0.0;
277
0
        if (optionType == Option::Put && strike <= adjustedLowerBound_)
278
0
            return 0.0;
279
280
        // determine lower or upper integration bound (depending on option type)
281
282
0
        Real lower = strike, upper = strike;
283
284
0
        switch (settings_.strategy_) {
285
286
0
        case Settings::RateBound: {
287
0
            if (optionType == Option::Call)
288
0
                upper = adjustedUpperBound_;
289
0
            else
290
0
                lower = adjustedLowerBound_;
291
0
            break;
292
0
        }
293
294
0
        case Settings::VegaRatio: {
295
            // strikeFromVegaRatio ensures that returned strike is on the
296
            // expected side of strike
297
0
            Real bound =
298
0
                strikeFromVegaRatio(settings_.vegaRatio_, optionType, strike);
299
0
            if (optionType == Option::Call)
300
0
                upper = std::min(bound, adjustedUpperBound_);
301
0
            else
302
0
                lower = std::max(bound, adjustedLowerBound_);
303
0
            break;
304
0
        }
305
306
0
        case Settings::PriceThreshold: {
307
            // strikeFromPrice ensures that returned strike is on the expected
308
            // side of strike
309
0
            Real bound =
310
0
                strikeFromPrice(settings_.vegaRatio_, optionType, strike);
311
0
            if (optionType == Option::Call)
312
0
                upper = std::min(bound, adjustedUpperBound_);
313
0
            else
314
0
                lower = std::max(bound, adjustedLowerBound_);
315
0
            break;
316
0
        }
317
318
0
        case Settings::BSStdDevs : {
319
0
            Real atm = smileSection_->atmLevel();
320
0
            Real atmVol = smileSection_->volatility(atm);
321
0
            Real shift = smileSection_->shift();
322
0
            Real lowerTmp, upperTmp;
323
0
            if (smileSection_->volatilityType() == ShiftedLognormal) {
324
0
                upperTmp = (atm + shift) *
325
0
                               std::exp(settings_.stdDevs_ * atmVol -
326
0
                                        0.5 * atmVol * atmVol *
327
0
                                            smileSection_->exerciseTime()) -
328
0
                           shift;
329
0
                lowerTmp = (atm + shift) *
330
0
                               std::exp(-settings_.stdDevs_ * atmVol -
331
0
                                        0.5 * atmVol * atmVol *
332
0
                                            smileSection_->exerciseTime()) -
333
0
                           shift;
334
0
            } else {
335
0
                Real tmp = settings_.stdDevs_ * atmVol *
336
0
                           std::sqrt(smileSection_->exerciseTime());
337
0
                upperTmp = atm + tmp;
338
0
                lowerTmp = atm - tmp;
339
0
            }
340
0
            upper = std::min(upperTmp - shift, adjustedUpperBound_);
341
0
            lower = std::max(lowerTmp - shift, adjustedLowerBound_);
342
0
            break;
343
0
        }
344
345
0
        default:
346
0
            QL_FAIL("Unknown strategy (" << settings_.strategy_ << ")");
347
0
        }
348
349
        // compute the relevant integral
350
351
0
        Real result = 0.0;
352
0
        Real tmpBound;
353
0
        if (upper > lower) {
354
0
            tmpBound = std::min(upper, swapRateValue_);
355
0
            if (tmpBound > lower) {
356
0
                result += (*integrator_)(integrand_f(this),
357
0
                                         lower, tmpBound);
358
0
            }
359
0
            tmpBound = std::max(lower, swapRateValue_);
360
0
            if (upper > tmpBound) {
361
0
                result += (*integrator_)(integrand_f(this),
362
0
                                         tmpBound, upper);
363
0
            }
364
0
            result *= (optionType == Option::Call ? 1.0 : -1.0);
365
0
        }
366
367
0
        result += singularTerms(optionType, strike);
368
369
0
        return annuity_ * result * couponDiscountRatio_ *
370
0
               coupon_->accrualPeriod();
371
0
    }
372
373
0
    Real LinearTsrPricer::meanReversion() const { return meanReversion_->value(); }
374
375
0
    Rate LinearTsrPricer::swapletRate() const {
376
0
        return swapletPrice() /
377
0
               (coupon_->accrualPeriod() * discountCurvePaymentDiscount_ * couponDiscountRatio_);
378
0
    }
379
380
0
    Real LinearTsrPricer::capletPrice(Rate effectiveCap) const {
381
        // caplet is equivalent to call option on fixing
382
0
        if (fixingDate_ <= today_) {
383
            // the fixing is determined
384
0
            const Rate Rs = std::max(
385
0
                coupon_->swapIndex()->fixing(fixingDate_) - effectiveCap, 0.);
386
0
            Rate price = (gearing_ * Rs) * (coupon_->accrualPeriod() *
387
0
                                            discountCurvePaymentDiscount_ * couponDiscountRatio_);
388
0
            return price;
389
0
        } else {
390
0
            Real capletPrice = optionletPrice(Option::Call, effectiveCap);
391
0
            return gearing_ * capletPrice;
392
0
        }
393
0
    }
394
395
0
    Rate LinearTsrPricer::capletRate(Rate effectiveCap) const {
396
0
        return capletPrice(effectiveCap) /
397
0
               (coupon_->accrualPeriod() * discountCurvePaymentDiscount_ * couponDiscountRatio_);
398
0
    }
399
400
0
    Real LinearTsrPricer::floorletPrice(Rate effectiveFloor) const {
401
        // floorlet is equivalent to put option on fixing
402
0
        if (fixingDate_ <= today_) {
403
            // the fixing is determined
404
0
            const Rate Rs = std::max(
405
0
                effectiveFloor - coupon_->swapIndex()->fixing(fixingDate_), 0.);
406
0
            Rate price = (gearing_ * Rs) * (coupon_->accrualPeriod() *
407
0
                                            discountCurvePaymentDiscount_ * couponDiscountRatio_);
408
0
            return price;
409
0
        } else {
410
0
            Real floorletPrice = optionletPrice(Option::Put, effectiveFloor);
411
0
            return gearing_ * floorletPrice;
412
0
        }
413
0
    }
414
415
0
    Rate LinearTsrPricer::floorletRate(Rate effectiveFloor) const {
416
0
        return floorletPrice(effectiveFloor) /
417
0
               (coupon_->accrualPeriod() * discountCurvePaymentDiscount_ * couponDiscountRatio_);
418
0
    }
419
420
0
    Real LinearTsrPricer::swapletPrice() const {
421
0
        if (fixingDate_ <= today_) {
422
            // the fixing is determined
423
0
            const Rate Rs = coupon_->swapIndex()->fixing(fixingDate_);
424
0
            Rate price =
425
0
                (gearing_ * Rs + spread_) *
426
0
                (coupon_->accrualPeriod() * discountCurvePaymentDiscount_ * couponDiscountRatio_);
427
0
            return price;
428
0
        } else {
429
0
            Real atmCapletPrice = optionletPrice(Option::Call, swapRateValue_);
430
0
            Real atmFloorletPrice = optionletPrice(Option::Put, swapRateValue_);
431
0
            return gearing_ * (coupon_->accrualPeriod() * discountCurvePaymentDiscount_ *
432
0
                                   swapRateValue_ * couponDiscountRatio_ +
433
0
                               atmCapletPrice - atmFloorletPrice) +
434
0
                   spreadLegValue_;
435
0
        }
436
0
    }
437
}