/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 | | } |