Coverage Report

Created: 2025-08-05 06:45

/src/quantlib/ql/pricingengines/blackformula.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) 2001, 2002, 2003 Sadruddin Rejeb
5
 Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2012 Ferdinando Ametrano
6
 Copyright (C) 2006 Mark Joshi
7
 Copyright (C) 2006 StatPro Italia srl
8
 Copyright (C) 2007 Cristina Duminuco
9
 Copyright (C) 2007 Chiara Fornarola
10
 Copyright (C) 2013 Gary Kennedy
11
 Copyright (C) 2015, 2024 Peter Caspers
12
 Copyright (C) 2017 Klaus Spanderen
13
 Copyright (C) 2019 Wojciech Ĺšlusarski
14
 Copyright (C) 2020 Marcin Rybacki
15
16
 This file is part of QuantLib, a free-software/open-source library
17
 for financial quantitative analysts and developers - http://quantlib.org/
18
19
 QuantLib is free software: you can redistribute it and/or modify it
20
 under the terms of the QuantLib license.  You should have received a
21
 copy of the license along with this program; if not, please email
22
 <quantlib-dev@lists.sf.net>. The license is also available online at
23
 <http://quantlib.org/license.shtml>.
24
25
 This program is distributed in the hope that it will be useful, but WITHOUT
26
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
27
 FOR A PARTICULAR PURPOSE.  See the license for more details.
28
*/
29
30
#include <ql/pricingengines/blackformula.hpp>
31
#include <ql/math/functional.hpp>
32
#include <ql/math/solvers1d/newtonsafe.hpp>
33
#include <ql/math/distributions/normaldistribution.hpp>
34
#include <boost/math/distributions/normal.hpp>
35
#include <boost/math/special_functions/fpclassify.hpp>
36
#include <boost/math/special_functions/atanh.hpp>
37
#include <boost/math/special_functions/sign.hpp>
38
39
namespace {
40
    void checkParameters(QuantLib::Real strike,
41
                         QuantLib::Real forward,
42
                         QuantLib::Real displacement)
43
0
    {
44
0
        QL_REQUIRE(displacement >= 0.0, "displacement ("
45
0
                                            << displacement
46
0
                                            << ") must be non-negative");
47
0
        QL_REQUIRE(strike + displacement >= 0.0,
48
0
                   "strike + displacement (" << strike << " + " << displacement
49
0
                                             << ") must be non-negative");
50
0
        QL_REQUIRE(forward + displacement > 0.0, "forward + displacement ("
51
0
                                                     << forward << " + "
52
0
                                                     << displacement
53
0
                                                     << ") must be positive");
54
0
    }
55
}
56
57
namespace QuantLib {
58
59
    Real blackFormula(Option::Type optionType,
60
                      Real strike,
61
                      Real forward,
62
                      Real stdDev,
63
                      Real discount,
64
                      Real displacement)
65
0
    {
66
0
        checkParameters(strike, forward, displacement);
67
0
        QL_REQUIRE(stdDev>=0.0,
68
0
                   "stdDev (" << stdDev << ") must be non-negative");
69
0
        QL_REQUIRE(discount>0.0,
70
0
                   "discount (" << discount << ") must be positive");
71
72
0
        auto sign = Integer(optionType);
73
74
0
        if (stdDev == 0.0)
75
0
            return std::max((forward-strike) * sign, Real(0.0)) * discount;
76
77
0
        forward = forward + displacement;
78
0
        strike = strike + displacement;
79
80
        // since displacement is non-negative strike==0 iff displacement==0
81
        // so returning forward*discount is OK
82
0
        if (strike==0.0)
83
0
            return (optionType==Option::Call ? Real(forward*discount) : 0.0);
84
85
0
        Real d1 = std::log(forward/strike)/stdDev + 0.5*stdDev;
86
0
        Real d2 = d1 - stdDev;
87
0
        CumulativeNormalDistribution phi;
88
0
        Real nd1 = phi(sign * d1);
89
0
        Real nd2 = phi(sign * d2);
90
0
        Real result = discount * sign * (forward*nd1 - strike*nd2);
91
0
        QL_ENSURE(result>=0.0,
92
0
                  "negative value (" << result << ") for " <<
93
0
                  stdDev << " stdDev, " <<
94
0
                  optionType << " option, " <<
95
0
                  strike << " strike , " <<
96
0
                  forward << " forward");
97
0
        return result;
98
0
    }
99
100
    Real blackFormula(const ext::shared_ptr<PlainVanillaPayoff>& payoff,
101
                      Real forward,
102
                      Real stdDev,
103
                      Real discount,
104
0
                      Real displacement) {
105
0
        return blackFormula(payoff->optionType(),
106
0
            payoff->strike(), forward, stdDev, discount, displacement);
107
0
    }
108
109
    Real blackFormulaForwardDerivative(Option::Type optionType,
110
                                       Real strike,
111
                                       Real forward,
112
                                       Real stdDev,
113
                                       Real discount,
114
                                       Real displacement)
115
0
    {
116
0
        checkParameters(strike, forward, displacement);
117
0
        QL_REQUIRE(stdDev>=0.0,
118
0
                   "stdDev (" << stdDev << ") must be non-negative");
119
0
        QL_REQUIRE(discount>0.0,
120
0
                   "discount (" << discount << ") must be positive");
121
122
0
        auto sign = Integer(optionType);
123
124
0
        if (stdDev == 0.0)
125
0
            return sign * std::max(1.0 * boost::math::sign((forward - strike) * sign), 0.0) * discount;
126
127
0
        forward = forward + displacement;
128
0
        strike = strike + displacement;
129
130
0
        if (strike == 0.0)
131
0
            return (optionType == Option::Call ? discount : 0.0);
132
133
0
        Real d1 = std::log(forward/strike)/stdDev + 0.5*stdDev;
134
0
        CumulativeNormalDistribution phi;
135
0
        return sign * phi(sign * d1) * discount;
136
0
    }
137
138
    Real blackFormulaForwardDerivative(const ext::shared_ptr<PlainVanillaPayoff>& payoff,
139
                                       Real forward,
140
                                       Real stdDev,
141
                                       Real discount,
142
                                       Real displacement) 
143
0
    {
144
0
        return blackFormulaForwardDerivative(payoff->optionType(),
145
0
            payoff->strike(), forward, stdDev, discount, displacement);
146
0
    }
147
148
    Real blackFormulaImpliedStdDevApproximation(Option::Type optionType,
149
                                                Real strike,
150
                                                Real forward,
151
                                                Real blackPrice,
152
                                                Real discount,
153
                                                Real displacement)
154
0
    {
155
0
        checkParameters(strike, forward, displacement);
156
0
        QL_REQUIRE(blackPrice>=0.0,
157
0
                   "blackPrice (" << blackPrice << ") must be non-negative");
158
0
        QL_REQUIRE(discount>0.0,
159
0
                   "discount (" << discount << ") must be positive");
160
161
0
        Real stdDev;
162
0
        forward = forward + displacement;
163
0
        strike = strike + displacement;
164
0
        if (strike==forward)
165
            // Brenner-Subrahmanyan (1988) and Feinstein (1988) ATM approx.
166
0
            stdDev = blackPrice/discount*std::sqrt(2.0 * M_PI)/forward;
167
0
        else {
168
            // Corrado and Miller extended moneyness approximation
169
0
            Real moneynessDelta = Integer(optionType) * (forward-strike);
170
0
            Real moneynessDelta_2 = moneynessDelta/2.0;
171
0
            Real temp = blackPrice/discount - moneynessDelta_2;
172
0
            Real moneynessDelta_PI = moneynessDelta*moneynessDelta/M_PI;
173
0
            Real temp2 = temp*temp-moneynessDelta_PI;
174
0
            if (temp2<0.0) // approximation breaks down, 2 alternatives:
175
                // 1. zero it
176
0
                temp2=0.0;
177
                // 2. Manaster-Koehler (1982) efficient Newton-Raphson seed
178
                //return std::fabs(std::log(forward/strike))*std::sqrt(2.0);
179
0
            temp2 = std::sqrt(temp2);
180
0
            temp += temp2;
181
0
            temp *= std::sqrt(2.0 * M_PI);
182
0
            stdDev = temp/(forward+strike);
183
0
        }
184
0
        QL_ENSURE(stdDev>=0.0,
185
0
                  "stdDev (" << stdDev << ") must be non-negative");
186
0
        return stdDev;
187
0
    }
188
189
    Real blackFormulaImpliedStdDevApproximation(
190
                      const ext::shared_ptr<PlainVanillaPayoff>& payoff,
191
                      Real forward,
192
                      Real blackPrice,
193
                      Real discount,
194
0
                      Real displacement) {
195
0
        return blackFormulaImpliedStdDevApproximation(payoff->optionType(),
196
0
            payoff->strike(), forward, blackPrice, discount, displacement);
197
0
    }
198
199
    Real blackFormulaImpliedStdDevChambers(Option::Type optionType,
200
                                                Real strike,
201
                                                Real forward,
202
                                                Real blackPrice,
203
                                                Real blackAtmPrice,
204
                                                Real discount,
205
0
                                                Real displacement) {
206
0
        checkParameters(strike, forward, displacement);
207
0
        QL_REQUIRE(blackPrice >= 0.0,
208
0
                   "blackPrice (" << blackPrice << ") must be non-negative");
209
0
        QL_REQUIRE(blackAtmPrice >= 0.0, "blackAtmPrice ("
210
0
                                             << blackAtmPrice
211
0
                                             << ") must be non-negative");
212
0
        QL_REQUIRE(discount > 0.0, "discount (" << discount
213
0
                                                << ") must be positive");
214
215
0
        Real stdDev;
216
217
0
        forward = forward + displacement;
218
0
        strike = strike + displacement;
219
0
        blackPrice /= discount;
220
0
        blackAtmPrice /= discount;
221
222
0
        Real s0 = M_SQRT2 * M_SQRTPI * blackAtmPrice /
223
0
                  forward; // Brenner-Subrahmanyam formula
224
0
        Real priceAtmVol =
225
0
            blackFormula(optionType, strike, forward, s0, 1.0, 0.0);
226
0
        Real dc = blackPrice - priceAtmVol;
227
228
0
        if (close(dc, 0.0)) {
229
0
            stdDev = s0;
230
0
        } else {
231
0
            Real d1 =
232
0
                blackFormulaStdDevDerivative(strike, forward, s0, 1.0, 0.0);
233
0
            Real d2 = blackFormulaStdDevSecondDerivative(strike, forward, s0,
234
0
                                                         1.0, 0.0);
235
0
            Real ds = 0.0;
236
0
            Real tmp = d1 * d1 + 2.0 * d2 * dc;
237
0
            if (std::fabs(d2) > 1E-10 && tmp >= 0.0)
238
0
                ds = (-d1 + std::sqrt(tmp)) / d2; // second order approximation
239
0
            else
240
0
                if(std::fabs(d1) > 1E-10)
241
0
                    ds = dc / d1; // first order approximation
242
0
            stdDev = s0 + ds;
243
0
        }
244
245
0
        QL_ENSURE(stdDev >= 0.0, "stdDev (" << stdDev
246
0
                                            << ") must be non-negative");
247
0
        return stdDev;
248
0
    }
249
250
    Real blackFormulaImpliedStdDevChambers(
251
        const ext::shared_ptr<PlainVanillaPayoff> &payoff,
252
        Real forward,
253
        Real blackPrice,
254
        Real blackAtmPrice,
255
        Real discount,
256
0
        Real displacement) {
257
0
        return blackFormulaImpliedStdDevChambers(
258
0
            payoff->optionType(), payoff->strike(), forward, blackPrice,
259
0
            blackAtmPrice, discount, displacement);
260
0
    }
261
262
    namespace {
263
0
        Real Af(Real x) {
264
0
            return 0.5*(1.0+boost::math::sign(x)
265
0
                *std::sqrt(1.0-std::exp(-M_2_PI*x*x)));
266
0
        }
267
    }
268
269
    Real blackFormulaImpliedStdDevApproximationRS(
270
        Option::Type type, Real K, Real F,
271
0
        Real marketValue, Real df, Real displacement) {
272
273
0
        checkParameters(K, F, displacement);
274
0
        QL_REQUIRE(marketValue >= 0.0,
275
0
                   "blackPrice (" << marketValue << ") must be non-negative");
276
0
        QL_REQUIRE(df > 0.0, "discount (" << df << ") must be positive");
277
278
0
        F = F + displacement;
279
0
        K = K + displacement;
280
281
0
        const Real ey = F/K;
282
0
        const Real ey2 = ey*ey;
283
0
        const Real y = std::log(ey);
284
0
        const Real alpha = marketValue/(K*df);
285
0
        const Real R = 2 * alpha + ((type == Option::Call) ? Real(-ey + 1.0) : ey - 1.0);
286
0
        const Real R2 = R*R;
287
288
0
        const Real a = std::exp((1.0-M_2_PI)*y);
289
0
        const Real A = squared(a - 1.0/a);
290
0
        const Real b = std::exp(M_2_PI*y);
291
0
        const Real B = 4.0*(b + 1/b)
292
0
            - 2*K/F*(a + 1.0/a)*(ey2 + 1 - R2);
293
0
        const Real C = (R2-squared(ey-1))*(squared(ey+1)-R2)/ey2;
294
295
0
        const Real beta = 2*C/(B+std::sqrt(B*B+4*A*C));
296
0
        const Real gamma = -M_PI_2*std::log(beta);
297
298
0
        if (y >= 0.0) {
299
0
            const Real M0 = K*df*(
300
0
                (type == Option::Call) ? Real(ey*Af(std::sqrt(2*y)) - 0.5)
301
0
                                       : 0.5-ey*Af(-std::sqrt(2*y)));
302
303
0
            if (marketValue <= M0)
304
0
                return std::sqrt(gamma+y)-std::sqrt(gamma-y);
305
0
            else
306
0
                return std::sqrt(gamma+y)+std::sqrt(gamma-y);
307
0
        }
308
0
        else {
309
0
            const Real M0 = K*df*(
310
0
                (type == Option::Call) ? Real(0.5*ey - Af(-std::sqrt(-2*y)))
311
0
                                       : Af(std::sqrt(-2*y)) - 0.5*ey);
312
313
0
            if (marketValue <= M0)
314
0
                return std::sqrt(gamma-y)-std::sqrt(gamma+y);
315
0
            else
316
0
                return std::sqrt(gamma+y)+std::sqrt(gamma-y);
317
0
        }
318
0
    }
319
320
    Real blackFormulaImpliedStdDevApproximationRS(
321
        const ext::shared_ptr<PlainVanillaPayoff> &payoff,
322
        Real F, Real marketValue,
323
0
        Real df, Real displacement) {
324
325
0
        return blackFormulaImpliedStdDevApproximationRS(
326
0
            payoff->optionType(), payoff->strike(),
327
0
            F, marketValue, df, displacement);
328
0
    }
329
330
    class BlackImpliedStdDevHelper {
331
      public:
332
        BlackImpliedStdDevHelper(Option::Type optionType,
333
                                 Real strike,
334
                                 Real forward,
335
                                 Real undiscountedBlackPrice,
336
                                 Real displacement = 0.0)
337
0
        : halfOptionType_(0.5 * Integer(optionType)),
338
0
          signedStrike_(Integer(optionType) * (strike+displacement)),
339
0
          signedForward_(Integer(optionType) * (forward+displacement)),
340
0
          undiscountedBlackPrice_(undiscountedBlackPrice)
341
0
        {
342
0
            checkParameters(strike, forward, displacement);
343
0
            QL_REQUIRE(undiscountedBlackPrice>=0.0,
344
0
                       "undiscounted Black price (" <<
345
0
                       undiscountedBlackPrice << ") must be non-negative");
346
0
            signedMoneyness_ = Integer(optionType) * std::log((forward+displacement)/(strike+displacement));
347
0
        }
348
349
0
        Real operator()(Real stdDev) const {
350
            #if defined(QL_EXTRA_SAFETY_CHECKS)
351
            QL_REQUIRE(stdDev>=0.0,
352
                       "stdDev (" << stdDev << ") must be non-negative");
353
            #endif
354
0
            if (stdDev==0.0)
355
0
                return std::max(signedForward_-signedStrike_, Real(0.0))
356
0
                                                   - undiscountedBlackPrice_;
357
0
            Real temp = halfOptionType_*stdDev;
358
0
            Real d = signedMoneyness_/stdDev;
359
0
            Real signedD1 = d + temp;
360
0
            Real signedD2 = d - temp;
361
0
            Real result = signedForward_ * N_(signedD1)
362
0
                - signedStrike_ * N_(signedD2);
363
            // numerical inaccuracies can yield a negative answer
364
0
            return std::max(Real(0.0), result) - undiscountedBlackPrice_;
365
0
        }
366
0
        Real derivative(Real stdDev) const {
367
            #if defined(QL_EXTRA_SAFETY_CHECKS)
368
            QL_REQUIRE(stdDev>=0.0,
369
                       "stdDev (" << stdDev << ") must be non-negative");
370
            #endif
371
0
            Real signedD1 = signedMoneyness_/stdDev + halfOptionType_*stdDev;
372
0
            return signedForward_*N_.derivative(signedD1);
373
0
        }
374
      private:
375
        Real halfOptionType_;
376
        Real signedStrike_, signedForward_;
377
        Real undiscountedBlackPrice_, signedMoneyness_;
378
        CumulativeNormalDistribution N_;
379
    };
380
381
382
    Real blackFormulaImpliedStdDev(Option::Type optionType,
383
                                   Real strike,
384
                                   Real forward,
385
                                   Real blackPrice,
386
                                   Real discount,
387
                                   Real displacement,
388
                                   Real guess,
389
                                   Real accuracy,
390
                                   Natural maxIterations)
391
0
    {
392
0
        checkParameters(strike, forward, displacement);
393
394
0
        QL_REQUIRE(discount>0.0,
395
0
                   "discount (" << discount << ") must be positive");
396
397
0
        QL_REQUIRE(blackPrice>=0.0,
398
0
                   "option price (" << blackPrice << ") must be non-negative");
399
        // check the price of the "other" option implied by put-call paity
400
0
        Real otherOptionPrice = blackPrice - Integer(optionType) * (forward-strike)*discount;
401
0
        QL_REQUIRE(otherOptionPrice>=0.0,
402
0
                   "negative " << Option::Type(-1*optionType) <<
403
0
                   " price (" << otherOptionPrice <<
404
0
                   ") implied by put-call parity. No solution exists for " <<
405
0
                   optionType << " strike " << strike <<
406
0
                   ", forward " << forward <<
407
0
                   ", price " << blackPrice <<
408
0
                   ", deflator " << discount);
409
410
        // solve for the out-of-the-money option which has
411
        // greater vega/price ratio, i.e.
412
        // it is numerically more robust for implied vol calculations
413
0
        if (optionType==Option::Put && strike>forward) {
414
0
            optionType = Option::Call;
415
0
            blackPrice = otherOptionPrice;
416
0
        }
417
0
        if (optionType==Option::Call && strike<forward) {
418
0
            optionType = Option::Put;
419
0
            blackPrice = otherOptionPrice;
420
0
        }
421
422
0
        strike = strike + displacement;
423
0
        forward = forward + displacement;
424
425
0
        if (guess==Null<Real>())
426
0
            guess = blackFormulaImpliedStdDevApproximation(
427
0
                optionType, strike, forward, blackPrice, discount, displacement);
428
0
        else
429
0
            QL_REQUIRE(guess>=0.0,
430
0
                       "stdDev guess (" << guess << ") must be non-negative");
431
0
        BlackImpliedStdDevHelper f(optionType, strike, forward,
432
0
                                   blackPrice/discount);
433
0
        NewtonSafe solver;
434
0
        solver.setMaxEvaluations(maxIterations);
435
0
        Real minSdtDev = 0.0, maxStdDev = 24.0; // 24 = 300% * sqrt(60)
436
0
        Real stdDev = solver.solve(f, accuracy, guess, minSdtDev, maxStdDev);
437
0
        QL_ENSURE(stdDev>=0.0,
438
0
                  "stdDev (" << stdDev << ") must be non-negative");
439
0
        return stdDev;
440
0
    }
441
442
    Real blackFormulaImpliedStdDev(
443
                        const ext::shared_ptr<PlainVanillaPayoff>& payoff,
444
                        Real forward,
445
                        Real blackPrice,
446
                        Real discount,
447
                        Real displacement,
448
                        Real guess,
449
                        Real accuracy,
450
0
                        Natural maxIterations) {
451
0
        return blackFormulaImpliedStdDev(payoff->optionType(), payoff->strike(),
452
0
            forward, blackPrice, discount, displacement, guess, accuracy, maxIterations);
453
0
    }
454
455
456
    namespace {
457
0
        Real Np(Real x, Real v) {
458
0
            return CumulativeNormalDistribution()(x/v + 0.5*v);
459
0
        }
460
0
        Real Nm(Real x, Real v) {
461
0
            return std::exp(-x)*CumulativeNormalDistribution()(x/v - 0.5*v);
462
0
        }
463
0
        Real phi(Real x, Real v) {
464
0
            const Real ax = 2*std::fabs(x);
465
0
            const Real v2 = v*v;
466
0
            return (v2-ax)/(v2+ax);
467
0
        }
468
0
        Real F(Real v, Real x, Real cs, Real w) {
469
0
            return cs+Nm(x,v)+w*Np(x,v);
470
0
        }
471
0
        Real G(Real v, Real x, Real cs, Real w) {
472
0
            const Real q = F(v,x,cs,w)/(1+w);
473
474
            // Acklam's inverse w/o Halley's refinement step
475
            // does not provide enough accuracy. But both together are
476
            // slower than the boost replacement.
477
0
            const Real k = MaddockInverseCumulativeNormal()(q);
478
479
0
            return k + std::sqrt(k*k + 2*std::fabs(x));
480
0
        }
481
    }
482
483
    Real blackFormulaImpliedStdDevLiRS(
484
        Option::Type optionType,
485
        Real strike,
486
        Real forward,
487
        Real blackPrice,
488
        Real discount,
489
        Real displacement,
490
        Real guess,
491
        Real w,
492
        Real accuracy,
493
0
        Natural maxIterations) {
494
495
0
        QL_REQUIRE(discount>0.0,
496
0
                   "discount (" << discount << ") must be positive");
497
498
0
        QL_REQUIRE(blackPrice>=0.0,
499
0
                   "option price (" << blackPrice << ") must be non-negative");
500
501
0
        strike = strike + displacement;
502
0
        forward = forward + displacement;
503
504
0
        if (guess == Null<Real>()) {
505
0
            guess = blackFormulaImpliedStdDevApproximationRS(
506
0
                optionType, strike, forward,
507
0
                blackPrice, discount, displacement);
508
0
        }
509
0
        else {
510
0
            QL_REQUIRE(guess>=0.0,
511
0
                "stdDev guess (" << guess << ") must be non-negative");
512
0
        }
513
514
0
        Real x = std::log(forward/strike);
515
0
        Real cs = (optionType == Option::Call)
516
0
            ? Real(blackPrice / (forward*discount))
517
0
            : (blackPrice/ (forward*discount) + 1.0 - strike/forward);
518
519
0
        QL_REQUIRE(cs >= 0.0, "normalized call price (" << cs
520
0
                   << ") must be positive");
521
522
0
        if (x > 0) {
523
            // use in-out duality
524
0
            cs = forward/strike*cs + 1.0 - forward/strike;
525
0
            QL_REQUIRE(cs >= 0.0, "negative option price from in-out duality");
526
0
            x = -x;
527
0
        }
528
529
0
        Size nIter = 0;
530
0
        Real dv, vk, vkp1 = guess;
531
532
0
        do {
533
0
            vk = vkp1;
534
0
            const Real alphaK = (1+w)/(1+phi(x,vk));
535
0
            vkp1 = alphaK*G(vk,x,cs,w) + (1-alphaK)*vk;
536
0
            dv = std::fabs(vkp1 - vk);
537
0
        } while (dv > accuracy && ++nIter < maxIterations);
538
539
0
        QL_REQUIRE(dv <= accuracy, "max iterations exceeded");
540
0
        QL_REQUIRE(vk >= 0.0, "stdDev (" << vk << ") must be non-negative");
541
542
0
        return vk;
543
0
    }
544
545
    Real blackFormulaImpliedStdDevLiRS(
546
        const ext::shared_ptr<PlainVanillaPayoff>& payoff,
547
        Real forward,
548
        Real blackPrice,
549
        Real discount,
550
        Real displacement,
551
        Real guess,
552
        Real omega,
553
        Real accuracy,
554
0
        Natural maxIterations) {
555
556
0
        return blackFormulaImpliedStdDevLiRS(
557
0
            payoff->optionType(), payoff->strike(),
558
0
            forward, blackPrice, discount, displacement,
559
0
            guess, omega, accuracy, maxIterations);
560
0
    }
561
562
563
    Real blackFormulaCashItmProbability(Option::Type optionType,
564
                                        Real strike,
565
                                        Real forward,
566
                                        Real stdDev,
567
0
                                        Real displacement) {
568
0
        checkParameters(strike, forward, displacement);
569
570
0
        auto sign = Integer(optionType);
571
572
0
        if (stdDev==0.0)
573
0
            return (forward * sign > strike * sign ? 1.0 : 0.0);
574
575
0
        forward = forward + displacement;
576
0
        strike = strike + displacement;
577
0
        if (strike==0.0)
578
0
            return (optionType == Option::Call ? 1.0 : 0.0);
579
0
        Real d2 = std::log(forward/strike)/stdDev - 0.5*stdDev;
580
0
        CumulativeNormalDistribution phi;
581
0
        return phi(sign * d2);
582
0
    }
583
584
    Real blackFormulaCashItmProbability(
585
                        const ext::shared_ptr<PlainVanillaPayoff>& payoff,
586
                        Real forward,
587
                        Real stdDev,
588
0
                        Real displacement) {
589
0
        return blackFormulaCashItmProbability(payoff->optionType(),
590
0
            payoff->strike(), forward, stdDev , displacement);
591
0
    }
592
593
    Real blackFormulaAssetItmProbability(
594
                        Option::Type optionType,
595
                        Real strike,
596
                        Real forward,
597
                        Real stdDev,
598
0
                        Real displacement) {
599
0
        checkParameters(strike, forward, displacement);
600
601
0
        auto sign = Integer(optionType);
602
603
0
        if (stdDev==0.0)
604
0
            return (forward * sign < strike * sign ? 1.0 : 0.0);
605
606
0
        forward = forward + displacement;
607
0
        strike = strike + displacement;
608
0
        if (strike == 0.0)
609
0
            return (optionType == Option::Call ? 1.0 : 0.0);
610
0
        Real d1 = std::log(forward/strike)/stdDev + 0.5*stdDev;
611
0
        CumulativeNormalDistribution phi;
612
0
        return phi(sign * d1);
613
0
    }
614
615
    Real blackFormulaAssetItmProbability(
616
                        const ext::shared_ptr<PlainVanillaPayoff>& payoff,
617
                        Real forward,
618
                        Real stdDev,
619
0
                        Real displacement) {
620
0
        return blackFormulaAssetItmProbability(payoff->optionType(),
621
0
            payoff->strike(), forward, stdDev , displacement);
622
0
    }
623
624
    Real blackFormulaVolDerivative(Rate strike,
625
                                      Rate forward,
626
                                      Real stdDev,
627
                                      Real expiry,
628
                                      Real discount,
629
                                      Real displacement)
630
0
    {
631
0
        return  blackFormulaStdDevDerivative(strike,
632
0
                                     forward,
633
0
                                     stdDev,
634
0
                                     discount,
635
0
                                     displacement)*std::sqrt(expiry);
636
0
    }
637
638
    Real blackFormulaStdDevDerivative(Rate strike,
639
                                      Rate forward,
640
                                      Real stdDev,
641
                                      Real discount,
642
                                      Real displacement)
643
0
    {
644
0
        checkParameters(strike, forward, displacement);
645
0
        QL_REQUIRE(stdDev>=0.0,
646
0
                   "stdDev (" << stdDev << ") must be non-negative");
647
0
        QL_REQUIRE(discount>0.0,
648
0
                   "discount (" << discount << ") must be positive");
649
650
0
        forward = forward + displacement;
651
0
        strike = strike + displacement;
652
653
0
        if (stdDev==0.0 || strike==0.0)
654
0
            return 0.0;
655
656
0
        Real d1 = std::log(forward/strike)/stdDev + .5*stdDev;
657
0
        return discount * forward *
658
0
            CumulativeNormalDistribution().derivative(d1);
659
0
    }
660
661
    Real blackFormulaStdDevDerivative(
662
                        const ext::shared_ptr<PlainVanillaPayoff>& payoff,
663
                        Real forward,
664
                        Real stdDev,
665
                        Real discount,
666
0
                        Real displacement) {
667
0
        return blackFormulaStdDevDerivative(payoff->strike(), forward,
668
0
                                     stdDev, discount, displacement);
669
0
    }
670
671
    Real blackFormulaStdDevSecondDerivative(Rate strike,
672
                                            Rate forward,
673
                                            Real stdDev,
674
                                            Real discount,
675
                                            Real displacement)
676
0
    {
677
0
        checkParameters(strike, forward, displacement);
678
0
        QL_REQUIRE(stdDev>=0.0,
679
0
                   "stdDev (" << stdDev << ") must be non-negative");
680
0
        QL_REQUIRE(discount>0.0,
681
0
                   "discount (" << discount << ") must be positive");
682
683
0
        forward = forward + displacement;
684
0
        strike = strike + displacement;
685
686
0
        if (stdDev==0.0 || strike==0.0)
687
0
            return 0.0;
688
689
0
        Real d1 = std::log(forward/strike)/stdDev + .5*stdDev;
690
0
        Real d1p = -std::log(forward/strike)/(stdDev*stdDev) + .5;
691
0
        return discount * forward *
692
0
            NormalDistribution().derivative(d1) * d1p;
693
0
    }
694
695
    Real blackFormulaStdDevSecondDerivative(
696
                        const ext::shared_ptr<PlainVanillaPayoff>& payoff,
697
                        Real forward,
698
                        Real stdDev,
699
                        Real discount,
700
0
                        Real displacement) {
701
0
        return blackFormulaStdDevSecondDerivative(payoff->strike(), forward,
702
0
                                     stdDev, discount, displacement);
703
0
    }
704
705
    Real bachelierBlackFormula(Option::Type optionType,
706
                               Real strike,
707
                               Real forward,
708
                               Real stdDev,
709
                               Real discount)
710
0
    {
711
0
        QL_REQUIRE(stdDev>=0.0,
712
0
                   "stdDev (" << stdDev << ") must be non-negative");
713
0
        QL_REQUIRE(discount>0.0,
714
0
                   "discount (" << discount << ") must be positive");
715
0
        Real d = (forward-strike) * Integer(optionType), h = d / stdDev;
716
0
        if (stdDev==0.0)
717
0
            return discount*std::max(d, 0.0);
718
0
        CumulativeNormalDistribution phi;
719
0
        Real result = discount*(stdDev*phi.derivative(h) + d*phi(h));
720
0
        QL_ENSURE(result>=0.0,
721
0
                  "negative value (" << result << ") for " <<
722
0
                  stdDev << " stdDev, " <<
723
0
                  optionType << " option, " <<
724
0
                  strike << " strike , " <<
725
0
                  forward << " forward");
726
0
        return result;
727
0
    }
728
729
    Real bachelierBlackFormula(
730
                        const ext::shared_ptr<PlainVanillaPayoff>& payoff,
731
                        Real forward,
732
                        Real stdDev,
733
0
                        Real discount) {
734
0
        return bachelierBlackFormula(payoff->optionType(),
735
0
            payoff->strike(), forward, stdDev, discount);
736
0
    }
737
738
    Real bachelierBlackFormulaForwardDerivative(
739
        Option::Type optionType, Real strike, Real forward, Real stdDev, Real discount)
740
0
    {
741
0
        QL_REQUIRE(stdDev>=0.0,
742
0
                   "stdDev (" << stdDev << ") must be non-negative");
743
0
        QL_REQUIRE(discount>0.0,
744
0
                   "discount (" << discount << ") must be positive");
745
0
        auto sign = Integer(optionType);
746
0
        if (stdDev == 0.0)
747
0
            return sign * std::max(1.0 * boost::math::sign((forward - strike) * sign), 0.0) * discount;
748
0
        Real d = (forward - strike) * sign, h = d / stdDev;
749
0
        CumulativeNormalDistribution phi;
750
0
        return sign * phi(h) * discount;
751
0
    }
752
753
    Real bachelierBlackFormulaForwardDerivative(
754
        const ext::shared_ptr<PlainVanillaPayoff>& payoff,
755
        Real forward,
756
        Real stdDev,
757
        Real discount)
758
0
    {
759
0
        return bachelierBlackFormulaForwardDerivative(payoff->optionType(),
760
0
            payoff->strike(), forward, stdDev, discount);
761
0
    }
762
763
0
    static Real h(Real eta) {
764
765
0
        const static Real  A0          = 3.994961687345134e-1;
766
0
        const static Real  A1          = 2.100960795068497e+1;
767
0
        const static Real  A2          = 4.980340217855084e+1;
768
0
        const static Real  A3          = 5.988761102690991e+2;
769
0
        const static Real  A4          = 1.848489695437094e+3;
770
0
        const static Real  A5          = 6.106322407867059e+3;
771
0
        const static Real  A6          = 2.493415285349361e+4;
772
0
        const static Real  A7          = 1.266458051348246e+4;
773
774
0
        const static Real  B0          = 1.000000000000000e+0;
775
0
        const static Real  B1          = 4.990534153589422e+1;
776
0
        const static Real  B2          = 3.093573936743112e+1;
777
0
        const static Real  B3          = 1.495105008310999e+3;
778
0
        const static Real  B4          = 1.323614537899738e+3;
779
0
        const static Real  B5          = 1.598919697679745e+4;
780
0
        const static Real  B6          = 2.392008891720782e+4;
781
0
        const static Real  B7          = 3.608817108375034e+3;
782
0
        const static Real  B8          = -2.067719486400926e+2;
783
0
        const static Real  B9          = 1.174240599306013e+1;
784
785
0
        QL_REQUIRE(eta>=0.0,
786
0
                       "eta (" << eta << ") must be non-negative");
787
788
0
        const Real num = A0 + eta * (A1 + eta * (A2 + eta * (A3 + eta * (A4 + eta
789
0
                    * (A5 + eta * (A6 + eta * A7))))));
790
791
0
        const Real den = B0 + eta * (B1 + eta * (B2 + eta * (B3 + eta * (B4 + eta
792
0
                    * (B5 + eta * (B6 + eta * (B7 + eta * (B8 + eta * B9))))))));
793
794
0
        return std::sqrt(eta) * (num / den);
795
796
0
    }
797
798
    Real bachelierBlackFormulaImpliedVolChoi(Option::Type optionType,
799
                                             Real strike,
800
                                             Real forward,
801
                                             Real tte,
802
                                             Real bachelierPrice,
803
0
                                             Real discount) {
804
805
0
        const static Real SQRT_QL_EPSILON = std::sqrt(QL_EPSILON);
806
807
0
        QL_REQUIRE(tte>0.0,
808
0
                   "tte (" << tte << ") must be positive");
809
810
0
        Real forwardPremium = bachelierPrice/discount;
811
812
0
        Real straddlePremium;
813
0
        if (optionType==Option::Call){
814
0
            straddlePremium = 2.0 * forwardPremium - (forward - strike);
815
0
        } else {
816
0
            straddlePremium = 2.0 * forwardPremium + (forward - strike);
817
0
        }
818
819
0
        Real nu = (forward - strike) / straddlePremium;
820
0
        QL_REQUIRE(nu<1.0 || close_enough(nu,1.0),
821
0
                   "nu (" << nu << ") must be <= 1.0");
822
0
        QL_REQUIRE(nu>-1.0 || close_enough(nu,-1.0),
823
0
                     "nu (" << nu << ") must be >= -1.0");
824
825
0
        nu = std::max(-1.0 + QL_EPSILON, std::min(nu,1.0 - QL_EPSILON));
826
827
        // nu / arctanh(nu) -> 1 as nu -> 0
828
0
        Real eta = (std::fabs(nu) < SQRT_QL_EPSILON) ? 1.0 : Real(nu / boost::math::atanh(nu));
829
830
0
        Real heta = h(eta);
831
832
0
        Real impliedBpvol = std::sqrt(M_PI / (2 * tte)) * straddlePremium * heta;
833
834
0
        return impliedBpvol;
835
0
    }
836
837
    namespace {
838
839
        boost::math::normal_distribution<Real> normal_dist;
840
841
0
        Real phi(const Real x) {
842
0
            return boost::math::pdf(normal_dist, x);
843
0
        }
844
845
0
        Real Phi(const Real x) {
846
0
            return boost::math::cdf(normal_dist, x);
847
0
        }
848
849
0
        Real PhiTilde(const Real x) {
850
0
            return Phi(x) + phi(x) / x;
851
0
        }
852
853
0
        Real inversePhiTilde(const Real PhiTildeStar) {
854
0
            QL_REQUIRE(PhiTildeStar < 0.0,
855
0
                       "inversePhiTilde(" << PhiTildeStar << "): negative argument required");
856
0
            Real xbar;
857
0
            if (PhiTildeStar < -0.001882039271) {
858
0
                Real g = 1.0 / (PhiTildeStar - 0.5);
859
0
                Real xibar =
860
0
                    (0.032114372355 -
861
0
                     g * g *
862
0
                         (0.016969777977 - g * g * (2.6207332461E-3 - 9.6066952861E-5 * g * g))) /
863
0
                    (1.0 -
864
0
                     g * g * (0.6635646938 - g * g * (0.14528712196 - 0.010472855461 * g * g)));
865
0
                xbar = g * (0.3989422804014326 + xibar * g * g);
866
0
            } else {
867
0
                Real h = std::sqrt(-std::log(-PhiTildeStar));
868
0
                xbar =
869
0
                    (9.4883409779 - h * (9.6320903635 - h * (0.58556997323 + 2.1464093351 * h))) /
870
0
                    (1.0 - h * (0.65174820867 + h * (1.5120247828 + 6.6437847132E-5 * h)));
871
0
            }
872
0
            Real q = (PhiTilde(xbar) - PhiTildeStar) / phi(xbar);
873
0
            Real xstar =
874
0
                xbar +
875
0
                3.0 * q * xbar * xbar * (2.0 - q * xbar * (2.0 + xbar * xbar)) /
876
0
                    (6.0 + q * xbar *
877
0
                               (-12.0 +
878
0
                                xbar * (6.0 * q + xbar * (-6.0 + q * xbar * (3.0 + xbar * xbar)))));
879
0
            return xstar;
880
0
        }
881
882
    } // namespace
883
884
    Real bachelierBlackFormulaImpliedVol(Option::Type optionType,
885
                                         Real strike,
886
                                         Real forward,
887
                                         Real tte,
888
                                         Real bachelierPrice,
889
0
                                         Real discount) {
890
891
0
        Real theta = optionType == Option::Call ? 1.0 : -1.0;
892
893
        // compound bechelierPrice, so that effectively discount = 1
894
895
0
        bachelierPrice /= discount;
896
897
        // handle case strike = forward
898
899
0
        if (close_enough(strike, forward)) {
900
0
            return bachelierPrice / (std::sqrt(tte) * phi(0.0));
901
0
        }
902
903
        // handle case strike != forward
904
905
0
        Real timeValue = bachelierPrice - std::max(theta * (forward - strike), 0.0);
906
907
0
        if (close_enough(timeValue, 0.0))
908
0
            return 0.0;
909
910
0
        QL_REQUIRE(timeValue > 0.0, "bachelierBlackFormulaImpliedVolExact(theta="
911
0
                                        << theta << ",strike=" << strike << ",forward=" << forward
912
0
                                        << ",tte=" << tte << ",price=" << bachelierPrice
913
0
                                        << "): option price implies negative time value ("
914
0
                                        << timeValue << ")");
915
916
0
        Real PhiTildeStar = -std::abs(timeValue / (strike - forward));
917
0
        Real xstar = inversePhiTilde(PhiTildeStar);
918
0
        Real impliedVol = std::abs((strike - forward) / (xstar * std::sqrt(tte)));
919
920
0
        return impliedVol;
921
0
    }
922
923
    Real bachelierBlackFormulaStdDevDerivative(Rate strike,
924
                                      Rate forward,
925
                                      Real stdDev,
926
                                      Real discount)
927
0
    {
928
0
        QL_REQUIRE(stdDev>=0.0,
929
0
                   "stdDev (" << stdDev << ") must be non-negative");
930
0
        QL_REQUIRE(discount>0.0,
931
0
                   "discount (" << discount << ") must be positive");
932
933
0
        if (stdDev==0.0)
934
0
            return 0.0;
935
936
0
        Real d1 = (forward - strike)/stdDev;
937
0
        return discount *
938
0
            CumulativeNormalDistribution().derivative(d1);
939
0
    }
940
941
    Real bachelierBlackFormulaStdDevDerivative(
942
                        const ext::shared_ptr<PlainVanillaPayoff>& payoff,
943
                        Real forward,
944
                        Real stdDev,
945
0
                        Real discount) {
946
0
        return bachelierBlackFormulaStdDevDerivative(payoff->strike(), forward,
947
0
                                     stdDev, discount);
948
0
    }
949
950
    Real bachelierBlackFormulaAssetItmProbability(
951
                        Option::Type optionType,
952
                        Real strike,
953
                        Real forward,
954
0
                        Real stdDev) {
955
0
        QL_REQUIRE(stdDev>=0.0,
956
0
                   "stdDev (" << stdDev << ") must be non-negative");
957
0
        Real d = (forward - strike) * Integer(optionType), h = d / stdDev;
958
0
        if (stdDev==0.0)
959
0
            return std::max(d, 0.0);
960
0
        CumulativeNormalDistribution phi;
961
0
        Real result = phi(h);
962
0
        return result;
963
0
    }
964
965
    Real bachelierBlackFormulaAssetItmProbability(
966
                        const ext::shared_ptr<PlainVanillaPayoff>& payoff,
967
                        Real forward,
968
0
                        Real stdDev) {
969
0
        return bachelierBlackFormulaAssetItmProbability(payoff->optionType(),
970
0
            payoff->strike(), forward, stdDev);
971
0
    }
972
}