diff --git a/QuantLib.vcxproj b/QuantLib.vcxproj
index 3ac7544e02c..81efc70968c 100644
--- a/QuantLib.vcxproj
+++ b/QuantLib.vcxproj
@@ -1011,6 +1011,7 @@
+
@@ -1019,6 +1020,7 @@
+
@@ -2260,6 +2262,7 @@
+
diff --git a/QuantLib.vcxproj.filters b/QuantLib.vcxproj.filters
index cba8a6501da..2a38c4cbf63 100644
--- a/QuantLib.vcxproj.filters
+++ b/QuantLib.vcxproj.filters
@@ -987,6 +987,9 @@
math
+
+ math
+
math
@@ -1164,6 +1167,9 @@
math\integrals
+
+ math\integrals
+
math\integrals
@@ -4850,6 +4856,9 @@
math
+
+ math
+
math
diff --git a/ql/CMakeLists.txt b/ql/CMakeLists.txt
index da79a0b8790..9e578e0d265 100644
--- a/ql/CMakeLists.txt
+++ b/ql/CMakeLists.txt
@@ -364,6 +364,7 @@ set(QL_SOURCES
math/distributions/normaldistribution.cpp
math/distributions/studenttdistribution.cpp
math/errorfunction.cpp
+ math/expm1.cpp
math/factorial.cpp
math/incompletegamma.cpp
math/integrals/discreteintegrals.cpp
@@ -1426,6 +1427,7 @@ set(QL_HEADERS
math/distributions/poissondistribution.hpp
math/distributions/studenttdistribution.hpp
math/errorfunction.hpp
+ math/expm1.hpp
math/factorial.hpp
math/fastfouriertransform.hpp
math/functional.hpp
@@ -1433,6 +1435,7 @@ set(QL_HEADERS
math/incompletegamma.hpp
math/integrals/discreteintegrals.hpp
math/integrals/exponentialintegrals.hpp
+ math/integrals/expsinhintegral.hpp
math/integrals/filonintegral.hpp
math/integrals/gaussianorthogonalpolynomial.hpp
math/integrals/gaussianquadratures.hpp
diff --git a/ql/math/Makefile.am b/ql/math/Makefile.am
index f7c8778f873..535de3132a5 100644
--- a/ql/math/Makefile.am
+++ b/ql/math/Makefile.am
@@ -17,6 +17,7 @@ this_include_HEADERS = \
comparison.hpp \
curve.hpp \
errorfunction.hpp \
+ expm1.hpp \
factorial.hpp \
fastfouriertransform.hpp \
functional.hpp \
@@ -44,6 +45,7 @@ cpp_files = \
beta.cpp \
bspline.cpp \
errorfunction.cpp \
+ expm1.cpp \
factorial.cpp \
incompletegamma.cpp \
matrix.cpp \
diff --git a/ql/math/all.hpp b/ql/math/all.hpp
index e724b24b3ce..7df092f2b55 100644
--- a/ql/math/all.hpp
+++ b/ql/math/all.hpp
@@ -9,6 +9,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/ql/math/expm1.cpp b/ql/math/expm1.cpp
new file mode 100644
index 00000000000..29f595bfc85
--- /dev/null
+++ b/ql/math/expm1.cpp
@@ -0,0 +1,54 @@
+/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/*
+ Copyright (C) 2023 Klaus Spanderen
+
+ This file is part of QuantLib, a free-software/open-source library
+ for financial quantitative analysts and developers - http://quantlib.org/
+
+ QuantLib is free software: you can redistribute it and/or modify it
+ under the terms of the QuantLib license. You should have received a
+ copy of the license along with this program; if not, please email
+ . The license is also available online at
+ .
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the license for more details.
+*/
+
+#include
+#include
+
+#include
+
+namespace QuantLib {
+ std::complex expm1(const std::complex& z) {
+ if (std::abs(z) < 1.0) {
+ const Real a = z.real(), b = z.imag();
+ const Real exp_1 = std::expm1(a);
+ const Real cos_1 = -2*squared(std::sin(0.5*b));
+
+ return std::complex(
+ exp_1*cos_1 + exp_1 + cos_1,
+ std::sin(b)*std::exp(a)
+ );
+ }
+ else {
+ return std::exp(z)-1.0;
+ }
+ }
+
+ std::complex log1p(const std::complex& z) {
+ const Real a = z.real(), b = z.imag();
+ if (std::abs(a) < 0.5 && std::abs(b) < 0.5) {
+ return std::complex(
+ 0.5*std::log1p(a*a + 2*a + b*b),
+ std::arg(1.0 + z)
+ );
+ }
+ else {
+ return std::log(1.0+z);
+ }
+ }
+}
diff --git a/ql/math/expm1.hpp b/ql/math/expm1.hpp
new file mode 100644
index 00000000000..c143a6f70a2
--- /dev/null
+++ b/ql/math/expm1.hpp
@@ -0,0 +1,35 @@
+/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/*
+ Copyright (C) 2023 Klaus Spanderen
+
+ This file is part of QuantLib, a free-software/open-source library
+ for financial quantitative analysts and developers - http://quantlib.org/
+
+ QuantLib is free software: you can redistribute it and/or modify it
+ under the terms of the QuantLib license. You should have received a
+ copy of the license along with this program; if not, please email
+ . The license is also available online at
+ .
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the license for more details.
+*/
+
+ /*! \file expm1.hpp
+ \brief complex versions of expm1 and logp1
+*/
+
+#ifndef quantlib_expm1_hpp
+#define quantlib_expm1_hpp
+
+#include
+#include
+
+namespace QuantLib {
+ std::complex expm1(const std::complex& z);
+ std::complex log1p(const std::complex& z);
+}
+#endif
+
diff --git a/ql/math/integrals/Makefile.am b/ql/math/integrals/Makefile.am
index fcbcf543f5e..59bfbfe6fc6 100644
--- a/ql/math/integrals/Makefile.am
+++ b/ql/math/integrals/Makefile.am
@@ -5,6 +5,7 @@ this_include_HEADERS = \
all.hpp \
discreteintegrals.hpp \
exponentialintegrals.hpp \
+ expsinhintegral.hpp \
filonintegral.hpp \
gausslaguerrecosinepolynomial.hpp \
gausslobattointegral.hpp \
diff --git a/ql/math/integrals/all.hpp b/ql/math/integrals/all.hpp
index 5ee9cedebd1..ee03ff1a828 100644
--- a/ql/math/integrals/all.hpp
+++ b/ql/math/integrals/all.hpp
@@ -3,6 +3,7 @@
#include
#include
+#include
#include
#include
#include
diff --git a/ql/math/integrals/exponentialintegrals.cpp b/ql/math/integrals/exponentialintegrals.cpp
index 8140542315b..b87d3836367 100644
--- a/ql/math/integrals/exponentialintegrals.cpp
+++ b/ql/math/integrals/exponentialintegrals.cpp
@@ -23,14 +23,12 @@
#include
#include
+#include
#include
+#include
#include
-#ifndef M_EULER_MASCHERONI
- #define M_EULER_MASCHERONI 0.5772156649015328606065121
-#endif
-
namespace QuantLib {
namespace exponential_integrals_helper {
@@ -122,28 +120,61 @@ namespace QuantLib {
}
}
+ std::complex _Ei(
+ const std::complex& z, const std::complex& acc) {
- std::complex E1(std::complex z) {
- QL_REQUIRE(std::abs(z) <= 25.0, "Insufficient precision for |z| > 25.0");
+ if (z.real() == 0.0 && z.imag() == 0.0
+ && std::numeric_limits::has_infinity) {
+ return std::complex(-std::numeric_limits::infinity());
+ }
- std::complex s(0.0), sn(-z);
- Size n;
- for (n=2; n < 1000 && s + sn/Real(n-1) != s; ++n) {
- s+=sn/Real(n-1);
- sn *= -z/Real(n);
- }
+ constexpr Real DIST = 4.5;
+ constexpr Real MAX_ERROR = 5*QL_EPSILON;
- QL_REQUIRE(n < 1000, "series conversion issue");
+ const Real z_inf = std::log(0.01*QL_MAX_REAL) + std::log(100.0);
+ QL_REQUIRE(z.real() < z_inf, "argument error " << z);
- return -M_EULER_MASCHERONI - std::log(z) -s;
- }
+ const Real z_asym = 2.0 - 1.035*std::log(MAX_ERROR);
- std::complex Ei(std::complex z) {
- QL_REQUIRE(std::abs(z) <= 25.0, "Insufficient precision for |z| > 25.0");
+ using boost::math::sign;
+ const Real abs_z = std::abs(z);
- std::complex s(0.0), sn=z;
+ const auto match = [=](
+ const std::complex& z1, const std::complex& z2)
+ -> bool {
+ const std::complex d = z1 - z2;
+ return std::abs(d.real()) <= MAX_ERROR*std::abs(z1.real())
+ && std::abs(d.imag()) <= MAX_ERROR*std::abs(z1.imag());
+ };
+
+ if (z.real() > z_inf)
+ return std::complex(std::exp(z)/z) + acc;
+ if (abs_z > 1.1*z_asym) {
+ std::complex ei = acc + std::complex(Real(0.0), sign(z.imag())*M_PI);
+ std::complex s(std::exp(z)/z);
+ for (Size i=1; i <= std::floor(abs_z)+1; ++i) {
+ if (match(ei+s, ei))
+ return ei+s;
+ ei += s;
+ s *= Real(i)/z;
+ }
+ QL_FAIL("series conversion issue for Ei(" << z << ")");
+ }
+
+ if (abs_z > DIST && (z.real() < 0 || std::abs(z.imag()) > DIST)) {
+ std::complex ei(0.0);
+ for (Size k = 47; k >=1; --k) {
+ ei = - Real(k*k)/(2.0*k + 1.0 - z + ei);
+ }
+ return (acc + std::complex(0.0, sign(z.imag())*M_PI))
+ - std::exp(z)/ (1.0 - z + ei);
+
+ QL_FAIL("series conversion issue for Ei(" << z << ")");
+ }
+
+ std::complex s(0.0), sn=z;
Real nn=1.0;
Size n;
@@ -156,27 +187,65 @@ namespace QuantLib {
sn *= -z / Real(2*n);
}
- QL_REQUIRE(n < 1000, "series conversion issue");
+ QL_REQUIRE(n < 1000, "series conversion issue for Ei(" << z << ")");
+
+ const std::complex r
+ = (M_EULER_MASCHERONI + acc) + std::log(z) + std::exp(0.5*z)*s;
- return M_EULER_MASCHERONI + std::log(z) + std::exp(0.5*z)*s;
+ if (z.imag() != Real(0.0))
+ return r;
+ else
+ return std::complex(r.real(), acc.imag());
+ }
+
+ std::complex Ei(const std::complex&z) {
+ return _Ei(z, std::complex(0.0));
+ }
+
+ std::complex E1(const std::complex& z) {
+ if (z.imag() < 0.0) {
+ return -_Ei(-z, std::complex(0.0, -M_PI));
+ }
+ else if (z.imag() > 0.0 || z.real() < 0.0) {
+ return -_Ei(-z, std::complex(0.0, M_PI));
+ }
+ else {
+ return -Ei(-z);
+ }
}
// Reference:
// https://functions.wolfram.com/GammaBetaErf/ExpIntegralEi/introductions/ExpIntegrals/ShowAll.html
- std::complex Si(std::complex z) {
- const std::complex i(0.0, 1.0);
-
- return 0.25*i*(2.0*(Ei(-i*z) - Ei(i*z))
- + std::log(i/z) - std::log(-i/z) - std::log(-i*z)
- + std::log(i*z));
+ std::complex Si(const std::complex& z) {
+ if (std::abs(z) <= 0.2) {
+ std::complex s(0), nn(z);
+ Size k;
+ for (k=2; k < 100 && s != s+nn; ++k) {
+ s += nn;
+ nn *= -z*z/((2.0*k-2)*(2*k-1)*(2*k-1))*(2.0*k-3);
+ }
+ QL_REQUIRE(k < 100, "series conversion issue for Si(" << z << ")");
+
+ return s;
+ }
+ else {
+ const std::complex i(0.0, 1.0);
+ return 0.5*i*(E1(-i*z) - E1(i*z)
+ - std::complex(0.0, ((z.real() >= 0 && z.imag() >= 0)
+ || (z.real() > 0 && z.imag() < 0) )? M_PI : -M_PI));
+ }
}
- std::complex Ci(std::complex z) {
+ std::complex Ci(const std::complex& z) {
const std::complex i(0.0, 1.0);
- return 0.25*(2.0*(Ei(-i*z) + Ei(i*z))
- + std::log(i/z) + std::log(-i/z) - std::log(-i*z)
- - std::log(i*z)) + std::log(z);
+ std::complex acc(0.0);
+ if (z.real() < 0.0 && z.imag() >= 0.0)
+ acc.imag(M_PI);
+ else if (z.real() <= 0.0 && z.imag() <= 0.0)
+ acc.imag(-M_PI);
+
+ return -0.5*(E1(-i*z) + E1(i*z)) + acc;
}
}
}
diff --git a/ql/math/integrals/exponentialintegrals.hpp b/ql/math/integrals/exponentialintegrals.hpp
index 957ca25d002..eff7d4fbbe3 100644
--- a/ql/math/integrals/exponentialintegrals.hpp
+++ b/ql/math/integrals/exponentialintegrals.hpp
@@ -20,23 +20,37 @@
/*! \file exponentialintegrals.hpp
*/
-#include
+#ifndef quantlib_exponentail_integral_hpp
+#define quantlib_exponentail_integral_hpp
+#include
#include
+#ifndef M_EULER_MASCHERONI
+#define M_EULER_MASCHERONI 0.5772156649015328606065121
+#endif
+
namespace QuantLib {
/*! References:
B. Rowe et al: GALSIM: The modular galaxy image simulation toolkit
https://arxiv.org/abs/1407.7676
+
+ V. Pegoraro, P. Slusallek:
+ On the Evaluation of the Complex-Valued Exponential Integral
+ https://www.sci.utah.edu/~vpegorar/research/2011_JGT.pdf
+
*/
+
namespace ExponentialIntegral {
Real Si(Real x);
Real Ci(Real x);
- std::complex Ci(std::complex z);
- std::complex Si(std::complex z);
- std::complex E1(std::complex z);
- std::complex Ei(std::complex z);
+ std::complex Ci(const std::complex& z);
+ std::complex Si(const std::complex& z);
+ std::complex E1(const std::complex& z);
+ std::complex Ei(const std::complex& z);
}
}
+
+#endif
diff --git a/ql/math/integrals/expsinhintegral.hpp b/ql/math/integrals/expsinhintegral.hpp
new file mode 100644
index 00000000000..b62c711fdf6
--- /dev/null
+++ b/ql/math/integrals/expsinhintegral.hpp
@@ -0,0 +1,104 @@
+/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/*
+ Copyright (C) 2023 Klaus Spanderen
+
+ This file is part of QuantLib, a free-software/open-source library
+ for financial quantitative analysts and developers - http://quantlib.org/
+
+ QuantLib is free software: you can redistribute it and/or modify it
+ under the terms of the QuantLib license. You should have received a
+ copy of the license along with this program; if not, please email
+ . The license is also available online at
+ .
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the license for more details.
+*/
+
+/*! \file expsinhintegral.hpp
+*/
+
+#ifndef quantlib_exp_sinh_integral_hpp
+#define quantlib_exp_sinh_integral_hpp
+
+#include
+#include
+#include
+
+#if BOOST_VERSION >= 106900
+#define QL_BOOST_HAS_EXP_SINH
+
+#include
+#include
+
+namespace QuantLib {
+ /*! The exp-sinh quadrature routine provided by boost is a rapidly convergent
+ numerical integration scheme for holomorphic integrands. The tolerance
+ is used against the error estimate for the L1 norm of the integral.
+ */
+ class ExpSinhIntegral : public Integrator {
+ public:
+ explicit ExpSinhIntegral(
+ Real relTolerance = std::sqrt(std::numeric_limits::epsilon()),
+ Size maxRefinements = 9)
+ : Integrator(QL_MAX_REAL, Null()),
+ relTolerance_(relTolerance),
+ exp_sinh_(maxRefinements) {}
+
+ // For half-infinite interval [0, \inf), the exp-sinh quadrature is provided by
+ Real integrate(const ext::function& f) const {
+ setNumberOfEvaluations(0);
+ Real error;
+ Real value = exp_sinh_.integrate(
+ [this, &f](Real x) -> Real { increaseNumberOfEvaluations(1); return f(x); },
+ relTolerance_, &error);
+ setAbsoluteError(error);
+
+ return value;
+ }
+
+ protected:
+ Real integrate(const ext::function& f, Real a, Real b)
+ const override {
+ Real error;
+ Real value = exp_sinh_.integrate(
+ [this, &f](Real x) -> Real {
+ increaseNumberOfEvaluations(1);
+ return f(x);
+ },
+ a, b, relTolerance_, &error);
+ setAbsoluteError(error);
+
+ return value;
+ }
+
+ private:
+ const Real relTolerance_;
+ mutable boost::math::quadrature::exp_sinh exp_sinh_;
+ };
+}
+
+#else
+
+namespace QuantLib {
+
+ class ExpSinhIntegral : public Integrator {
+ public:
+ explicit ExpSinhIntegral( Real relTolerance = 0, Size maxRefinements = 0)
+ : Integrator(Null(), Null()) {
+ QL_FAIL("boost version 1.69 or higher is required in order to use ExpSinhIntegral");
+ }
+
+ protected:
+ Real integrate(const ext::function& f, Real a, Real b) const override {
+ QL_FAIL("boost version 1.69 or higher is required in order to use ExpSinhIntegral");
+ }
+ };
+
+}
+
+#endif
+
+#endif
diff --git a/ql/math/integrals/gausslobattointegral.cpp b/ql/math/integrals/gausslobattointegral.cpp
index 27187a44453..df2e2971786 100644
--- a/ql/math/integrals/gausslobattointegral.cpp
+++ b/ql/math/integrals/gausslobattointegral.cpp
@@ -138,8 +138,8 @@ namespace QuantLib {
+432*(fmll+fmrr)+625*(fml+fmr)+672*fm);
// avoid 80 bit logic on x86 cpu
- volatile Real dist = acc + (integral1-integral2);
- if(const_cast(dist)==acc || mll<=a || b<=mrr) {
+ const Real dist = acc + (integral1-integral2);
+ if(dist==acc || mll<=a || b<=mrr) {
QL_REQUIRE(m>a && b>m,"Interval contains no more machine number");
return integral1;
}
diff --git a/ql/math/integrals/simpsonintegral.hpp b/ql/math/integrals/simpsonintegral.hpp
index 69443578147..346cb6f2b3b 100644
--- a/ql/math/integrals/simpsonintegral.hpp
+++ b/ql/math/integrals/simpsonintegral.hpp
@@ -44,11 +44,14 @@ namespace QuantLib {
// start from the coarsest trapezoid...
Size N = 1;
Real I = (f(a)+f(b))*(b-a)/2.0, newI;
+ increaseNumberOfEvaluations(2);
+
Real adjI = I, newAdjI;
// ...and refine it
Size i = 1;
do {
newI = Default::integrate(f,a,b,I,N);
+ increaseNumberOfEvaluations(N);
N *= 2;
newAdjI = (4.0*newI-I)/3.0;
// good enough? Also, don't run away immediately
diff --git a/ql/math/integrals/tanhsinhintegral.hpp b/ql/math/integrals/tanhsinhintegral.hpp
index 31def3d45e7..61d915fb740 100644
--- a/ql/math/integrals/tanhsinhintegral.hpp
+++ b/ql/math/integrals/tanhsinhintegral.hpp
@@ -34,8 +34,6 @@
#include
namespace QuantLib {
- using tanh_sinh = boost::math::quadrature::tanh_sinh;
-
/*! The tanh-sinh quadrature routine provided by boost is a rapidly convergent
numerical integration scheme for holomorphic integrands. The tolerance
is used against the error estimate for the L1 norm of the integral.
@@ -55,7 +53,12 @@ namespace QuantLib {
Real integrate(const ext::function& f, Real a, Real b)
const override {
Real error;
- Real value = tanh_sinh_.integrate(f, a, b, relTolerance_, &error);
+ Real value = tanh_sinh_.integrate(
+ [this, f](Real x)-> Real {
+ increaseNumberOfEvaluations(1);
+ return f(x);
+ },
+ a, b, relTolerance_, &error);
setAbsoluteError(error);
return value;
@@ -63,7 +66,7 @@ namespace QuantLib {
private:
const Real relTolerance_;
- mutable tanh_sinh tanh_sinh_;
+ mutable boost::math::quadrature::tanh_sinh tanh_sinh_;
};
}
@@ -79,8 +82,9 @@ namespace QuantLib {
}
protected:
- Real integrate(const ext::function& f, Real a, Real b)
- const override { return 0.0; }
+ Real integrate(const ext::function& f, Real a, Real b) const override {
+ QL_FAIL("boost version 1.69 or higher is required in order to use TanhSinhIntegral");
+ }
};
}
diff --git a/ql/math/integrals/trapezoidintegral.hpp b/ql/math/integrals/trapezoidintegral.hpp
index 074499d8e8e..68954e217f6 100644
--- a/ql/math/integrals/trapezoidintegral.hpp
+++ b/ql/math/integrals/trapezoidintegral.hpp
@@ -61,10 +61,12 @@ namespace QuantLib {
// start from the coarsest trapezoid...
Size N = 1;
Real I = (f(a)+f(b))*(b-a)/2.0, newI;
+ increaseNumberOfEvaluations(2);
// ...and refine it
Size i = 1;
do {
newI = IntegrationPolicy::integrate(f,a,b,I,N);
+ increaseNumberOfEvaluations(N*(IntegrationPolicy::nbEvalutions()-1));
N *= IntegrationPolicy::nbEvalutions();
// good enough? Also, don't run away immediately
if (std::fabs(I-newI) <= absoluteAccuracy() && i > 5)
diff --git a/ql/pricingengines/vanilla/analytichestonengine.cpp b/ql/pricingengines/vanilla/analytichestonengine.cpp
index 8c2a9017ee0..369bb9fe41b 100644
--- a/ql/pricingengines/vanilla/analytichestonengine.cpp
+++ b/ql/pricingengines/vanilla/analytichestonengine.cpp
@@ -31,10 +31,18 @@
#include
#include
#include
+#include
#include
+#include
#include
#include
#include
+
+#include
+#include
+
+#include
+#include
#include
#if defined(QL_PATCH_MSVC)
@@ -121,38 +129,11 @@ namespace QuantLib {
// helper class for integration
class AnalyticHestonEngine::Fj_Helper {
public:
- Fj_Helper(const VanillaOption::arguments& arguments,
- const ext::shared_ptr& model,
+ Fj_Helper(Real kappa, Real theta, Real sigma, Real v0,
+ Real s0, Real rho,
const AnalyticHestonEngine* engine,
ComplexLogFormula cpxLog,
- Time term,
- Real ratio,
- Size j);
-
- Fj_Helper(Real kappa,
- Real theta,
- Real sigma,
- Real v0,
- Real s0,
- Real rho,
- const AnalyticHestonEngine* engine,
- ComplexLogFormula cpxLog,
- Time term,
- Real strike,
- Real ratio,
- Size j);
-
- Fj_Helper(Real kappa,
- Real theta,
- Real sigma,
- Real v0,
- Real s0,
- Real rho,
- ComplexLogFormula cpxLog,
- Time term,
- Real strike,
- Real ratio,
- Size j);
+ Time term, Real strike, Real ratio, Size j);
Real operator()(Real phi) const;
@@ -175,37 +156,11 @@ namespace QuantLib {
const AnalyticHestonEngine* const engine_;
};
-
- AnalyticHestonEngine::Fj_Helper::Fj_Helper(
- const VanillaOption::arguments& arguments,
- const ext::shared_ptr& model,
- const AnalyticHestonEngine* const engine,
- ComplexLogFormula cpxLog,
- Time term, Real ratio, Size j)
- : j_ (j), //arg_(arguments),
- kappa_(model->kappa()), theta_(model->theta()),
- sigma_(model->sigma()), v0_(model->v0()),
- cpxLog_(cpxLog), term_(term),
- x_(std::log(model->process()->s0()->value())),
- sx_(std::log(ext::dynamic_pointer_cast
- (arguments.payoff)->strike())),
- dd_(x_-std::log(ratio)),
- sigma2_(sigma_*sigma_),
- rsigma_(model->rho()*sigma_),
- t0_(kappa_ - ((j_== 1)? model->rho()*sigma_ : Real(0))),
- b_(0), g_km1_(0),
- engine_(engine)
- {
- }
-
AnalyticHestonEngine::Fj_Helper::Fj_Helper(Real kappa, Real theta,
Real sigma, Real v0, Real s0, Real rho,
const AnalyticHestonEngine* const engine,
ComplexLogFormula cpxLog,
- Time term,
- Real strike,
- Real ratio,
- Size j)
+ Time term, Real strike, Real ratio, Size j)
:
j_(j),
kappa_(kappa),
@@ -226,23 +181,6 @@ namespace QuantLib {
{
}
- AnalyticHestonEngine::Fj_Helper::Fj_Helper(Real kappa,
- Real theta,
- Real sigma,
- Real v0,
- Real s0,
- Real rho,
- ComplexLogFormula cpxLog,
- Time term,
- Real strike,
- Real ratio,
- Size j)
- : j_(j), kappa_(kappa), theta_(theta), sigma_(sigma), v0_(v0), cpxLog_(cpxLog), term_(term),
- x_(std::log(s0)), sx_(std::log(strike)), dd_(x_ - std::log(ratio)), sigma2_(sigma_ * sigma_),
- rsigma_(rho * sigma_), t0_(kappa - ((j == 1) ? rho * sigma : Real(0))), b_(0), g_km1_(0),
- engine_(nullptr) {}
-
-
Real AnalyticHestonEngine::Fj_Helper::operator()(Real phi) const
{
const Real rpsig(rsigma_*phi);
@@ -360,16 +298,169 @@ namespace QuantLib {
}
}
+ AnalyticHestonEngine::OptimalAlpha::OptimalAlpha(
+ const Time t,
+ const AnalyticHestonEngine* const enginePtr)
+ : t_(t),
+ fwd_(enginePtr->model_->process()->s0()->value()
+ * enginePtr->model_->process()->dividendYield()->discount(t)
+ / enginePtr->model_->process()->riskFreeRate()->discount(t)),
+ kappa_(enginePtr->model_->kappa()),
+ theta_(enginePtr->model_->theta()),
+ sigma_(enginePtr->model_->sigma()),
+ rho_(enginePtr->model_->rho()),
+ eps_(std::pow(2, -int(0.5*std::numeric_limits::digits))),
+ enginePtr_(enginePtr),
+ evaluations_(0) {
+ km_ = k(0.0, -1);
+ kp_ = k(0.0, 1);
+ }
+
+ Real AnalyticHestonEngine::OptimalAlpha::alphaMax(Real strike) const {
+ const Real eps = 1e-8;
+ const auto cm = [this](Real k) -> Real { return M(k) - t_; };
+
+ Real alpha_max;
+ const Real adx = kappa_ - sigma_*rho_;
+ if (adx > 0.0) {
+ const Real kp_2pi = k(2*M_PI, 1);
+
+ alpha_max = Brent().solve(
+ cm, eps_, 0.5*(kp_ + kp_2pi), (1+eps)*kp_, (1-eps)*kp_2pi
+ ) - 1.0;
+ }
+ else if (adx < 0.0) {
+ const Time tCut = -2/(kappa_ - sigma_*rho_*kp_);
+ if (t_ < tCut) {
+ const Real kp_pi = k(M_PI, 1);
+ alpha_max = Brent().solve(
+ cm, eps_, 0.5*(kp_ + kp_pi), (1+eps)*kp_, (1-eps)*kp_pi
+ ) - 1.0;
+ }
+ else {
+ alpha_max = Brent().solve(
+ cm, eps_, 0.5*(1.0 + kp_),
+ 1 + eps, (1-eps)*kp_
+ ) - 1.0;
+ }
+ }
+ else { // adx == 0.0
+ const Real kp_pi = k(M_PI, 1);
+ alpha_max = Brent().solve(
+ cm, eps_, 0.5*(kp_ + kp_pi), (1+eps)*kp_, (1-eps)*kp_pi
+ ) - 1.0;
+ }
+
+ QL_REQUIRE(alpha_max >= 0.0, "alpha max must be larger than zero");
+
+ return alpha_max;
+ }
+
+ std::pair
+ AnalyticHestonEngine::OptimalAlpha::alphaGreaterZero(Real strike) const {
+ const Real alpha_max = alphaMax(strike);
+
+ return findMinima(eps_, std::max(2*eps_, (1.0-1e-6)*alpha_max), strike);
+ }
+
+ Real AnalyticHestonEngine::OptimalAlpha::alphaMin(Real strike) const {
+ const auto cm = [this](Real k) -> Real { return M(k) - t_; };
+
+ const Real km_2pi = k(2*M_PI, -1);
+
+ const Real alpha_min = Brent().solve(
+ cm, eps_, 0.5*(km_2pi + km_), (1-1e-8)*km_2pi, (1+1e-8)*km_
+ ) - 1.0;
+
+ QL_REQUIRE(alpha_min <= -1.0, "alpha min must be smaller than minus one");
+
+ return alpha_min;
+ }
+
+ std::pair
+ AnalyticHestonEngine::OptimalAlpha::alphaSmallerMinusOne(Real strike) const {
+ const Real alpha_min = alphaMin(strike);
+
+ return findMinima(
+ std::min(-1.0-1e-6, -1.0 + (1.0-1e-6)*(alpha_min + 1.0)),
+ -1.0 - eps_, strike
+ );
+ }
+
+ Real AnalyticHestonEngine::OptimalAlpha::operator()(Real strike) const {
+ try {
+ const std::pair minusOne = alphaSmallerMinusOne(strike);
+ const std::pair greaterZero = alphaGreaterZero(strike);
+
+ if (minusOne.second < greaterZero.second) {
+ return minusOne.first;
+ }
+ else {
+ return greaterZero.first;
+ }
+ }
+ catch (const Error&) {
+ return -0.5;
+ }
+
+ }
+
+ Size AnalyticHestonEngine::OptimalAlpha::numberOfEvaluations() const {
+ return evaluations_;
+ }
+
+ std::pair AnalyticHestonEngine::OptimalAlpha::findMinima(
+ Real lower, Real upper, Real strike) const {
+ const Real freq = std::log(fwd_/strike);
+
+ return boost::math::tools::brent_find_minima(
+ [freq, this](Real alpha) -> Real {
+ ++evaluations_;
+ const std::complex z(0, -(alpha+1));
+ return enginePtr_->lnChF(z, t_).real()
+ - std::log(alpha*(alpha+1)) + alpha*freq;
+ },
+ lower, upper, int(0.5*std::numeric_limits::digits)
+ );
+ }
+
+ Real AnalyticHestonEngine::OptimalAlpha::M(Real k) const {
+ const Real beta = kappa_ - sigma_*rho_*k;
+
+ if (k >= km_ && k <= kp_) {
+ const Real D = std::sqrt(beta*beta - sigma_*sigma_*k*(k-1));
+ return std::log((beta-D)/(beta+D)) / D;
+ }
+ else {
+ const Real D_imag =
+ std::sqrt(-(beta*beta - sigma_*sigma_*k*(k-1)));
+
+ return 2/D_imag
+ * ( ((beta>0.0)? M_PI : 0.0) - std::atan(D_imag/beta) );
+ }
+ }
+
+ Real AnalyticHestonEngine::OptimalAlpha::k(Real x, Integer sgn) const {
+ return ( (sigma_ - 2*rho_*kappa_)
+ + sgn*std::sqrt(
+ squared(sigma_-2*rho_*kappa_)
+ + 4*(kappa_*kappa_ + x*x/(t_*t_))*(1-rho_*rho_)))
+ /(2*sigma_*(1-rho_*rho_));
+ }
AnalyticHestonEngine::AP_Helper::AP_Helper(
Time term, Real fwd, Real strike, ComplexLogFormula cpxLog,
- const AnalyticHestonEngine* const enginePtr)
+ const AnalyticHestonEngine* const enginePtr,
+ const Real alpha)
: term_(term),
fwd_(fwd),
strike_(strike),
freq_(std::log(fwd/strike)),
cpxLog_(cpxLog),
- enginePtr_(enginePtr) {
+ enginePtr_(enginePtr),
+ alpha_(alpha),
+ s_alpha_(std::exp(alpha*freq_))
+ {
QL_REQUIRE(enginePtr != nullptr, "pricing engine required");
const Real v0 = enginePtr->model_->v0();
@@ -385,7 +476,7 @@ namespace QuantLib {
break;
case AndersenPiterbargOptCV:
vAvg_ = -8.0*std::log(enginePtr->chF(
- std::complex(0, -0.5), term).real())/term;
+ std::complex(0, alpha_), term).real())/term;
break;
case AsymptoticChF:
phi_ = -(v0+term*kappa*theta)/sigma
@@ -398,6 +489,16 @@ namespace QuantLib {
*(v0 + kappa*theta*term)
- 2*kappa*theta*std::atan(rho/std::sqrt(1-rho*rho))))
/(sigma*sigma);
+ case AngledContour:
+ vAvg_ = (1-std::exp(-kappa*term))*(v0 - theta)
+ /(kappa*term) + theta;
+ case AngledContourNoCV:
+ {
+ const Real r = rho - sigma*freq_ / (v0 + kappa*theta*term);
+ tanPhi_ = std::tan(
+ (r*freq_ < 0.0)? M_PI/12*boost::math::sign(freq_) : 0.0
+ );
+ }
break;
default:
QL_FAIL("unknown control variate");
@@ -411,34 +512,52 @@ namespace QuantLib {
== std::complex(0.0),
"only Heston model is supported");
- const std::complex z(u, -0.5);
-
- std::complex phiBS;
-
- switch (cpxLog_) {
- case AndersenPiterbarg:
- case AndersenPiterbargOptCV:
- phiBS = std::exp(
- -0.5*vAvg_*term_*(z*z + std::complex(-z.imag(), z.real())));
- break;
- case AsymptoticChF:
- phiBS = std::exp(u*phi_ + psi_);
- break;
- default:
- QL_FAIL("unknown control variate");
+ constexpr std::complex i(0, 1);
+
+ if (cpxLog_ == AngledContour || cpxLog_ == AngledContourNoCV || cpxLog_ == AsymptoticChF) {
+ const std::complex h_u(u, u*tanPhi_ - alpha_);
+ const std::complex hPrime(h_u-i);
+
+ std::complex phiBS(0.0);
+ if (cpxLog_ == AngledContour)
+ phiBS = std::exp(
+ -0.5*vAvg_*term_*(hPrime*hPrime +
+ std::complex(-hPrime.imag(), hPrime.real())));
+ else if (cpxLog_ == AsymptoticChF)
+ phiBS = std::exp(u*std::complex(1, tanPhi_)*phi_ + psi_);
+
+ return std::exp(-u*tanPhi_*freq_)
+ *(std::exp(std::complex(0.0, u*freq_))
+ *std::complex(1, tanPhi_)
+ *(phiBS - enginePtr_->chF(hPrime, term_))/(h_u*hPrime)
+ ).real()*s_alpha_;
}
+ else if (cpxLog_ == AndersenPiterbarg || cpxLog_ == AndersenPiterbargOptCV) {
+ const std::complex z(u, -alpha_);
+ const std::complex zPrime(u, -alpha_-1);
+ const std::complex phiBS = std::exp(
+ -0.5*vAvg_*term_*(zPrime*zPrime +
+ std::complex(-zPrime.imag(), zPrime.real()))
+ );
- return (std::exp(std::complex(0.0, u*freq_))
- * (phiBS - enginePtr_->chF(z, term_)) / (u*u + 0.25)).real();
+ return (std::exp(std::complex (0.0, u*freq_))
+ * (phiBS - enginePtr_->chF(zPrime, term_)) / (z*zPrime)
+ ).real()*s_alpha_;
+ }
+ else
+ QL_FAIL("unknown control variate");
}
Real AnalyticHestonEngine::AP_Helper::controlVariateValue() const {
- if (cpxLog_ == AndersenPiterbarg || cpxLog_ == AndersenPiterbargOptCV) {
+ if ( cpxLog_ == AngledContour
+ || cpxLog_ == AndersenPiterbarg || cpxLog_ == AndersenPiterbargOptCV) {
return BlackCalculator(
Option::Call, strike_, fwd_, std::sqrt(vAvg_*term_))
.value();
}
else if (cpxLog_ == AsymptoticChF) {
+ QL_REQUIRE(alpha_ == -0.5, "alpha must be equal to -0.5");
+
const std::complex phiFreq(phi_.real(), phi_.imag() + freq_);
using namespace ExponentialIntegral;
@@ -447,35 +566,30 @@ namespace QuantLib {
-2.0*Ci(-0.5*phiFreq)*std::sin(0.5*phiFreq)
+std::cos(0.5*phiFreq)*(M_PI+2.0*Si(0.5*phiFreq)))).real();
}
+ else if (cpxLog_ == AngledContourNoCV) {
+ return ((alpha_ <= 0.0)? fwd_ : 0.0)
+ - ((alpha_ <= -1.0)? strike_ : 0.0)
+ -0.5*((alpha_ == 0.0)? fwd_ :0.0)
+ +0.5*((alpha_ == -1.0)? strike_: 0.0);
+ }
else
QL_FAIL("unknown control variate");
}
std::complex AnalyticHestonEngine::chF(
const std::complex& z, Time t) const {
-
- const Real kappa = model_->kappa();
- const Real sigma = model_->sigma();
- const Real theta = model_->theta();
- const Real rho = model_->rho();
- const Real v0 = model_->v0();
-
- const Real sigma2 = sigma*sigma;
-
- if (sigma > 1e-4) {
- const std::complex g
- = kappa + rho*sigma*std::complex(z.imag(), -z.real());
-
- const std::complex D = std::sqrt(
- g*g + (z*z + std::complex(-z.imag(), z.real()))*sigma2);
-
- const std::complex G = (g-D)/(g+D);
-
- return std::exp(v0/sigma2*(1.0-std::exp(-D*t))/(1.0-G*std::exp(-D*t))
- *(g-D) + kappa*theta/sigma2*((g-D)*t
- -2.0*std::log((1.0-G*std::exp(-D*t))/(1.0-G))));
+ if (model_->sigma() > 1e-6 || model_->kappa() < 1e-8) {
+ return std::exp(lnChF(z, t));
}
else {
+ const Real kappa = model_->kappa();
+ const Real sigma = model_->sigma();
+ const Real theta = model_->theta();
+ const Real rho = model_->rho();
+ const Real v0 = model_->v0();
+
+ const Real sigma2 = sigma*sigma;
+
const Real kt = kappa*t;
const Real ekt = std::exp(kt);
const Real e2kt = std::exp(2*kt);
@@ -484,12 +598,14 @@ namespace QuantLib {
return std::exp(-(((theta - v0 + ekt*((-1 + kt)*theta + v0))
*z*zpi)/ekt)/(2.*kappa))
+
+ (std::exp(-(kt) - ((theta - v0 + ekt
*((-1 + kt)*theta + v0))*z*zpi)
/(2.*ekt*kappa))*rho*(2*theta + kt*theta -
v0 - kt*v0 + ekt*((-2 + kt)*theta + v0))
*(1.0 - std::complex(-z.imag(),z.real()))*z*z)
/(2.*kappa*kappa)*sigma
+
+ (std::exp(-2*kt - ((theta - v0 + ekt
*((-1 + kt)*theta + v0))*z*zpi)/(2.*ekt*kappa))*z*z*zpi
*(-2*rho2*squared(2*theta + kt*theta - v0 -
@@ -504,8 +620,41 @@ namespace QuantLib {
}
std::complex AnalyticHestonEngine::lnChF(
- const std::complex& z, Time T) const {
- return std::log(chF(z, T));
+ const std::complex& z, Time t) const {
+
+ const Real kappa = model_->kappa();
+ const Real sigma = model_->sigma();
+ const Real theta = model_->theta();
+ const Real rho = model_->rho();
+ const Real v0 = model_->v0();
+
+ const Real sigma2 = sigma*sigma;
+
+ const std::complex g
+ = kappa + rho*sigma*std::complex(z.imag(), -z.real());
+
+ const std::complex D = std::sqrt(
+ g*g + (z*z + std::complex(-z.imag(), z.real()))*sigma2);
+
+ // reduce cancelation errors, see. L. Andersen and M. Lake
+ std::complex r(g-D);
+ if (g.real()*D.real() + g.imag()*D.imag() > 0.0) {
+ r = -sigma2*z*std::complex(z.real(), z.imag()+1)/(g+D);
+ }
+
+ std::complex y;
+ if (D.real() != 0.0 || D.imag() != 0.0) {
+ y = expm1(-D*t)/(2.0*D);
+ }
+ else
+ y = -0.5*t;
+
+ const std::complex A
+ = kappa*theta/sigma2*(r*t - 2.0*log1p(-r*y));
+ const std::complex B
+ = z*std::complex(z.real(), z.imag()+1)*y/(1.0-r*y);
+
+ return A+v0*B;
}
AnalyticHestonEngine::AnalyticHestonEngine(
@@ -515,10 +664,11 @@ namespace QuantLib {
VanillaOption::arguments,
VanillaOption::results>(model),
evaluations_(0),
- cpxLog_ (Gatheral),
+ cpxLog_ (OptimalCV),
integration_(new Integration(
Integration::gaussLaguerre(integrationOrder))),
- andersenPiterbargEpsilon_(Null()) {
+ andersenPiterbargEpsilon_(Null()),
+ alpha_(-0.5) {
}
AnalyticHestonEngine::AnalyticHestonEngine(
@@ -528,24 +678,27 @@ namespace QuantLib {
VanillaOption::arguments,
VanillaOption::results>(model),
evaluations_(0),
- cpxLog_(Gatheral),
+ cpxLog_(OptimalCV),
integration_(new Integration(Integration::gaussLobatto(
relTolerance, Null(), maxEvaluations))),
- andersenPiterbargEpsilon_(Null()) {
+ andersenPiterbargEpsilon_(1e-40),
+ alpha_(-0.5) {
}
AnalyticHestonEngine::AnalyticHestonEngine(
const ext::shared_ptr& model,
ComplexLogFormula cpxLog,
const Integration& integration,
- const Real andersenPiterbargEpsilon)
+ Real andersenPiterbargEpsilon,
+ Real alpha)
: GenericModelEngine(model),
evaluations_(0),
cpxLog_(cpxLog),
integration_(new Integration(integration)),
- andersenPiterbargEpsilon_(andersenPiterbargEpsilon) {
+ andersenPiterbargEpsilon_(andersenPiterbargEpsilon),
+ alpha_(alpha) {
QL_REQUIRE( cpxLog_ != BranchCorrection
|| !integration.isAdaptiveIntegration(),
"Branch correction does not work in conjunction "
@@ -556,61 +709,92 @@ namespace QuantLib {
AnalyticHestonEngine::optimalControlVariate(
Time t, Real v0, Real kappa, Real theta, Real sigma, Real rho) {
- if (t > 0.1 && (v0+t*kappa*theta)/sigma*std::sqrt(1-rho*rho) < 0.055) {
+ if (t > 0.15 && (v0+t*kappa*theta)/sigma*std::sqrt(1-rho*rho) < 0.15
+ && ((kappa- 0.5*rho*sigma)*(v0 + t*kappa*theta)
+ + kappa*theta*std::log(4*(1-rho*rho)))/(sigma*sigma) < 0.1) {
return AsymptoticChF;
}
else {
- return AndersenPiterbargOptCV;
+ return AngledContour;
}
}
-
Size AnalyticHestonEngine::numberOfEvaluations() const {
return evaluations_;
}
- void AnalyticHestonEngine::doCalculation(Real riskFreeDiscount,
- Real dividendDiscount,
- Real spotPrice,
- Real strikePrice,
- Real term,
- Real kappa, Real theta, Real sigma, Real v0, Real rho,
- const TypePayoff& type,
- const Integration& integration,
- const ComplexLogFormula cpxLog,
- const AnalyticHestonEngine* const enginePtr,
- Real& value,
- Size& evaluations) {
+ Real AnalyticHestonEngine::priceVanillaPayoff(
+ const ext::shared_ptr& payoff,
+ const Date& maturity) const {
- const Real ratio = riskFreeDiscount/dividendDiscount;
+ const ext::shared_ptr& process = model_->process();
+ const Real fwd = process->s0()->value()
+ * process->dividendYield()->discount(maturity)
+ / process->riskFreeRate()->discount(maturity);
- evaluations = 0;
+ return priceVanillaPayoff(payoff, process->time(maturity), fwd);
+ }
- switch(cpxLog) {
+ Real AnalyticHestonEngine::priceVanillaPayoff(
+ const ext::shared_ptr& payoff, Time maturity) const {
+
+ const ext::shared_ptr& process = model_->process();
+ const Real fwd = process->s0()->value()
+ * process->dividendYield()->discount(maturity)
+ / process->riskFreeRate()->discount(maturity);
+
+ return priceVanillaPayoff(payoff, maturity, fwd);
+ }
+
+ Real AnalyticHestonEngine::priceVanillaPayoff(
+ const ext::shared_ptr& payoff,
+ Time maturity, Real fwd) const {
+
+ Real value;
+
+ const ext::shared_ptr& process = model_->process();
+ const DiscountFactor dr = process->riskFreeRate()->discount(maturity);
+
+ const Real strike = payoff->strike();
+ const Real spot = process->s0()->value();
+ QL_REQUIRE(spot > 0.0, "negative or null underlying given");
+
+ const DiscountFactor df = spot/fwd;
+ const DiscountFactor dd = dr/df;
+
+ const Real kappa = model_->kappa();
+ const Real sigma = model_->sigma();
+ const Real theta = model_->theta();
+ const Real rho = model_->rho();
+ const Real v0 = model_->v0();
+
+ evaluations_ = 0;
+
+ switch(cpxLog_) {
case Gatheral:
case BranchCorrection: {
const Real c_inf = std::min(0.2, std::max(0.0001,
- std::sqrt(1.0-rho*rho)/sigma))*(v0 + kappa*theta*term);
+ std::sqrt(1.0-rho*rho)/sigma))*(v0 + kappa*theta*maturity);
- const Real p1 = integration.calculate(c_inf,
- Fj_Helper(kappa, theta, sigma, v0, spotPrice, rho, enginePtr,
- cpxLog, term, strikePrice, ratio, 1))/M_PI;
- evaluations += integration.numberOfEvaluations();
+ const Real p1 = integration_->calculate(c_inf,
+ Fj_Helper(kappa, theta, sigma, v0, spot, rho, this,
+ cpxLog_, maturity, strike, df, 1))/M_PI;
+ evaluations_ += integration_->numberOfEvaluations();
- const Real p2 = integration.calculate(c_inf,
- Fj_Helper(kappa, theta, sigma, v0, spotPrice, rho, enginePtr,
- cpxLog, term, strikePrice, ratio, 2))/M_PI;
- evaluations += integration.numberOfEvaluations();
+ const Real p2 = integration_->calculate(c_inf,
+ Fj_Helper(kappa, theta, sigma, v0, spot, rho, this,
+ cpxLog_, maturity, strike, df, 2))/M_PI;
+ evaluations_ += integration_->numberOfEvaluations();
- switch (type.optionType())
+ switch (payoff->optionType())
{
case Option::Call:
- value = spotPrice*dividendDiscount*(p1+0.5)
- - strikePrice*riskFreeDiscount*(p2+0.5);
+ value = spot*dd*(p1+0.5)
+ - strike*dr*(p2+0.5);
break;
case Option::Put:
- value = spotPrice*dividendDiscount*(p1-0.5)
- - strikePrice*riskFreeDiscount*(p2-0.5);
+ value = spot*dd*(p1-0.5)
+ - strike*dr*(p2-0.5);
break;
default:
QL_FAIL("unknown option type");
@@ -620,49 +804,59 @@ namespace QuantLib {
case AndersenPiterbarg:
case AndersenPiterbargOptCV:
case AsymptoticChF:
+ case AngledContour:
+ case AngledContourNoCV:
case OptimalCV: {
const Real c_inf =
- std::sqrt(1.0-rho*rho)*(v0 + kappa*theta*term)/sigma;
-
- const Real fwdPrice = spotPrice / ratio;
+ std::sqrt(1.0-rho*rho)*(v0 + kappa*theta*maturity)/sigma;
- const Real epsilon = enginePtr->andersenPiterbargEpsilon_
- *M_PI/(std::sqrt(strikePrice*fwdPrice)*riskFreeDiscount);
+ const Real epsilon = andersenPiterbargEpsilon_
+ *M_PI/(std::sqrt(strike*fwd)*dr);
const ext::function uM = [&](){
- return Integration::andersenPiterbargIntegrationLimit(c_inf, epsilon, v0, term);
+ return Integration::andersenPiterbargIntegrationLimit(
+ c_inf, epsilon, v0, maturity);
};
- AP_Helper cvHelper(term, fwdPrice, strikePrice,
- (cpxLog == OptimalCV)
- ? optimalControlVariate(term, v0, kappa, theta, sigma, rho)
- : cpxLog,
- enginePtr
+ const ComplexLogFormula finalLog = (cpxLog_ == OptimalCV)
+ ? optimalControlVariate(maturity, v0, kappa, theta, sigma, rho)
+ : cpxLog_;
+
+ const AP_Helper cvHelper(
+ maturity, fwd, strike, finalLog, this, alpha_
);
const Real cvValue = cvHelper.controlVariateValue();
- const Real h_cv = integration.calculate(c_inf, cvHelper, uM)
- * std::sqrt(strikePrice * fwdPrice)/M_PI;
- evaluations += integration.numberOfEvaluations();
+ const Real vAvg = (1-std::exp(-kappa*maturity))*(v0-theta)/(kappa*maturity) + theta;
- switch (type.optionType())
+ const Real scalingFactor = (cpxLog_ != OptimalCV && cpxLog_ != AsymptoticChF)
+ ? std::max(0.25, std::min(1000.0, 0.25/std::sqrt(0.5*vAvg*maturity)))
+ : Real(1.0);
+
+ const Real h_cv =
+ fwd/M_PI*integration_->calculate(c_inf, cvHelper, uM, scalingFactor);
+
+ evaluations_ += integration_->numberOfEvaluations();
+
+ switch (payoff->optionType())
{
case Option::Call:
- value = (cvValue + h_cv)*riskFreeDiscount;
+ value = (cvValue + h_cv)*dr;
break;
case Option::Put:
- value = (cvValue + h_cv - (fwdPrice - strikePrice))*riskFreeDiscount;
+ value = (cvValue + h_cv - (fwd - strike))*dr;
break;
default:
QL_FAIL("unknown option type");
}
}
break;
-
default:
QL_FAIL("unknown complex log formula");
}
+
+ return value;
}
void AnalyticHestonEngine::calculate() const
@@ -676,35 +870,9 @@ namespace QuantLib {
ext::dynamic_pointer_cast(arguments_.payoff);
QL_REQUIRE(payoff, "non plain vanilla payoff given");
- const ext::shared_ptr& process = model_->process();
-
- const Real riskFreeDiscount = process->riskFreeRate()->discount(
- arguments_.exercise->lastDate());
- const Real dividendDiscount = process->dividendYield()->discount(
- arguments_.exercise->lastDate());
-
- const Real spotPrice = process->s0()->value();
- QL_REQUIRE(spotPrice > 0.0, "negative or null underlying given");
-
- const Real strikePrice = payoff->strike();
- const Real term = process->time(arguments_.exercise->lastDate());
+ const Date exerciseDate = arguments_.exercise->lastDate();
- doCalculation(riskFreeDiscount,
- dividendDiscount,
- spotPrice,
- strikePrice,
- term,
- model_->kappa(),
- model_->theta(),
- model_->sigma(),
- model_->v0(),
- model_->rho(),
- *payoff,
- *integration_,
- cpxLog_,
- this,
- results_.value,
- evaluations_);
+ results_.value = priceVanillaPayoff(payoff, exerciseDate);
}
@@ -717,15 +885,14 @@ namespace QuantLib {
: intAlgo_(intAlgo), gaussianQuadrature_(std::move(gaussianQuadrature)) {}
AnalyticHestonEngine::Integration
- AnalyticHestonEngine::Integration::gaussLobatto(Real relTolerance,
- Real absTolerance,
- Size maxEvaluations) {
- return Integration(GaussLobatto,
+ AnalyticHestonEngine::Integration::gaussLobatto(
+ Real relTolerance, Real absTolerance, Size maxEvaluations, bool useConvergenceEstimate) {
+ return Integration(GaussLobatto,
ext::shared_ptr(
new GaussLobattoIntegral(maxEvaluations,
absTolerance,
relTolerance,
- false)));
+ useConvergenceEstimate)));
}
AnalyticHestonEngine::Integration
@@ -798,6 +965,13 @@ namespace QuantLib {
new DiscreteTrapezoidIntegrator(evaluations)));
}
+ AnalyticHestonEngine::Integration
+ AnalyticHestonEngine::Integration::expSinh(Real relTolerance) {
+ return Integration(
+ ExpSinh, ext::shared_ptr(
+ new ExpSinhIntegral(relTolerance)));
+ }
+
Size AnalyticHestonEngine::Integration::numberOfEvaluations() const {
if (integrator_ != nullptr) {
return integrator_->numberOfEvaluations();
@@ -812,13 +986,15 @@ namespace QuantLib {
return intAlgo_ == GaussLobatto
|| intAlgo_ == GaussKronrod
|| intAlgo_ == Simpson
- || intAlgo_ == Trapezoid;
+ || intAlgo_ == Trapezoid
+ || intAlgo_ == ExpSinh;
}
Real AnalyticHestonEngine::Integration::calculate(
Real c_inf,
const ext::function& f,
- const ext::function& maxBound) const {
+ const ext::function& maxBound,
+ const Real scaling) const {
Real retVal;
@@ -831,6 +1007,11 @@ namespace QuantLib {
case GaussChebyshev2nd:
retVal = (*gaussianQuadrature_)(integrand1(c_inf, f));
break;
+ case ExpSinh:
+ retVal = scaling*(*integrator_)(
+ [scaling, f](Real x) -> Real { return f(scaling*x);},
+ 0.0, std::numeric_limits::max());
+ break;
case Simpson:
case Trapezoid:
case GaussLobatto:
@@ -872,10 +1053,112 @@ namespace QuantLib {
const Real uMax = Brent().solve(u_Max(c_inf, epsilon),
QL_EPSILON*uMaxGuess, uMaxGuess, uMaxStep);
- const Real uHatMax = Brent().solve(uHat_Max(0.5*v0*t, epsilon),
- QL_EPSILON*std::sqrt(uMaxGuess),
- std::sqrt(uMaxGuess), 0.1*std::sqrt(uMaxGuess));
+ try {
+ const Real uHatMaxGuess = std::sqrt(-std::log(epsilon)/(0.5*v0*t));
+ const Real uHatMax = Brent().solve(uHat_Max(0.5*v0*t, epsilon),
+ QL_EPSILON*uHatMaxGuess, uHatMaxGuess, 0.001*uHatMaxGuess);
+
+ return std::max(uMax, uHatMax);
+ }
+ catch (const Error&) {
+ return uMax;
+ }
+ }
+
+
+ void AnalyticHestonEngine::doCalculation(Real riskFreeDiscount,
+ Real dividendDiscount,
+ Real spotPrice,
+ Real strikePrice,
+ Real term,
+ Real kappa, Real theta, Real sigma, Real v0, Real rho,
+ const TypePayoff& type,
+ const Integration& integration,
+ const ComplexLogFormula cpxLog,
+ const AnalyticHestonEngine* const enginePtr,
+ Real& value,
+ Size& evaluations) {
+
+ const Real ratio = riskFreeDiscount/dividendDiscount;
+
+ evaluations = 0;
+
+ switch(cpxLog) {
+ case Gatheral:
+ case BranchCorrection: {
+ const Real c_inf = std::min(0.2, std::max(0.0001,
+ std::sqrt(1.0-rho*rho)/sigma))*(v0 + kappa*theta*term);
+
+ const Real p1 = integration.calculate(c_inf,
+ Fj_Helper(kappa, theta, sigma, v0, spotPrice, rho, enginePtr,
+ cpxLog, term, strikePrice, ratio, 1))/M_PI;
+ evaluations += integration.numberOfEvaluations();
+
+ const Real p2 = integration.calculate(c_inf,
+ Fj_Helper(kappa, theta, sigma, v0, spotPrice, rho, enginePtr,
+ cpxLog, term, strikePrice, ratio, 2))/M_PI;
+ evaluations += integration.numberOfEvaluations();
+
+ switch (type.optionType())
+ {
+ case Option::Call:
+ value = spotPrice*dividendDiscount*(p1+0.5)
+ - strikePrice*riskFreeDiscount*(p2+0.5);
+ break;
+ case Option::Put:
+ value = spotPrice*dividendDiscount*(p1-0.5)
+ - strikePrice*riskFreeDiscount*(p2-0.5);
+ break;
+ default:
+ QL_FAIL("unknown option type");
+ }
+ }
+ break;
+ case AndersenPiterbarg:
+ case AndersenPiterbargOptCV:
+ case AsymptoticChF:
+ case OptimalCV: {
+ const Real c_inf =
+ std::sqrt(1.0-rho*rho)*(v0 + kappa*theta*term)/sigma;
+
+ const Real fwdPrice = spotPrice / ratio;
- return std::max(uMax, uHatMax);
+ const Real epsilon = enginePtr->andersenPiterbargEpsilon_
+ *M_PI/(std::sqrt(strikePrice*fwdPrice)*riskFreeDiscount);
+
+ const ext::function uM = [&](){
+ return Integration::andersenPiterbargIntegrationLimit(c_inf, epsilon, v0, term);
+ };
+
+ AP_Helper cvHelper(term, fwdPrice, strikePrice,
+ (cpxLog == OptimalCV)
+ ? optimalControlVariate(term, v0, kappa, theta, sigma, rho)
+ : cpxLog,
+ enginePtr
+ );
+
+ const Real cvValue = cvHelper.controlVariateValue();
+
+ const Real h_cv = integration.calculate(c_inf, cvHelper, uM)
+ * std::sqrt(strikePrice * fwdPrice)/M_PI;
+ evaluations += integration.numberOfEvaluations();
+
+ switch (type.optionType())
+ {
+ case Option::Call:
+ value = (cvValue + h_cv)*riskFreeDiscount;
+ break;
+ case Option::Put:
+ value = (cvValue + h_cv - (fwdPrice - strikePrice))*riskFreeDiscount;
+ break;
+ default:
+ QL_FAIL("unknown option type");
+ }
+ }
+ break;
+
+ default:
+ QL_FAIL("unknown complex log formula");
+ }
}
}
diff --git a/ql/pricingengines/vanilla/analytichestonengine.hpp b/ql/pricingengines/vanilla/analytichestonengine.hpp
index 47e5f2f8d83..79d1a0a9784 100644
--- a/ql/pricingengines/vanilla/analytichestonengine.hpp
+++ b/ql/pricingengines/vanilla/analytichestonengine.hpp
@@ -77,6 +77,10 @@ namespace QuantLib {
Interest Rate Modeling, Volume I: Foundations and Vanilla Models,
Atlantic Financial Press London.
+ L. Andersen and M. Lake, 2018
+ Robust High-Precision Option Pricing by Fourier Transforms:
+ Contour Deformations and Double-Exponential Quadrature,
+ https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3231626
\ingroup vanillaengines
@@ -90,6 +94,9 @@ namespace QuantLib {
VanillaOption::results> {
public:
class Integration;
+ class OptimalAlpha;
+ class AP_Helper;
+
enum ComplexLogFormula {
// Gatheral form of characteristic function w/o control variate
Gatheral,
@@ -102,6 +109,10 @@ namespace QuantLib {
// Gatheral form with asymptotic expansion of the characteristic function as control variate
// https://hpcquantlib.wordpress.com/2020/08/30/a-novel-control-variate-for-the-heston-model
AsymptoticChF,
+ // angled contour shift integral with control variate
+ AngledContour,
+ // angled contour shift integral w/o control variate
+ AngledContourNoCV,
// auto selection of best control variate algorithm from above
OptimalCV
};
@@ -123,15 +134,18 @@ namespace QuantLib {
// over the Fourier integration algorithm
AnalyticHestonEngine(const ext::shared_ptr& model,
ComplexLogFormula cpxLog, const Integration& itg,
- Real andersenPiterbargEpsilon = 1e-8);
+ Real andersenPiterbargEpsilon = 1e-25,
+ Real alpha = -0.5);
+
+ void calculate() const override;
// normalized characteristic function
std::complex chF(const std::complex& z, Time t) const;
std::complex lnChF(const std::complex& z, Time t) const;
- void calculate() const override;
Size numberOfEvaluations() const;
+ [[deprecated("Use AnalyticHestonEngine::priceVanillaPayoff instead.")]]
static void doCalculation(Real riskFreeDiscount,
Real dividendDiscount,
Real spotPrice,
@@ -149,29 +163,16 @@ namespace QuantLib {
Real& value,
Size& evaluations);
+ Real priceVanillaPayoff(
+ const ext::shared_ptr& payoff,
+ const Date& maturity) const;
+
+ Real priceVanillaPayoff(
+ const ext::shared_ptr& payoff, Time maturity) const;
+
static ComplexLogFormula optimalControlVariate(
Time t, Real v0, Real kappa, Real theta, Real sigma, Real rho);
- class AP_Helper {
- public:
- AP_Helper(Time term,
- Real fwd,
- Real strike,
- ComplexLogFormula cpxLog,
- const AnalyticHestonEngine* enginePtr);
-
- Real operator()(Real u) const;
- Real controlVariateValue() const;
-
- private:
- const Time term_;
- const Real fwd_, strike_, freq_;
- const ComplexLogFormula cpxLog_;
- const AnalyticHestonEngine* const enginePtr_;
- Real vAvg_;
- std::complex phi_, psi_;
- };
-
protected:
// call back for extended stochastic volatility
// plus jump diffusion engines like bates model
@@ -182,10 +183,15 @@ namespace QuantLib {
private:
class Fj_Helper;
+ Real priceVanillaPayoff(
+ const ext::shared_ptr& payoff,
+ const Time maturity, Real fwd) const;
+
+
mutable Size evaluations_;
const ComplexLogFormula cpxLog_;
const ext::shared_ptr integration_;
- const Real andersenPiterbargEpsilon_;
+ const Real andersenPiterbargEpsilon_, alpha_;
};
@@ -197,12 +203,13 @@ namespace QuantLib {
static Integration gaussChebyshev (Size integrationOrder = 128);
static Integration gaussChebyshev2nd(Size integrationOrder = 128);
- // for an adaptive integration algorithm Gatheral's version has to
- // be used.Be aware: using a too large number for maxEvaluations might
+ // Gatheral's version has to be used for an adaptive integration
+ // algorithm .Be aware: using a too large number for maxEvaluations might
// result in a stack overflow as the these integrations are based on
// recursive algorithms.
static Integration gaussLobatto(Real relTolerance, Real absTolerance,
- Size maxEvaluations = 1000);
+ Size maxEvaluations = 1000,
+ bool useConvergenceEstimate = false);
// usually these routines have a poor convergence behavior.
static Integration gaussKronrod(Real absTolerance,
@@ -213,13 +220,15 @@ namespace QuantLib {
Size maxEvaluations = 1000);
static Integration discreteSimpson(Size evaluation = 1000);
static Integration discreteTrapezoid(Size evaluation = 1000);
+ static Integration expSinh(Real relTolerance = 1e-8);
static Real andersenPiterbargIntegrationLimit(
Real c_inf, Real epsilon, Real v0, Real t);
Real calculate(Real c_inf,
const ext::function& f,
- const ext::function& maxBound = {}) const;
+ const ext::function& maxBound = {},
+ Real scaling = 1.0) const;
Real calculate(Real c_inf,
const ext::function& f,
@@ -233,7 +242,8 @@ namespace QuantLib {
{ GaussLobatto, GaussKronrod, Simpson, Trapezoid,
DiscreteTrapezoid, DiscreteSimpson,
GaussLaguerre, GaussLegendre,
- GaussChebyshev, GaussChebyshev2nd };
+ GaussChebyshev, GaussChebyshev2nd,
+ ExpSinh};
Integration(Algorithm intAlgo, ext::shared_ptr quadrature);
@@ -244,12 +254,58 @@ namespace QuantLib {
const ext::shared_ptr gaussianQuadrature_;
};
- // inline
+ class AnalyticHestonEngine::AP_Helper {
+ public:
+ AP_Helper(Time term, Real fwd, Real strike,
+ ComplexLogFormula cpxLog,
+ const AnalyticHestonEngine* enginePtr,
+ const Real alpha = -0.5);
+
+ Real operator()(Real u) const;
+ Real controlVariateValue() const;
+
+ private:
+ const Time term_;
+ const Real fwd_, strike_, freq_;
+ const ComplexLogFormula cpxLog_;
+ const AnalyticHestonEngine* const enginePtr_;
+ const Real alpha_, s_alpha_;
+ Real vAvg_, tanPhi_;
+ std::complex phi_, psi_;
+ };
+
+
+ class AnalyticHestonEngine::OptimalAlpha {
+ public:
+ OptimalAlpha(
+ const Time t,
+ const AnalyticHestonEngine* const enginePtr);
+
+ Real operator()(Real strike) const;
+ std::pair alphaGreaterZero(Real strike) const;
+ std::pair alphaSmallerMinusOne(Real strike) const;
+
+ Size numberOfEvaluations() const;
+ Real M(Real k) const;
+ Real k(Real x, Integer sgn) const;
+ Real alphaMin(Real strike) const;
+ Real alphaMax(Real strike) const;
+
+ private:
+ std::pair findMinima(Real lower, Real upper, Real strike) const;
+
+ const Real t_, fwd_, kappa_, theta_, sigma_, rho_;
+
+ const Real eps_;
+
+ const AnalyticHestonEngine* const enginePtr_;
+ Real km_, kp_;
+ mutable Size evaluations_;
+ };
+
- inline
- std::complex AnalyticHestonEngine::addOnTerm(Real,
- Time,
- Size) const {
+ inline std::complex AnalyticHestonEngine::addOnTerm(
+ Real, Time, Size) const {
return std::complex(0,0);
}
}
diff --git a/ql/pricingengines/vanilla/analytichestonhullwhiteengine.cpp b/ql/pricingengines/vanilla/analytichestonhullwhiteengine.cpp
index 86195faef15..80481c8bae8 100644
--- a/ql/pricingengines/vanilla/analytichestonhullwhiteengine.cpp
+++ b/ql/pricingengines/vanilla/analytichestonhullwhiteengine.cpp
@@ -27,7 +27,9 @@ namespace QuantLib {
const ext::shared_ptr& hestonModel,
ext::shared_ptr hullWhiteModel,
Size integrationOrder)
- : AnalyticHestonEngine(hestonModel, integrationOrder),
+ : AnalyticHestonEngine(
+ hestonModel, AnalyticHestonEngine::Gatheral,
+ AnalyticHestonEngine::Integration::gaussLaguerre(integrationOrder)),
hullWhiteModel_(std::move(hullWhiteModel)) {
setParameters();
registerWith(hullWhiteModel_);
@@ -38,7 +40,10 @@ namespace QuantLib {
ext::shared_ptr hullWhiteModel,
Real relTolerance,
Size maxEvaluations)
- : AnalyticHestonEngine(hestonModel, relTolerance, maxEvaluations),
+ : AnalyticHestonEngine(
+ hestonModel, AnalyticHestonEngine::Gatheral,
+ AnalyticHestonEngine::Integration::gaussLobatto(
+ relTolerance, Null(), maxEvaluations)),
hullWhiteModel_(std::move(hullWhiteModel)) {
setParameters();
registerWith(hullWhiteModel_);
diff --git a/ql/pricingengines/vanilla/batesengine.cpp b/ql/pricingengines/vanilla/batesengine.cpp
index eb23662fd84..54068dee713 100644
--- a/ql/pricingengines/vanilla/batesengine.cpp
+++ b/ql/pricingengines/vanilla/batesengine.cpp
@@ -25,11 +25,16 @@ namespace QuantLib {
BatesEngine::BatesEngine(const ext::shared_ptr & model,
Size integrationOrder)
- : AnalyticHestonEngine(model, integrationOrder) { }
+ : AnalyticHestonEngine(
+ model, AnalyticHestonEngine::Gatheral,
+ AnalyticHestonEngine::Integration::gaussLaguerre(integrationOrder)) { }
BatesEngine::BatesEngine(const ext::shared_ptr& model,
Real relTolerance, Size maxEvaluations)
- : AnalyticHestonEngine(model, relTolerance, maxEvaluations) { }
+ : AnalyticHestonEngine(
+ model, AnalyticHestonEngine::Gatheral,
+ AnalyticHestonEngine::Integration::gaussLobatto(
+ relTolerance, Null(), maxEvaluations)) { }
std::complex BatesEngine::addOnTerm(
Real phi, Time t, Size j) const {
@@ -81,12 +86,17 @@ namespace QuantLib {
BatesDoubleExpEngine::BatesDoubleExpEngine(
const ext::shared_ptr & model,
Size integrationOrder)
- : AnalyticHestonEngine(model, integrationOrder) { }
+ : AnalyticHestonEngine(
+ model, AnalyticHestonEngine::Gatheral,
+ AnalyticHestonEngine::Integration::gaussLaguerre(integrationOrder)) { }
BatesDoubleExpEngine::BatesDoubleExpEngine(
const ext::shared_ptr& model,
Real relTolerance, Size maxEvaluations)
- : AnalyticHestonEngine(model, relTolerance, maxEvaluations) { }
+ : AnalyticHestonEngine(
+ model, AnalyticHestonEngine::Gatheral,
+ AnalyticHestonEngine::Integration::gaussLobatto(
+ relTolerance, Null(), maxEvaluations)) { }
std::complex BatesDoubleExpEngine::addOnTerm(
Real phi, Time t, Size j) const {
diff --git a/ql/pricingengines/vanilla/exponentialfittinghestonengine.cpp b/ql/pricingengines/vanilla/exponentialfittinghestonengine.cpp
index f80d148e8c7..d9a69af46fc 100644
--- a/ql/pricingengines/vanilla/exponentialfittinghestonengine.cpp
+++ b/ql/pricingengines/vanilla/exponentialfittinghestonengine.cpp
@@ -23,7 +23,8 @@
#include
#include
#include
-#include
+#include
+#include
#include