Coverage Report

Created: 2025-08-11 06:28

/src/quantlib/ql/pricingengines/vanilla/qdfpamericanengine.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) 2022 Klaus Spanderen
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
 <http://quantlib.org/license.shtml>.
14
15
 This program is distributed in the hope that it will be useful, but WITHOUT
16
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 FOR A PARTICULAR PURPOSE.  See the license for more details.
18
*/
19
20
/*! \file qrfpamericanengine.cpp
21
*/
22
23
#include <ql/math/distributions/normaldistribution.hpp>
24
#include <ql/math/functional.hpp>
25
#include <ql/math/integrals/gaussianquadratures.hpp>
26
#include <ql/math/integrals/tanhsinhintegral.hpp>
27
#include <ql/math/interpolations/chebyshevinterpolation.hpp>
28
#include <ql/pricingengines/blackcalculator.hpp>
29
#include <ql/pricingengines/vanilla/qdfpamericanengine.hpp>
30
#include <utility>
31
#ifndef QL_BOOST_HAS_TANH_SINH
32
#    include <ql/math/integrals/gausslobattointegral.hpp>
33
#endif
34
35
namespace QuantLib {
36
37
    QdFpLegendreScheme::QdFpLegendreScheme(
38
        Size l, Size m, Size n, Size p):
39
0
        m_(m), n_(n),
40
0
        fpIntegrator_(ext::make_shared<GaussLegendreIntegrator>(l)),
41
0
        exerciseBoundaryIntegrator_(
42
0
            ext::make_shared<GaussLegendreIntegrator>(p)) {
43
44
0
        QL_REQUIRE(m_ > 0, "at least one fixed point iteration step is needed");
45
0
        QL_REQUIRE(n_ > 0, "at least one interpolation point is needed");
46
0
    }
47
48
    Size QdFpLegendreScheme::getNumberOfChebyshevInterpolationNodes()
49
0
        const {
50
0
        return n_;
51
0
    }
52
0
    Size QdFpLegendreScheme::getNumberOfNaiveFixedPointSteps() const {
53
0
        return m_-1;
54
0
    }
55
    Size QdFpLegendreScheme::getNumberOfJacobiNewtonFixedPointSteps()
56
0
        const {
57
0
        return Size(1);
58
0
    }
59
60
    ext::shared_ptr<Integrator>
61
0
        QdFpLegendreScheme::getFixedPointIntegrator() const {
62
0
        return fpIntegrator_;
63
0
    }
64
    ext::shared_ptr<Integrator>
65
        QdFpLegendreScheme::getExerciseBoundaryToPriceIntegrator()
66
0
        const {
67
0
        return exerciseBoundaryIntegrator_;
68
0
    }
69
70
    QdFpTanhSinhIterationScheme::QdFpTanhSinhIterationScheme(
71
        Size m, Size n, Real eps)
72
0
    : m_(m), n_(n),
73
#ifdef QL_BOOST_HAS_TANH_SINH
74
0
      integrator_(ext::make_shared<TanhSinhIntegral>(eps))
75
#else
76
      integrator_(ext::make_shared<GaussLobattoIntegral>(
77
          100000, QL_MAX_REAL, 0.1*eps))
78
#endif
79
0
    {}
80
81
    Size QdFpTanhSinhIterationScheme::getNumberOfChebyshevInterpolationNodes()
82
0
        const {
83
0
        return n_;
84
0
    }
85
0
    Size QdFpTanhSinhIterationScheme::getNumberOfNaiveFixedPointSteps() const {
86
0
        return m_-1;
87
0
    }
88
    Size QdFpTanhSinhIterationScheme::getNumberOfJacobiNewtonFixedPointSteps()
89
0
        const {
90
0
        return Size(1);
91
0
    }
92
93
    ext::shared_ptr<Integrator>
94
0
    QdFpTanhSinhIterationScheme::getFixedPointIntegrator() const {
95
0
        return integrator_;
96
0
    }
97
    ext::shared_ptr<Integrator>
98
    QdFpTanhSinhIterationScheme::getExerciseBoundaryToPriceIntegrator()
99
0
        const {
100
0
        return integrator_;
101
0
    }
102
103
    QdFpLegendreTanhSinhScheme::QdFpLegendreTanhSinhScheme(
104
        Size l, Size m, Size n, Real eps)
105
0
    : QdFpLegendreScheme(l, m, n, 1),
106
0
      eps_(eps) {}
107
108
    ext::shared_ptr<Integrator>
109
0
    QdFpLegendreTanhSinhScheme::getExerciseBoundaryToPriceIntegrator() const {
110
0
#ifdef QL_BOOST_HAS_TANH_SINH
111
0
            return ext::make_shared<TanhSinhIntegral>(eps_);
112
#else
113
            return ext::make_shared<GaussLobattoIntegral>(
114
                100000, QL_MAX_REAL, 0.1*eps_);
115
#endif
116
0
    }
117
118
119
    class DqFpEquation {
120
      public:
121
        DqFpEquation(Rate _r,
122
                     Rate _q,
123
                     Volatility _vol,
124
                     std::function<Real(Real)> B,
125
                     ext::shared_ptr<Integrator> _integrator)
126
0
        : r(_r), q(_q), vol(_vol), B(std::move(B)), integrator(std::move(_integrator)) {
127
0
            const auto legendreIntegrator =
128
0
                ext::dynamic_pointer_cast<GaussLegendreIntegrator>(integrator);
129
130
0
            if (legendreIntegrator != nullptr) {
131
0
                x_i = legendreIntegrator->getIntegration()->x();
132
0
                w_i = legendreIntegrator->getIntegration()->weights();
133
0
            }
134
0
        }
135
136
        virtual std::pair<Real, Real> NDd(Real tau, Real b) const = 0;
137
        virtual std::tuple<Real, Real, Real> f(Real tau, Real b) const = 0;
138
139
0
        virtual ~DqFpEquation() = default;
140
141
      protected:
142
0
        std::pair<Real, Real> d(Time t, Real z) const {
143
0
            const Real v = vol * std::sqrt(t);
144
0
            const Real m = (std::log(z) + (r-q)*t)/v + 0.5*v;
145
146
0
            return std::make_pair(m, m-v);
147
0
        }
148
149
        Array x_i, w_i;
150
        const Rate r, q;
151
        const Volatility vol;
152
153
        const std::function<Real(Real)> B;
154
        const ext::shared_ptr<Integrator> integrator;
155
156
        const NormalDistribution phi;
157
        const CumulativeNormalDistribution Phi;
158
    };
159
160
    class DqFpEquation_B: public DqFpEquation {
161
      public:
162
        DqFpEquation_B(Real K,
163
                       Rate _r,
164
                       Rate _q,
165
                       Volatility _vol,
166
                       std::function<Real(Real)> B,
167
                       ext::shared_ptr<Integrator> _integrator);
168
169
        std::pair<Real, Real> NDd(Real tau, Real b) const override;
170
        std::tuple<Real, Real, Real> f(Real tau, Real b) const override;
171
172
      private:
173
          const Real K;
174
    };
175
176
    class DqFpEquation_A: public DqFpEquation {
177
      public:
178
        DqFpEquation_A(Real K,
179
                       Rate _r,
180
                       Rate _q,
181
                       Volatility _vol,
182
                       std::function<Real(Real)> B,
183
                       ext::shared_ptr<Integrator> _integrator);
184
185
        std::pair<Real, Real> NDd(Real tau, Real b) const override;
186
        std::tuple<Real, Real, Real> f(Real tau, Real b) const override;
187
188
      private:
189
          const Real K;
190
    };
191
192
    DqFpEquation_A::DqFpEquation_A(Real K,
193
                                   Rate _r,
194
                                   Rate _q,
195
                                   Volatility _vol,
196
                                   std::function<Real(Real)> B,
197
                                   ext::shared_ptr<Integrator> _integrator)
198
0
    : DqFpEquation(_r, _q, _vol, std::move(B), std::move(_integrator)), K(K) {}
199
200
0
    std::tuple<Real, Real, Real> DqFpEquation_A::f(Real tau, Real b) const {
201
0
        const Real v = vol * std::sqrt(tau);
202
203
0
        Real N, D;
204
0
        if (tau < squared(QL_EPSILON)) {
205
0
            if (close_enough(b, K)) {
206
0
                N = 1/(M_SQRT2*M_SQRTPI * v);
207
0
                D = N + 0.5;
208
0
            }
209
0
            else {
210
0
                N = 0.0;
211
0
                D = (b > K)? 1.0: 0.0;
212
0
            }
213
0
        }
214
0
        else {
215
0
            const Real stv = std::sqrt(tau)/vol;
216
217
0
            Real K12, K3;
218
0
            if (!x_i.empty()) {
219
0
                K12 = K3 = 0.0;
220
221
0
                for (Integer i = x_i.size()-1; i >= 0; --i) {
222
0
                    const Real y = x_i[i];
223
0
                    const Real m = 0.25*tau*squared(1+y);
224
0
                    const std::pair<Real, Real> dpm = d(m, b/B(tau-m));
225
226
0
                    K12 += w_i[i] * std::exp(q*tau - q*m)
227
0
                        *(0.5*tau*(y+1)*Phi(dpm.first) + stv*phi(dpm.first));
228
0
                    K3 += w_i[i] * stv*std::exp(r*tau-r*m)*phi(dpm.second);
229
0
                }
230
0
            } else {
231
0
                K12 = (*integrator)([&, this](Real y) -> Real {
232
0
                    const Real m = 0.25*tau*squared(1+y);
233
0
                    const Real df = std::exp(q*tau - q*m);
234
235
0
                    if (y <= 5*QL_EPSILON - 1) {
236
0
                        if (close_enough(b, B(tau-m)))
237
0
                            return df*stv/(M_SQRT2*M_SQRTPI);
238
0
                        else
239
0
                            return 0.0;
240
0
                    }
241
0
                    else {
242
0
                        const Real dp = d(m, b/B(tau-m)).first;
243
0
                        return df*(0.5*tau*(y+1)*Phi(dp) + stv*phi(dp));
244
0
                    }
245
0
                }, -1, 1);
246
247
0
                K3 = (*integrator)([&, this](Real y) -> Real {
248
0
                    const Real m = 0.25*tau*squared(1+y);
249
0
                    const Real df = std::exp(r*tau-r*m);
250
251
0
                    if (y <= 5*QL_EPSILON - 1) {
252
0
                        if (close_enough(b, B(tau-m)))
253
0
                            return df*stv/(M_SQRT2*M_SQRTPI);
254
0
                        else
255
0
                            return 0.0;
256
0
                    }
257
0
                    else
258
0
                        return df*stv*phi(d(m, b/B(tau-m)).second);
259
0
                }, -1, 1);
260
0
            }
261
0
            const std::pair<Real, Real> dpm = d(tau, b/K);
262
0
            N = phi(dpm.second)/v + r*K3;
263
0
            D = phi(dpm.first)/v + Phi(dpm.first) + q*K12;
264
0
        }
265
266
0
        const Real alpha = K*std::exp(-(r-q)*tau);
267
0
        Real fv;
268
0
        if (tau < squared(QL_EPSILON)) {
269
0
            if (close_enough(b, K))
270
0
                fv = alpha;
271
0
            else if (b > K)
272
0
                fv = 0.0;
273
0
            else {
274
0
                if (close_enough(q, Real(0)))
275
0
                    fv = alpha*r*((q < 0)? -1.0 : 1.0)/QL_EPSILON;
276
0
                else
277
0
                    fv = alpha*r/q;
278
0
            }
279
0
        }
280
0
        else
281
0
            fv = alpha*N/D;
282
283
0
        return std::make_tuple(N, D, fv);
284
0
    }
285
286
0
    std::pair<Real, Real> DqFpEquation_A::NDd(Real tau, Real b) const {
287
0
        Real Dd, Nd;
288
289
0
        if (tau < squared(QL_EPSILON)) {
290
0
            if (close_enough(b, K)) {
291
0
                const Real sqTau = std::sqrt(tau);
292
0
                const Real vol2 = vol*vol;
293
0
                Dd = M_1_SQRTPI*M_SQRT_2*(
294
0
                    -(0.5*vol2 + r-q) / (b*vol*vol2*sqTau) + 1 / (b*vol*sqTau));
295
0
                Nd = M_1_SQRTPI*M_SQRT_2 * (-0.5*vol2 + r-q)  / (b*vol*vol2*sqTau);
296
0
            }
297
0
            else
298
0
                Dd = Nd = 0.0;
299
0
        }
300
0
        else {
301
0
            const std::pair<Real, Real> dpm = d(tau, b/K);
302
303
0
            Dd = -phi(dpm.first) * dpm.first / (b*vol*vol*tau) +
304
0
                    phi(dpm.first) / (b*vol * std::sqrt(tau));
305
0
            Nd = -phi(dpm.second) * dpm.second / (b*vol*vol*tau);
306
0
        }
307
308
0
        return std::make_pair(Nd, Dd);
309
0
    }
310
311
312
    DqFpEquation_B::DqFpEquation_B(Real K,
313
                                   Rate _r,
314
                                   Rate _q,
315
                                   Volatility _vol,
316
                                   std::function<Real(Real)> B,
317
                                   ext::shared_ptr<Integrator> _integrator)
318
0
    : DqFpEquation(_r, _q, _vol, std::move(B), std::move(_integrator)), K(K) {}
319
320
321
0
    std::tuple<Real, Real, Real> DqFpEquation_B::f(Real tau, Real b) const {
322
0
        Real N, D;
323
0
        if (tau < squared(QL_EPSILON)) {
324
0
            if (close_enough(b, K))
325
0
                N = D = 0.5;
326
0
            else if (b < K)
327
0
                N = D = 0.0;
328
0
            else
329
0
                N = D = 1.0;
330
0
        }
331
0
        else {
332
0
            Real ni, di;
333
0
            if (!x_i.empty()) {
334
0
                const Real c = 0.5*tau;
335
336
0
                ni = di = 0.0;
337
0
                for (Integer i = x_i.size()-1; i >= 0; --i) {
338
0
                    const Real u = c*x_i[i] + c;
339
0
                    const std::pair<Real, Real> dpm = d(tau - u, b/B(u));
340
0
                    ni += w_i[i] * std::exp(r*u)*Phi(dpm.second);
341
0
                    di += w_i[i] * std::exp(q*u)*Phi(dpm.first);
342
0
                }
343
0
                ni *= c;
344
0
                di *= c;
345
0
            } else {
346
0
                ni = (*integrator)([&, this](Real u) -> Real {
347
0
                  const Real df = std::exp(r*u);
348
0
                    if (u >= tau*(1 - 5*QL_EPSILON)) {
349
0
                        if (close_enough(b, B(u)))
350
0
                            return 0.5*df;
351
0
                        else
352
0
                            return df*((b < B(u)? 0.0: 1.0));
353
0
                    }
354
0
                    else
355
0
                        return df*Phi(d(tau - u, b/B(u)).second);
356
0
                }, 0, tau);
357
0
                di = (*integrator)([&, this](Real u) -> Real {
358
0
                  const Real df = std::exp(q*u);
359
0
                    if (u >= tau*(1 - 5*QL_EPSILON)) {
360
0
                        if (close_enough(b, B(u)))
361
0
                            return 0.5*df;
362
0
                        else
363
0
                            return df*((b < B(u)? 0.0: 1.0));
364
0
                    }
365
0
                    else
366
0
                        return df*Phi(d(tau - u, b/B(u)).first);
367
0
                    }, 0, tau);
368
0
            }
369
370
0
            const std::pair<Real, Real> dpm = d(tau, b/K);
371
372
0
            N = Phi(dpm.second) + r*ni;
373
0
            D = Phi(dpm.first) + q*di;
374
0
        }
375
376
0
        Real fv;
377
0
        const Real alpha = K*std::exp(-(r-q)*tau);
378
0
        if (tau < squared(QL_EPSILON)) {
379
0
            if (close_enough(b, K) || b > K)
380
0
                fv = alpha;
381
0
            else {
382
0
                if (close_enough(q, Real(0)))
383
0
                    fv = alpha*r*((q < 0)? -1.0 : 1.0)/QL_EPSILON;
384
0
                else
385
0
                    fv = alpha*r/q;
386
0
            }
387
0
        }
388
0
        else
389
0
            fv = alpha*N/D;
390
391
0
        return std::make_tuple(N, D, fv);
392
0
    }
393
394
0
    std::pair<Real, Real> DqFpEquation_B::NDd(Real tau, Real b) const {
395
0
        const std::pair<Real, Real> dpm = d(tau, b/K);
396
0
        return std::make_pair(
397
0
            phi(dpm.second) / (b*vol*std::sqrt(tau)),
398
0
            phi(dpm.first)  / (b*vol*std::sqrt(tau))
399
0
        );
400
0
    }
401
402
    QdFpAmericanEngine::QdFpAmericanEngine(
403
        ext::shared_ptr<GeneralizedBlackScholesProcess> bsProcess,
404
        ext::shared_ptr<QdFpIterationScheme> iterationScheme,
405
        FixedPointEquation fpEquation)
406
0
    : detail::QdPutCallParityEngine(std::move(bsProcess)),
407
0
      iterationScheme_(std::move(iterationScheme)),
408
0
      fpEquation_(fpEquation) {
409
0
    }
410
411
    ext::shared_ptr<QdFpIterationScheme>
412
0
    QdFpAmericanEngine::fastScheme() {
413
0
        static auto scheme = ext::make_shared<QdFpLegendreScheme>(7, 2, 7, 27);
414
0
        return scheme;
415
0
    }
416
417
    ext::shared_ptr<QdFpIterationScheme>
418
0
    QdFpAmericanEngine::accurateScheme() {
419
0
        static auto scheme = ext::make_shared<QdFpLegendreTanhSinhScheme>(25, 5, 13, 1e-8);
420
0
        return scheme;
421
0
    }
422
423
    ext::shared_ptr<QdFpIterationScheme>
424
0
    QdFpAmericanEngine::highPrecisionScheme() {
425
0
        static auto scheme = ext::make_shared<QdFpTanhSinhIterationScheme>(10, 30, 1e-10);
426
0
        return scheme;
427
0
    }
428
429
    Real QdFpAmericanEngine::calculatePut(
430
0
            Real S, Real K, Rate r, Rate q, Volatility vol, Time T) const {
431
432
0
        if (r < 0.0 && q < r)
433
0
            QL_FAIL("double-boundary case q<r<0 for a put option is given");
434
435
0
        const Real xmax =  QdPlusAmericanEngine::xMax(K, r, q);
436
0
        const Size n = iterationScheme_->getNumberOfChebyshevInterpolationNodes();
437
438
0
        const ext::shared_ptr<ChebyshevInterpolation> interp =
439
0
            QdPlusAmericanEngine(
440
0
                    process_, n+1, QdPlusAmericanEngine::Halley, 1e-8)
441
0
                .getPutExerciseBoundary(S, K, r, q, vol, T);
442
443
0
        const Array z = interp->nodes();
444
0
        const Array x = 0.5*std::sqrt(T)*(1.0+z);
445
446
0
        const auto B = [xmax, T, &interp](Real tau) -> Real {
447
0
            const Real z = 2*std::sqrt(std::abs(tau)/T)-1;
448
0
            return xmax*std::exp(-std::sqrt(std::max(Real(0), (*interp)(z, true))));
449
0
        };
450
451
0
        const auto h = [=](Real fv) -> Real {
452
0
            return squared(std::log(fv/xmax));
453
0
        };
454
455
0
        const ext::shared_ptr<DqFpEquation> eqn
456
0
            = (fpEquation_ == FP_A
457
0
               || (fpEquation_ == Auto && std::abs(r-q) < 0.001))?
458
0
              ext::shared_ptr<DqFpEquation>(new DqFpEquation_A(
459
0
                  K, r, q, vol, B,
460
0
                  iterationScheme_->getFixedPointIntegrator()))
461
0
            : ext::shared_ptr<DqFpEquation>(new DqFpEquation_B(
462
0
                    K, r, q, vol, B,
463
0
                    iterationScheme_->getFixedPointIntegrator()));
464
465
0
        Array y(x.size());
466
0
        y[0] = 0.0;
467
468
0
        const Size n_newton
469
0
            = iterationScheme_->getNumberOfJacobiNewtonFixedPointSteps();
470
0
        for (Size k=0; k < n_newton; ++k) {
471
0
            for (Size i=1; i < x.size(); ++i) {
472
0
                const Real tau = squared(x[i]);
473
0
                const Real b = B(tau);
474
475
0
                const std::tuple<Real, Real, Real> results = eqn->f(tau, b);
476
0
                const Real N = std::get<0>(results);
477
0
                const Real D = std::get<1>(results);
478
0
                const Real fv = std::get<2>(results);
479
480
0
                if (tau < QL_EPSILON)
481
0
                    y[i] = h(fv);
482
0
                else {
483
0
                    const std::pair<Real, Real> ndd = eqn->NDd(tau, b);
484
0
                    const Real Nd = std::get<0>(ndd);
485
0
                    const Real Dd = std::get<1>(ndd);
486
487
0
                    const Real fd = K*std::exp(-(r-q)*tau) * (Nd/D - Dd*N/(D*D));
488
489
0
                    y[i] = h(b - (fv - b)/ (fd-1));
490
0
                }
491
0
            }
492
0
            interp->updateY(y);
493
0
        }
494
495
0
        const Size n_fp = iterationScheme_->getNumberOfNaiveFixedPointSteps();
496
0
        for (Size k=0; k < n_fp; ++k) {
497
0
            for (Size i=1; i < x.size(); ++i) {
498
0
                const Real tau = squared(x[i]);
499
0
                const Real fv = std::get<2>(eqn->f(tau, B(tau)));
500
501
0
                y[i] = h(fv);
502
0
            }
503
0
            interp->updateY(y);
504
0
        }
505
506
0
        const detail::QdPlusAddOnValue aov(T, S, K, r, q, vol, xmax, interp);
507
0
        const Real addOn =
508
0
           (*iterationScheme_->getExerciseBoundaryToPriceIntegrator())(
509
0
               aov, 0.0, std::sqrt(T));
510
511
0
        const Real europeanValue = BlackCalculator(
512
0
            Option::Put, K, S*std::exp((r-q)*T),
513
0
            vol*std::sqrt(T), std::exp(-r*T)).value();
514
515
0
        return std::max(europeanValue, 0.0) + std::max(0.0, addOn);
516
0
    }
517
518
}
519
520
521
522