Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heston Model: Support for Angled Contour Shift Integrals #1826

Merged
merged 41 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c7efbd6
first light from Heston update
klausspanderen Jul 3, 2023
46011bd
first light from Heston update
klausspanderen Jul 3, 2023
f516ebd
added missing file to project definition
klausspanderen Jul 3, 2023
cb4c3aa
improve windows build
klausspanderen Jul 3, 2023
33fc781
refactoring: removed doCalculation from AnalyticHestonEngine
klausspanderen Jul 3, 2023
2dbd08b
refactoring: removed unused Fp_Helper constructors
klausspanderen Jul 3, 2023
018df35
secure state
klausspanderen Aug 10, 2023
490c963
secure state
klausspanderen Aug 10, 2023
539c162
Merge remote-tracking branch 'origin/heston_update' into heston_update
klausspanderen Aug 10, 2023
c1a9ee4
Merge remote-tracking branch 'origin/heston_update' into heston_update
klausspanderen Sep 20, 2023
7f04a49
finish work on optimal alpha
klausspanderen Sep 20, 2023
360f8da
make alpha optional
klausspanderen Sep 20, 2023
f12e6a6
fixed test case on mac os
klausspanderen Sep 21, 2023
cc7ec7c
Merge remote-tracking branch 'origin/heston_update' into heston_update
klausspanderen Oct 6, 2023
a85fec3
improve Hestson integration
klausspanderen Oct 6, 2023
1070869
fixed windows build
klausspanderen Oct 10, 2023
b89e3c0
fixed test case
klausspanderen Oct 10, 2023
74b6f3d
fixed test case
klausspanderen Oct 11, 2023
306fff1
numbers are alright for order 64
klausspanderen Oct 11, 2023
f66acb5
.
klausspanderen Oct 11, 2023
445c6dd
change default to angled contour integration
klausspanderen Nov 5, 2023
d09386c
.
klausspanderen Nov 6, 2023
d5a0711
Merge remote-tracking branch 'origin/heston_update' into heston_update
klausspanderen Nov 6, 2023
0c6cfb1
removed useless includes
klausspanderen Nov 6, 2023
078e5de
removed commented out code
klausspanderen Nov 6, 2023
3b708e9
moved test to fast section
klausspanderen Nov 6, 2023
2de91c2
fixed explicit constructor
klausspanderen Nov 6, 2023
b0be193
update papi documentation and initialization
klausspanderen Nov 16, 2023
53863ae
merge with head
klausspanderen Nov 19, 2023
89a6948
fixed parallel benchmark
klausspanderen Nov 19, 2023
96f9c02
Merge remote-tracking branch 'origin/heston_update' into heston_update
klausspanderen Nov 25, 2023
95731b2
use contour integral also for asymptotic control variate
klausspanderen Nov 25, 2023
fd23f18
parameter fine-tuning
klausspanderen Nov 26, 2023
bc9ae04
eliminate separate enum
klausspanderen Dec 17, 2023
2dfb5b3
bring back deprecated function
klausspanderen Dec 17, 2023
7b7553a
merge
klausspanderen Dec 17, 2023
d98f85a
merge
klausspanderen Dec 17, 2023
604308a
merge
klausspanderen Dec 17, 2023
c106e66
merge
klausspanderen Dec 17, 2023
57666f2
fixed QL_REQUIRE condition
klausspanderen Dec 19, 2023
c1acc8b
Remove executable bit from source file
lballabio Dec 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions QuantLib.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,7 @@
<ClInclude Include="ql\math\distributions\poissondistribution.hpp" />
<ClInclude Include="ql\math\distributions\studenttdistribution.hpp" />
<ClInclude Include="ql\math\errorfunction.hpp" />
<ClInclude Include="ql\math\expm1.hpp" />
<ClInclude Include="ql\math\factorial.hpp" />
<ClInclude Include="ql\math\fastfouriertransform.hpp" />
<ClInclude Include="ql\math\functional.hpp" />
Expand All @@ -1019,6 +1020,7 @@
<ClInclude Include="ql\math\integrals\all.hpp" />
<ClInclude Include="ql\math\integrals\discreteintegrals.hpp" />
<ClInclude Include="ql\math\integrals\exponentialintegrals.hpp" />
<ClInclude Include="ql\math\integrals\expsinhintegral.hpp" />
<ClInclude Include="ql\math\integrals\filonintegral.hpp" />
<ClInclude Include="ql\math\integrals\gaussianorthogonalpolynomial.hpp" />
<ClInclude Include="ql\math\integrals\gaussianquadratures.hpp" />
Expand Down Expand Up @@ -2259,6 +2261,7 @@
<ClCompile Include="ql\math\distributions\normaldistribution.cpp" />
<ClCompile Include="ql\math\distributions\studenttdistribution.cpp" />
<ClCompile Include="ql\math\errorfunction.cpp" />
<ClCompile Include="ql\math\expm1.cpp" />
<ClCompile Include="ql\math\factorial.cpp" />
<ClCompile Include="ql\math\incompletegamma.cpp" />
<ClCompile Include="ql\math\integrals\discreteintegrals.cpp" />
Expand Down
9 changes: 9 additions & 0 deletions QuantLib.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,9 @@
<ClInclude Include="ql\math\errorfunction.hpp">
<Filter>math</Filter>
</ClInclude>
<ClInclude Include="ql\math\expm1.hpp">
<Filter>math</Filter>
</ClInclude>
<ClInclude Include="ql\math\factorial.hpp">
<Filter>math</Filter>
</ClInclude>
Expand Down Expand Up @@ -1164,6 +1167,9 @@
<ClInclude Include="ql\math\integrals\discreteintegrals.hpp">
<Filter>math\integrals</Filter>
</ClInclude>
<ClInclude Include="ql\math\integrals\expsinhintegral.hpp">
<Filter>math\integrals</Filter>
</ClInclude>
<ClInclude Include="ql\math\integrals\filonintegral.hpp">
<Filter>math\integrals</Filter>
</ClInclude>
Expand Down Expand Up @@ -4847,6 +4853,9 @@
<ClCompile Include="ql\math\errorfunction.cpp">
<Filter>math</Filter>
</ClCompile>
<ClCompile Include="ql\math\expm1.cpp">
<Filter>math</Filter>
</ClCompile>
<ClCompile Include="ql\math\factorial.cpp">
<Filter>math</Filter>
</ClCompile>
Expand Down
3 changes: 3 additions & 0 deletions ql/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1425,13 +1426,15 @@ 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
math/generallinearleastsquares.hpp
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
Expand Down
2 changes: 2 additions & 0 deletions ql/math/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ this_include_HEADERS = \
comparison.hpp \
curve.hpp \
errorfunction.hpp \
expm1.hpp \
factorial.hpp \
fastfouriertransform.hpp \
functional.hpp \
Expand Down Expand Up @@ -44,6 +45,7 @@ cpp_files = \
beta.cpp \
bspline.cpp \
errorfunction.cpp \
expm1.cpp \
factorial.cpp \
incompletegamma.cpp \
matrix.cpp \
Expand Down
1 change: 1 addition & 0 deletions ql/math/all.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <ql/math/bspline.hpp>
#include <ql/math/comparison.hpp>
#include <ql/math/errorfunction.hpp>
#include <ql/math/expm1.hpp>
#include <ql/math/factorial.hpp>
#include <ql/math/fastfouriertransform.hpp>
#include <ql/math/functional.hpp>
Expand Down
54 changes: 54 additions & 0 deletions ql/math/expm1.cpp
Original file line number Diff line number Diff line change
@@ -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
<[email protected]>. The license is also available online at
<http://quantlib.org/license.shtml>.

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 <ql/math/expm1.hpp>
#include <ql/math/functional.hpp>

#include <cmath>

namespace QuantLib {
std::complex<Real> expm1(const std::complex<Real>& z) {

This comment was marked as resolved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These implementations are for real arguments. Without further proof I'd be rather cautious to use the Pde-approximations in the complex plane.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct.
I found this stackoverflow answer giving the same formula. Regard this thread as void if you want.

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<Real>(
exp_1*cos_1 + exp_1 + cos_1,
std::sin(b)*std::exp(a)
);
}
else {
return std::exp(z)-1.0;
}
}

std::complex<Real> log1p(const std::complex<Real>& z) {
const Real a = z.real(), b = z.imag();
if (std::abs(a) < 0.5 && std::abs(b) < 0.5) {
return std::complex<Real>(
0.5*std::log1p(a*a + 2*a + b*b),
std::arg(1.0 + z)
);
}
else {
return std::log(1.0+z);
}
}
}
35 changes: 35 additions & 0 deletions ql/math/expm1.hpp
Original file line number Diff line number Diff line change
@@ -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
<[email protected]>. The license is also available online at
<http://quantlib.org/license.shtml>.

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 <ql/types.hpp>
#include <complex>

namespace QuantLib {
std::complex<Real> expm1(const std::complex<Real>& z);
std::complex<Real> log1p(const std::complex<Real>& z);
}
#endif

1 change: 1 addition & 0 deletions ql/math/integrals/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ this_include_HEADERS = \
all.hpp \
discreteintegrals.hpp \
exponentialintegrals.hpp \
expsinhintegral.hpp \
filonintegral.hpp \
gausslaguerrecosinepolynomial.hpp \
gausslobattointegral.hpp \
Expand Down
1 change: 1 addition & 0 deletions ql/math/integrals/all.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <ql/math/integrals/discreteintegrals.hpp>
#include <ql/math/integrals/exponentialintegrals.hpp>
#include <ql/math/integrals/expsinhintegral.hpp>
#include <ql/math/integrals/filonintegral.hpp>
#include <ql/math/integrals/gausslaguerrecosinepolynomial.hpp>
#include <ql/math/integrals/gausslobattointegral.hpp>
Expand Down
129 changes: 99 additions & 30 deletions ql/math/integrals/exponentialintegrals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,12 @@

#include <ql/errors.hpp>
#include <ql/mathconstants.hpp>
#include <ql/math/comparison.hpp>
#include <ql/math/integrals/exponentialintegrals.hpp>

#include <boost/math/special_functions/sign.hpp>
#include <cmath>

#ifndef M_EULER_MASCHERONI
#define M_EULER_MASCHERONI 0.5772156649015328606065121
#endif

namespace QuantLib {
namespace exponential_integrals_helper {

Expand Down Expand Up @@ -122,28 +120,61 @@ namespace QuantLib {
}
}

std::complex<Real> _Ei(
const std::complex<Real>& z, const std::complex<Real>& acc) {

std::complex<Real> E1(std::complex<Real> 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<Real>::has_infinity) {
return std::complex<Real>(-std::numeric_limits<Real>::infinity());
}

std::complex<Real> 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<Real> Ei(std::complex<Real> 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<Real> s(0.0), sn=z;
const auto match = [=](
const std::complex<Real>& z1, const std::complex<Real>& z2)
-> bool {
const std::complex<Real> 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<Real>(std::exp(z)/z) + acc;

if (abs_z > 1.1*z_asym) {
std::complex<Real> ei = acc + std::complex<Real>(Real(0.0), sign(z.imag())*M_PI);
std::complex<Real> 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<Real> 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<Real>(0.0, sign(z.imag())*M_PI))
- std::exp(z)/ (1.0 - z + ei);

QL_FAIL("series conversion issue for Ei(" << z << ")");
}

std::complex<Real> s(0.0), sn=z;
Real nn=1.0;

Size n;
Expand All @@ -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<Real> 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<Real>(r.real(), acc.imag());
}

std::complex<Real> Ei(const std::complex<Real>&z) {
return _Ei(z, std::complex<Real>(0.0));
}

std::complex<Real> E1(const std::complex<Real>& z) {
if (z.imag() < 0.0) {
return -_Ei(-z, std::complex<Real>(0.0, -M_PI));
}
else if (z.imag() > 0.0 || z.real() < 0.0) {
return -_Ei(-z, std::complex<Real>(0.0, M_PI));
}
else {
return -Ei(-z);
}
}

// Reference:
// https://functions.wolfram.com/GammaBetaErf/ExpIntegralEi/introductions/ExpIntegrals/ShowAll.html
std::complex<Real> Si(std::complex<Real> z) {
const std::complex<Real> 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<Real> Si(const std::complex<Real>& z) {
if (std::abs(z) <= 0.2) {
std::complex<Real> 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<Real> i(0.0, 1.0);
return 0.5*i*(E1(-i*z) - E1(i*z)
- std::complex<Real>(0.0, ((z.real() >= 0 && z.imag() >= 0)
|| (z.real() > 0 && z.imag() < 0) )? M_PI : -M_PI));
}
}

std::complex<Real> Ci(std::complex<Real> z) {
std::complex<Real> Ci(const std::complex<Real>& z) {
const std::complex<Real> 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<Real> 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;
}
}
}
Loading