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>
44 QL_REQUIRE(displacement >= 0.0,
"displacement ("
46 <<
") must be non-negative");
48 "strike + displacement (" << strike <<
" + " << displacement
49 <<
") must be non-negative");
50 QL_REQUIRE(forward + displacement > 0.0,
"forward + displacement ("
53 <<
") must be positive");
66 checkParameters(strike, forward, displacement);
68 "stdDev (" << stdDev <<
") must be non-negative");
70 "discount (" << discount <<
") must be positive");
72 auto sign =
Integer(optionType);
75 return std::max((forward-strike) * sign,
Real(0.0)) * discount;
77 forward = forward + displacement;
78 strike = strike + displacement;
85 Real d1 = std::log(forward/strike)/stdDev + 0.5*stdDev;
86 Real d2 = d1 - stdDev;
88 Real nd1 = phi(sign * d1);
89 Real nd2 = phi(sign * d2);
90 Real result = discount * sign * (forward*nd1 - strike*nd2);
92 "negative value (" << result <<
") for " <<
93 stdDev <<
" stdDev, " <<
94 optionType <<
" option, " <<
95 strike <<
" strike , " <<
96 forward <<
" forward");
106 payoff->strike(), forward, stdDev, discount, displacement);
116 checkParameters(strike, forward, displacement);
118 "stdDev (" << stdDev <<
") must be non-negative");
120 "discount (" << discount <<
") must be positive");
122 auto sign =
Integer(optionType);
125 return sign * std::max(1.0 * boost::math::sign((forward - strike) * sign), 0.0) * discount;
127 forward = forward + displacement;
128 strike = strike + displacement;
133 Real d1 = std::log(forward/strike)/stdDev + 0.5*stdDev;
135 return sign * phi(sign * d1) * discount;
145 payoff->strike(), forward, stdDev, discount, displacement);
155 checkParameters(strike, forward, displacement);
157 "blackPrice (" << blackPrice <<
") must be non-negative");
159 "discount (" << discount <<
") must be positive");
162 forward = forward + displacement;
163 strike = strike + displacement;
166 stdDev = blackPrice/discount*std::sqrt(2.0 *
M_PI)/forward;
169 Real moneynessDelta =
Integer(optionType) * (forward-strike);
170 Real moneynessDelta_2 = moneynessDelta/2.0;
171 Real temp = blackPrice/discount - moneynessDelta_2;
172 Real moneynessDelta_PI = moneynessDelta*moneynessDelta/
M_PI;
173 Real temp2 = temp*temp-moneynessDelta_PI;
179 temp2 = std::sqrt(temp2);
181 temp *= std::sqrt(2.0 *
M_PI);
182 stdDev = temp/(forward+strike);
185 "stdDev (" << stdDev <<
") must be non-negative");
190 const ext::shared_ptr<PlainVanillaPayoff>&
payoff,
196 payoff->strike(), forward, blackPrice, discount, displacement);
206 checkParameters(strike, forward, displacement);
208 "blackPrice (" << blackPrice <<
") must be non-negative");
209 QL_REQUIRE(blackAtmPrice >= 0.0,
"blackAtmPrice ("
211 <<
") must be non-negative");
212 QL_REQUIRE(discount > 0.0,
"discount (" << discount
213 <<
") must be positive");
217 forward = forward + displacement;
218 strike = strike + displacement;
219 blackPrice /= discount;
220 blackAtmPrice /= discount;
225 blackFormula(optionType, strike, forward, s0, 1.0, 0.0);
226 Real dc = blackPrice - priceAtmVol;
228 if (
close(dc, 0.0)) {
236 Real tmp = d1 * d1 + 2.0 * d2 * dc;
237 if (std::fabs(d2) > 1E-10 && tmp >= 0.0)
238 ds = (-d1 + std::sqrt(tmp)) / d2;
240 if(std::fabs(d1) > 1E-10)
245 QL_ENSURE(stdDev >= 0.0,
"stdDev (" << stdDev
246 <<
") must be non-negative");
251 const ext::shared_ptr<PlainVanillaPayoff> &
payoff,
258 payoff->optionType(),
payoff->strike(), forward, blackPrice,
259 blackAtmPrice, discount, displacement);
264 return 0.5*(1.0+boost::math::sign(x)
265 *std::sqrt(1.0-std::exp(-
M_2_PI*x*x)));
273 checkParameters(K,
F, displacement);
275 "blackPrice (" << marketValue <<
") must be non-negative");
276 QL_REQUIRE(df > 0.0,
"discount (" << df <<
") must be positive");
278 F =
F + displacement;
279 K = K + displacement;
282 const Real ey2 = ey*ey;
283 const Real y = std::log(ey);
291 const Real B = 4.0*(
b + 1/
b)
292 - 2*K/
F*(a + 1.0/a)*(ey2 + 1 - R2);
295 const Real beta = 2*C/(B+std::sqrt(B*B+4*A*C));
299 const Real M0 = K*df*(
301 : 0.5-ey*Af(-std::sqrt(2*
y)));
303 if (marketValue <= M0)
304 return std::sqrt(gamma+
y)-std::sqrt(gamma-
y);
306 return std::sqrt(gamma+
y)+std::sqrt(gamma-
y);
309 const Real M0 = K*df*(
311 : Af(std::sqrt(-2*
y)) - 0.5*ey);
313 if (marketValue <= M0)
314 return std::sqrt(gamma-
y)-std::sqrt(gamma+
y);
316 return std::sqrt(gamma+
y)+std::sqrt(gamma-
y);
321 const ext::shared_ptr<PlainVanillaPayoff> &
payoff,
327 F, marketValue, df, displacement);
330 class BlackImpliedStdDevHelper {
335 Real undiscountedBlackPrice,
336 Real displacement = 0.0)
337 : halfOptionType_(0.5 *
Integer(optionType)),
338 signedStrike_(
Integer(optionType) * (strike+displacement)),
339 signedForward_(
Integer(optionType) * (forward+displacement)),
340 undiscountedBlackPrice_(undiscountedBlackPrice)
342 checkParameters(strike, forward, displacement);
344 "undiscounted Black price (" <<
345 undiscountedBlackPrice <<
") must be non-negative");
346 signedMoneyness_ =
Integer(optionType) * std::log((forward+displacement)/(strike+displacement));
349 Real operator()(
Real stdDev)
const {
350 #if defined(QL_EXTRA_SAFETY_CHECKS)
352 "stdDev (" << stdDev <<
") must be non-negative");
355 return std::max(signedForward_-signedStrike_,
Real(0.0))
356 - undiscountedBlackPrice_;
357 Real temp = halfOptionType_*stdDev;
358 Real d = signedMoneyness_/stdDev;
359 Real signedD1 =
d + temp;
360 Real signedD2 =
d - temp;
361 Real result = signedForward_ * N_(signedD1)
362 - signedStrike_ * N_(signedD2);
364 return std::max(
Real(0.0), result) - undiscountedBlackPrice_;
366 Real derivative(
Real stdDev)
const {
367 #if defined(QL_EXTRA_SAFETY_CHECKS)
369 "stdDev (" << stdDev <<
") must be non-negative");
371 Real signedD1 = signedMoneyness_/stdDev + halfOptionType_*stdDev;
372 return signedForward_*N_.derivative(signedD1);
375 Real halfOptionType_;
376 Real signedStrike_, signedForward_;
377 Real undiscountedBlackPrice_, signedMoneyness_;
378 CumulativeNormalDistribution N_;
392 checkParameters(strike, forward, displacement);
395 "discount (" << discount <<
") must be positive");
398 "option price (" << blackPrice <<
") must be non-negative");
400 Real otherOptionPrice = blackPrice -
Integer(optionType) * (forward-strike)*discount;
403 " price (" << otherOptionPrice <<
404 ") implied by put-call parity. No solution exists for " <<
405 optionType <<
" strike " << strike <<
406 ", forward " << forward <<
407 ", price " << blackPrice <<
408 ", deflator " << discount);
415 blackPrice = otherOptionPrice;
419 blackPrice = otherOptionPrice;
422 strike = strike + displacement;
423 forward = forward + displacement;
427 optionType, strike, forward, blackPrice, discount, displacement);
430 "stdDev guess (" << guess <<
") must be non-negative");
431 BlackImpliedStdDevHelper
f(optionType, strike, forward,
432 blackPrice/discount);
435 Real minSdtDev = 0.0, maxStdDev = 24.0;
436 Real stdDev = solver.
solve(
f, accuracy, guess, minSdtDev, maxStdDev);
438 "stdDev (" << stdDev <<
") must be non-negative");
443 const ext::shared_ptr<PlainVanillaPayoff>&
payoff,
452 forward, blackPrice, discount, displacement, guess, accuracy, maxIterations);
458 return CumulativeNormalDistribution()(x/
v + 0.5*
v);
461 return std::exp(-x)*CumulativeNormalDistribution()(x/
v - 0.5*
v);
464 const Real ax = 2*std::fabs(x);
466 return (v2-ax)/(v2+ax);
469 return cs+Nm(x,
v)+w*Np(x,
v);
472 const Real q =
F(
v,x,cs,w)/(1+w);
477 const Real k = MaddockInverseCumulativeNormal()(
q);
479 return k + std::sqrt(k*k + 2*std::fabs(x));
496 "discount (" << discount <<
") must be positive");
499 "option price (" << blackPrice <<
") must be non-negative");
501 strike = strike + displacement;
502 forward = forward + displacement;
506 optionType, strike, forward,
507 blackPrice, discount, displacement);
511 "stdDev guess (" << guess <<
") must be non-negative");
514 Real x = std::log(forward/strike);
516 ?
Real(blackPrice / (forward*discount))
517 : (blackPrice/ (forward*discount) + 1.0 - strike/forward);
519 QL_REQUIRE(cs >= 0.0,
"normalized call price (" << cs
520 <<
") must be positive");
524 cs = forward/strike*cs + 1.0 - forward/strike;
525 QL_REQUIRE(cs >= 0.0,
"negative option price from in-out duality");
530 Real dv, vk, vkp1 = guess;
534 const Real alphaK = (1+w)/(1+phi(x,vk));
535 vkp1 = alphaK*G(vk,x,cs,w) + (1-alphaK)*vk;
536 dv = std::fabs(vkp1 - vk);
537 }
while (dv > accuracy && ++nIter < maxIterations);
539 QL_REQUIRE(dv <= accuracy,
"max iterations exceeded");
540 QL_REQUIRE(vk >= 0.0,
"stdDev (" << vk <<
") must be non-negative");
546 const ext::shared_ptr<PlainVanillaPayoff>&
payoff,
558 forward, blackPrice, discount, displacement,
559 guess, omega, accuracy, maxIterations);
568 checkParameters(strike, forward, displacement);
570 auto sign =
Integer(optionType);
573 return (forward * sign > strike * sign ? 1.0 : 0.0);
575 forward = forward + displacement;
576 strike = strike + displacement;
579 Real d2 = std::log(forward/strike)/stdDev - 0.5*stdDev;
581 return phi(sign * d2);
585 const ext::shared_ptr<PlainVanillaPayoff>&
payoff,
590 payoff->strike(), forward, stdDev , displacement);
599 checkParameters(strike, forward, displacement);
601 auto sign =
Integer(optionType);
604 return (forward * sign < strike * sign ? 1.0 : 0.0);
606 forward = forward + displacement;
607 strike = strike + displacement;
610 Real d1 = std::log(forward/strike)/stdDev + 0.5*stdDev;
612 return phi(sign * d1);
616 const ext::shared_ptr<PlainVanillaPayoff>&
payoff,
621 payoff->strike(), forward, stdDev , displacement);
635 displacement)*std::sqrt(expiry);
644 checkParameters(strike, forward, displacement);
646 "stdDev (" << stdDev <<
") must be non-negative");
648 "discount (" << discount <<
") must be positive");
650 forward = forward + displacement;
651 strike = strike + displacement;
653 if (stdDev==0.0 || strike==0.0)
656 Real d1 = std::log(forward/strike)/stdDev + .5*stdDev;
657 return discount * forward *
662 const ext::shared_ptr<PlainVanillaPayoff>&
payoff,
668 stdDev, discount, displacement);
677 checkParameters(strike, forward, displacement);
679 "stdDev (" << stdDev <<
") must be non-negative");
681 "discount (" << discount <<
") must be positive");
683 forward = forward + displacement;
684 strike = strike + displacement;
686 if (stdDev==0.0 || strike==0.0)
689 Real d1 = std::log(forward/strike)/stdDev + .5*stdDev;
690 Real d1p = -std::log(forward/strike)/(stdDev*stdDev) + .5;
691 return discount * forward *
696 const ext::shared_ptr<PlainVanillaPayoff>&
payoff,
702 stdDev, discount, displacement);
712 "stdDev (" << stdDev <<
") must be non-negative");
714 "discount (" << discount <<
") must be positive");
715 Real d = (forward-strike) *
Integer(optionType), h =
d / stdDev;
717 return discount*std::max(
d, 0.0);
721 "negative value (" << result <<
") for " <<
722 stdDev <<
" stdDev, " <<
723 optionType <<
" option, " <<
724 strike <<
" strike , " <<
725 forward <<
" forward");
730 const ext::shared_ptr<PlainVanillaPayoff>&
payoff,
735 payoff->strike(), forward, stdDev, discount);
742 "stdDev (" << stdDev <<
") must be non-negative");
744 "discount (" << discount <<
") must be positive");
745 auto sign =
Integer(optionType);
747 return sign * std::max(1.0 * boost::math::sign((forward - strike) * sign), 0.0) * discount;
748 Real d = (forward - strike) * sign, h =
d / stdDev;
750 return sign * phi(h) * discount;
754 const ext::shared_ptr<PlainVanillaPayoff>&
payoff,
760 payoff->strike(), forward, stdDev, discount);
765 const static Real A0 = 3.994961687345134e-1;
766 const static Real A1 = 2.100960795068497e+1;
767 const static Real A2 = 4.980340217855084e+1;
768 const static Real A3 = 5.988761102690991e+2;
769 const static Real A4 = 1.848489695437094e+3;
770 const static Real A5 = 6.106322407867059e+3;
771 const static Real A6 = 2.493415285349361e+4;
772 const static Real A7 = 1.266458051348246e+4;
774 const static Real B0 = 1.000000000000000e+0;
775 const static Real B1 = 4.990534153589422e+1;
776 const static Real B2 = 3.093573936743112e+1;
777 const static Real B3 = 1.495105008310999e+3;
778 const static Real B4 = 1.323614537899738e+3;
779 const static Real B5 = 1.598919697679745e+4;
780 const static Real B6 = 2.392008891720782e+4;
781 const static Real B7 = 3.608817108375034e+3;
782 const static Real B8 = -2.067719486400926e+2;
783 const static Real B9 = 1.174240599306013e+1;
786 "eta (" << eta <<
") must be non-negative");
788 const Real num = A0 + eta * (A1 + eta * (A2 + eta * (A3 + eta * (A4 + eta
789 * (A5 + eta * (A6 + eta * A7))))));
791 const Real den = B0 + eta * (B1 + eta * (B2 + eta * (B3 + eta * (B4 + eta
792 * (B5 + eta * (B6 + eta * (B7 + eta * (B8 + eta * B9))))))));
794 return std::sqrt(eta) * (num / den);
808 "tte (" << tte <<
") must be positive");
810 Real forwardPremium = bachelierPrice/discount;
812 Real straddlePremium;
814 straddlePremium = 2.0 * forwardPremium - (forward - strike);
816 straddlePremium = 2.0 * forwardPremium + (forward - strike);
819 Real nu = (forward - strike) / straddlePremium;
821 "nu (" <<
nu <<
") must be <= 1.0");
823 "nu (" <<
nu <<
") must be >= -1.0");
828 Real eta = (std::fabs(
nu) < SQRT_QL_EPSILON) ? 1.0 :
Real(
nu / boost::math::atanh(
nu));
832 Real impliedBpvol = std::sqrt(
M_PI / (2 * tte)) * straddlePremium * heta;
839 boost::math::normal_distribution<Real> normal_dist;
842 return boost::math::pdf(normal_dist, x);
846 return boost::math::cdf(normal_dist, x);
850 return Phi(x) + phi(x) / x;
853 Real inversePhiTilde(
const Real PhiTildeStar) {
855 "inversePhiTilde(" << PhiTildeStar <<
"): negative argument required");
857 if (PhiTildeStar < -0.001882039271) {
858 Real g = 1.0 / (PhiTildeStar - 0.5);
862 (0.016969777977 -
g *
g * (2.6207332461E-3 - 9.6066952861E-5 *
g *
g))) /
864 g * g * (0.6635646938 - g * g * (0.14528712196 - 0.010472855461 * g * g)));
865 xbar =
g * (0.3989422804014326 + xibar *
g *
g);
867 Real h = std::sqrt(-std::log(-PhiTildeStar));
869 (9.4883409779 - h * (9.6320903635 - h * (0.58556997323 + 2.1464093351 * h))) /
870 (1.0 - h * (0.65174820867 + h * (1.5120247828 + 6.6437847132E-5 * h)));
872 Real q = (PhiTilde(xbar) - PhiTildeStar) / phi(xbar);
875 3.0 *
q * xbar * xbar * (2.0 -
q * xbar * (2.0 + xbar * xbar)) /
878 xbar * (6.0 *
q + xbar * (-6.0 +
q * xbar * (3.0 + xbar * xbar)))));
895 bachelierPrice /= discount;
900 return bachelierPrice / (std::sqrt(tte) * phi(0.0));
905 Real timeValue = bachelierPrice - std::max(
theta * (forward - strike), 0.0);
910 QL_REQUIRE(timeValue > 0.0,
"bachelierBlackFormulaImpliedVolExact(theta="
911 <<
theta <<
",strike=" << strike <<
",forward=" << forward
912 <<
",tte=" << tte <<
",price=" << bachelierPrice
913 <<
"): option price implies negative time value ("
914 << timeValue <<
")");
916 Real PhiTildeStar = -std::abs(timeValue / (strike - forward));
917 Real xstar = inversePhiTilde(PhiTildeStar);
918 Real impliedVol = std::abs((strike - forward) / (xstar * std::sqrt(tte)));
929 "stdDev (" << stdDev <<
") must be non-negative");
931 "discount (" << discount <<
") must be positive");
936 Real d1 = (forward - strike)/stdDev;
942 const ext::shared_ptr<PlainVanillaPayoff>&
payoff,
956 "stdDev (" << stdDev <<
") must be non-negative");
957 Real d = (forward - strike) *
Integer(optionType), h =
d / stdDev;
959 return std::max(
d, 0.0);
961 Real result = phi(h);
966 const ext::shared_ptr<PlainVanillaPayoff>&
payoff,
970 payoff->strike(), forward, stdDev);
Cumulative normal distribution function.
Real derivative(Real x) const
Normal distribution function.
Real derivative(Real x) const
template class providing a null value for a given type.
void setMaxEvaluations(Size evaluations)
Real solve(const F &f, Real accuracy, Real guess, Real step) const
#define QL_ENSURE(condition, message)
throw an error if the given post-condition is not verified
#define QL_REQUIRE(condition, message)
throw an error if the given pre-condition is not verified
std::function< Real(Real)> b
unsigned QL_INTEGER Natural
positive integer
QL_INTEGER Integer
integer number
std::size_t Size
size of a container
ext::shared_ptr< QuantLib::Payoff > payoff
functionals and combinators not included in the STL
Real blackFormulaAssetItmProbability(Option::Type optionType, Real strike, Real forward, Real stdDev, Real displacement)
Real blackFormulaCashItmProbability(Option::Type optionType, Real strike, Real forward, Real stdDev, Real displacement)
Real blackFormulaImpliedStdDevLiRS(Option::Type optionType, Real strike, Real forward, Real blackPrice, Real discount, Real displacement, Real guess, Real w, Real accuracy, Natural maxIterations)
Real blackFormulaImpliedStdDevChambers(Option::Type optionType, Real strike, Real forward, Real blackPrice, Real blackAtmPrice, Real discount, Real displacement)
Real bachelierBlackFormulaAssetItmProbability(Option::Type optionType, Real strike, Real forward, Real stdDev)
Real bachelierBlackFormulaStdDevDerivative(Rate strike, Rate forward, Real stdDev, Real discount)
Real bachelierBlackFormula(Option::Type optionType, Real strike, Real forward, Real stdDev, Real discount)
Real blackFormulaImpliedStdDev(Option::Type optionType, Real strike, Real forward, Real blackPrice, Real discount, Real displacement, Real guess, Real accuracy, Natural maxIterations)
bool close(const Quantity &m1, const Quantity &m2, Size n)
Real blackFormulaForwardDerivative(Option::Type optionType, Real strike, Real forward, Real stdDev, Real discount, Real displacement)
Real bachelierBlackFormulaForwardDerivative(Option::Type optionType, Real strike, Real forward, Real stdDev, Real discount)
Real blackFormulaVolDerivative(Rate strike, Rate forward, Real stdDev, Real expiry, Real discount, Real displacement)
Real blackFormula(Option::Type optionType, Real strike, Real forward, Real stdDev, Real discount, Real displacement)
bool close_enough(const Quantity &m1, const Quantity &m2, Size n)
Real bachelierBlackFormulaImpliedVol(Option::Type optionType, Real strike, Real forward, Real tte, Real bachelierPrice, Real discount)
Real bachelierBlackFormulaImpliedVolChoi(Option::Type optionType, Real strike, Real forward, Real tte, Real bachelierPrice, Real discount)
Real blackFormulaStdDevSecondDerivative(Rate strike, Rate forward, Real stdDev, Real discount, Real displacement)
Real blackFormulaImpliedStdDevApproximation(Option::Type optionType, Real strike, Real forward, Real blackPrice, Real discount, Real displacement)
Real blackFormulaImpliedStdDevApproximationRS(Option::Type type, Real K, Real F, Real marketValue, Real df, Real displacement)
Real blackFormulaStdDevDerivative(Rate strike, Rate forward, Real stdDev, Real discount, Real displacement)
Safe (bracketed) Newton 1-D solver.
normal, cumulative and inverse cumulative distributions
ext::shared_ptr< YieldTermStructure > q
ext::shared_ptr< BlackVolTermStructure > v