Skip to content

Commit 42c3a24

Browse files
authored
Heston Model: Support for Angled Contour Shift Integrals (lballabio#1826)
2 parents 7cd3808 + c1acc8b commit 42c3a24

32 files changed

+1434
-583
lines changed

QuantLib.vcxproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,7 @@
10111011
<ClInclude Include="ql\math\distributions\poissondistribution.hpp" />
10121012
<ClInclude Include="ql\math\distributions\studenttdistribution.hpp" />
10131013
<ClInclude Include="ql\math\errorfunction.hpp" />
1014+
<ClInclude Include="ql\math\expm1.hpp" />
10141015
<ClInclude Include="ql\math\factorial.hpp" />
10151016
<ClInclude Include="ql\math\fastfouriertransform.hpp" />
10161017
<ClInclude Include="ql\math\functional.hpp" />
@@ -1019,6 +1020,7 @@
10191020
<ClInclude Include="ql\math\integrals\all.hpp" />
10201021
<ClInclude Include="ql\math\integrals\discreteintegrals.hpp" />
10211022
<ClInclude Include="ql\math\integrals\exponentialintegrals.hpp" />
1023+
<ClInclude Include="ql\math\integrals\expsinhintegral.hpp" />
10221024
<ClInclude Include="ql\math\integrals\filonintegral.hpp" />
10231025
<ClInclude Include="ql\math\integrals\gaussianorthogonalpolynomial.hpp" />
10241026
<ClInclude Include="ql\math\integrals\gaussianquadratures.hpp" />
@@ -2260,6 +2262,7 @@
22602262
<ClCompile Include="ql\math\distributions\normaldistribution.cpp" />
22612263
<ClCompile Include="ql\math\distributions\studenttdistribution.cpp" />
22622264
<ClCompile Include="ql\math\errorfunction.cpp" />
2265+
<ClCompile Include="ql\math\expm1.cpp" />
22632266
<ClCompile Include="ql\math\factorial.cpp" />
22642267
<ClCompile Include="ql\math\incompletegamma.cpp" />
22652268
<ClCompile Include="ql\math\integrals\discreteintegrals.cpp" />

QuantLib.vcxproj.filters

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,9 @@
987987
<ClInclude Include="ql\math\errorfunction.hpp">
988988
<Filter>math</Filter>
989989
</ClInclude>
990+
<ClInclude Include="ql\math\expm1.hpp">
991+
<Filter>math</Filter>
992+
</ClInclude>
990993
<ClInclude Include="ql\math\factorial.hpp">
991994
<Filter>math</Filter>
992995
</ClInclude>
@@ -1164,6 +1167,9 @@
11641167
<ClInclude Include="ql\math\integrals\discreteintegrals.hpp">
11651168
<Filter>math\integrals</Filter>
11661169
</ClInclude>
1170+
<ClInclude Include="ql\math\integrals\expsinhintegral.hpp">
1171+
<Filter>math\integrals</Filter>
1172+
</ClInclude>
11671173
<ClInclude Include="ql\math\integrals\filonintegral.hpp">
11681174
<Filter>math\integrals</Filter>
11691175
</ClInclude>
@@ -4850,6 +4856,9 @@
48504856
<ClCompile Include="ql\math\errorfunction.cpp">
48514857
<Filter>math</Filter>
48524858
</ClCompile>
4859+
<ClCompile Include="ql\math\expm1.cpp">
4860+
<Filter>math</Filter>
4861+
</ClCompile>
48534862
<ClCompile Include="ql\math\factorial.cpp">
48544863
<Filter>math</Filter>
48554864
</ClCompile>

ql/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ set(QL_SOURCES
364364
math/distributions/normaldistribution.cpp
365365
math/distributions/studenttdistribution.cpp
366366
math/errorfunction.cpp
367+
math/expm1.cpp
367368
math/factorial.cpp
368369
math/incompletegamma.cpp
369370
math/integrals/discreteintegrals.cpp
@@ -1426,13 +1427,15 @@ set(QL_HEADERS
14261427
math/distributions/poissondistribution.hpp
14271428
math/distributions/studenttdistribution.hpp
14281429
math/errorfunction.hpp
1430+
math/expm1.hpp
14291431
math/factorial.hpp
14301432
math/fastfouriertransform.hpp
14311433
math/functional.hpp
14321434
math/generallinearleastsquares.hpp
14331435
math/incompletegamma.hpp
14341436
math/integrals/discreteintegrals.hpp
14351437
math/integrals/exponentialintegrals.hpp
1438+
math/integrals/expsinhintegral.hpp
14361439
math/integrals/filonintegral.hpp
14371440
math/integrals/gaussianorthogonalpolynomial.hpp
14381441
math/integrals/gaussianquadratures.hpp

ql/math/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ this_include_HEADERS = \
1717
comparison.hpp \
1818
curve.hpp \
1919
errorfunction.hpp \
20+
expm1.hpp \
2021
factorial.hpp \
2122
fastfouriertransform.hpp \
2223
functional.hpp \
@@ -44,6 +45,7 @@ cpp_files = \
4445
beta.cpp \
4546
bspline.cpp \
4647
errorfunction.cpp \
48+
expm1.cpp \
4749
factorial.cpp \
4850
incompletegamma.cpp \
4951
matrix.cpp \

ql/math/all.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <ql/math/bspline.hpp>
1010
#include <ql/math/comparison.hpp>
1111
#include <ql/math/errorfunction.hpp>
12+
#include <ql/math/expm1.hpp>
1213
#include <ql/math/factorial.hpp>
1314
#include <ql/math/fastfouriertransform.hpp>
1415
#include <ql/math/functional.hpp>

ql/math/expm1.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2+
3+
/*
4+
Copyright (C) 2023 Klaus Spanderen
5+
6+
This file is part of QuantLib, a free-software/open-source library
7+
for financial quantitative analysts and developers - http://quantlib.org/
8+
9+
QuantLib is free software: you can redistribute it and/or modify it
10+
under the terms of the QuantLib license. You should have received a
11+
copy of the license along with this program; if not, please email
12+
<quantlib-dev@lists.sf.net>. The license is also available online at
13+
<http://quantlib.org/license.shtml>.
14+
15+
This program is distributed in the hope that it will be useful, but WITHOUT
16+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17+
FOR A PARTICULAR PURPOSE. See the license for more details.
18+
*/
19+
20+
#include <ql/math/expm1.hpp>
21+
#include <ql/math/functional.hpp>
22+
23+
#include <cmath>
24+
25+
namespace QuantLib {
26+
std::complex<Real> expm1(const std::complex<Real>& z) {
27+
if (std::abs(z) < 1.0) {
28+
const Real a = z.real(), b = z.imag();
29+
const Real exp_1 = std::expm1(a);
30+
const Real cos_1 = -2*squared(std::sin(0.5*b));
31+
32+
return std::complex<Real>(
33+
exp_1*cos_1 + exp_1 + cos_1,
34+
std::sin(b)*std::exp(a)
35+
);
36+
}
37+
else {
38+
return std::exp(z)-1.0;
39+
}
40+
}
41+
42+
std::complex<Real> log1p(const std::complex<Real>& z) {
43+
const Real a = z.real(), b = z.imag();
44+
if (std::abs(a) < 0.5 && std::abs(b) < 0.5) {
45+
return std::complex<Real>(
46+
0.5*std::log1p(a*a + 2*a + b*b),
47+
std::arg(1.0 + z)
48+
);
49+
}
50+
else {
51+
return std::log(1.0+z);
52+
}
53+
}
54+
}

ql/math/expm1.hpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2+
3+
/*
4+
Copyright (C) 2023 Klaus Spanderen
5+
6+
This file is part of QuantLib, a free-software/open-source library
7+
for financial quantitative analysts and developers - http://quantlib.org/
8+
9+
QuantLib is free software: you can redistribute it and/or modify it
10+
under the terms of the QuantLib license. You should have received a
11+
copy of the license along with this program; if not, please email
12+
<quantlib-dev@lists.sf.net>. The license is also available online at
13+
<http://quantlib.org/license.shtml>.
14+
15+
This program is distributed in the hope that it will be useful, but WITHOUT
16+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17+
FOR A PARTICULAR PURPOSE. See the license for more details.
18+
*/
19+
20+
/*! \file expm1.hpp
21+
\brief complex versions of expm1 and logp1
22+
*/
23+
24+
#ifndef quantlib_expm1_hpp
25+
#define quantlib_expm1_hpp
26+
27+
#include <ql/types.hpp>
28+
#include <complex>
29+
30+
namespace QuantLib {
31+
std::complex<Real> expm1(const std::complex<Real>& z);
32+
std::complex<Real> log1p(const std::complex<Real>& z);
33+
}
34+
#endif
35+

ql/math/integrals/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ this_include_HEADERS = \
55
all.hpp \
66
discreteintegrals.hpp \
77
exponentialintegrals.hpp \
8+
expsinhintegral.hpp \
89
filonintegral.hpp \
910
gausslaguerrecosinepolynomial.hpp \
1011
gausslobattointegral.hpp \

ql/math/integrals/all.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <ql/math/integrals/discreteintegrals.hpp>
55
#include <ql/math/integrals/exponentialintegrals.hpp>
6+
#include <ql/math/integrals/expsinhintegral.hpp>
67
#include <ql/math/integrals/filonintegral.hpp>
78
#include <ql/math/integrals/gausslaguerrecosinepolynomial.hpp>
89
#include <ql/math/integrals/gausslobattointegral.hpp>

ql/math/integrals/exponentialintegrals.cpp

Lines changed: 99 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@
2323

2424
#include <ql/errors.hpp>
2525
#include <ql/mathconstants.hpp>
26+
#include <ql/math/comparison.hpp>
2627
#include <ql/math/integrals/exponentialintegrals.hpp>
2728

29+
#include <boost/math/special_functions/sign.hpp>
2830
#include <cmath>
2931

30-
#ifndef M_EULER_MASCHERONI
31-
#define M_EULER_MASCHERONI 0.5772156649015328606065121
32-
#endif
33-
3432
namespace QuantLib {
3533
namespace exponential_integrals_helper {
3634

@@ -122,28 +120,61 @@ namespace QuantLib {
122120
}
123121
}
124122

123+
std::complex<Real> _Ei(
124+
const std::complex<Real>& z, const std::complex<Real>& acc) {
125125

126-
std::complex<Real> E1(std::complex<Real> z) {
127-
QL_REQUIRE(std::abs(z) <= 25.0, "Insufficient precision for |z| > 25.0");
126+
if (z.real() == 0.0 && z.imag() == 0.0
127+
&& std::numeric_limits<Real>::has_infinity) {
128+
return std::complex<Real>(-std::numeric_limits<Real>::infinity());
129+
}
128130

129-
std::complex<Real> s(0.0), sn(-z);
130131

131-
Size n;
132-
for (n=2; n < 1000 && s + sn/Real(n-1) != s; ++n) {
133-
s+=sn/Real(n-1);
134-
sn *= -z/Real(n);
135-
}
132+
constexpr Real DIST = 4.5;
133+
constexpr Real MAX_ERROR = 5*QL_EPSILON;
136134

137-
QL_REQUIRE(n < 1000, "series conversion issue");
135+
const Real z_inf = std::log(0.01*QL_MAX_REAL) + std::log(100.0);
136+
QL_REQUIRE(z.real() < z_inf, "argument error " << z);
138137

139-
return -M_EULER_MASCHERONI - std::log(z) -s;
140-
}
138+
const Real z_asym = 2.0 - 1.035*std::log(MAX_ERROR);
141139

142-
std::complex<Real> Ei(std::complex<Real> z) {
143-
QL_REQUIRE(std::abs(z) <= 25.0, "Insufficient precision for |z| > 25.0");
140+
using boost::math::sign;
141+
const Real abs_z = std::abs(z);
144142

145-
std::complex<Real> s(0.0), sn=z;
143+
const auto match = [=](
144+
const std::complex<Real>& z1, const std::complex<Real>& z2)
145+
-> bool {
146+
const std::complex<Real> d = z1 - z2;
147+
return std::abs(d.real()) <= MAX_ERROR*std::abs(z1.real())
148+
&& std::abs(d.imag()) <= MAX_ERROR*std::abs(z1.imag());
149+
};
150+
151+
if (z.real() > z_inf)
152+
return std::complex<Real>(std::exp(z)/z) + acc;
146153

154+
if (abs_z > 1.1*z_asym) {
155+
std::complex<Real> ei = acc + std::complex<Real>(Real(0.0), sign(z.imag())*M_PI);
156+
std::complex<Real> s(std::exp(z)/z);
157+
for (Size i=1; i <= std::floor(abs_z)+1; ++i) {
158+
if (match(ei+s, ei))
159+
return ei+s;
160+
ei += s;
161+
s *= Real(i)/z;
162+
}
163+
QL_FAIL("series conversion issue for Ei(" << z << ")");
164+
}
165+
166+
if (abs_z > DIST && (z.real() < 0 || std::abs(z.imag()) > DIST)) {
167+
std::complex<Real> ei(0.0);
168+
for (Size k = 47; k >=1; --k) {
169+
ei = - Real(k*k)/(2.0*k + 1.0 - z + ei);
170+
}
171+
return (acc + std::complex<Real>(0.0, sign(z.imag())*M_PI))
172+
- std::exp(z)/ (1.0 - z + ei);
173+
174+
QL_FAIL("series conversion issue for Ei(" << z << ")");
175+
}
176+
177+
std::complex<Real> s(0.0), sn=z;
147178
Real nn=1.0;
148179

149180
Size n;
@@ -156,27 +187,65 @@ namespace QuantLib {
156187
sn *= -z / Real(2*n);
157188
}
158189

159-
QL_REQUIRE(n < 1000, "series conversion issue");
190+
QL_REQUIRE(n < 1000, "series conversion issue for Ei(" << z << ")");
191+
192+
const std::complex<Real> r
193+
= (M_EULER_MASCHERONI + acc) + std::log(z) + std::exp(0.5*z)*s;
160194

161-
return M_EULER_MASCHERONI + std::log(z) + std::exp(0.5*z)*s;
195+
if (z.imag() != Real(0.0))
196+
return r;
197+
else
198+
return std::complex<Real>(r.real(), acc.imag());
199+
}
200+
201+
std::complex<Real> Ei(const std::complex<Real>&z) {
202+
return _Ei(z, std::complex<Real>(0.0));
203+
}
204+
205+
std::complex<Real> E1(const std::complex<Real>& z) {
206+
if (z.imag() < 0.0) {
207+
return -_Ei(-z, std::complex<Real>(0.0, -M_PI));
208+
}
209+
else if (z.imag() > 0.0 || z.real() < 0.0) {
210+
return -_Ei(-z, std::complex<Real>(0.0, M_PI));
211+
}
212+
else {
213+
return -Ei(-z);
214+
}
162215
}
163216

164217
// Reference:
165218
// https://functions.wolfram.com/GammaBetaErf/ExpIntegralEi/introductions/ExpIntegrals/ShowAll.html
166-
std::complex<Real> Si(std::complex<Real> z) {
167-
const std::complex<Real> i(0.0, 1.0);
168-
169-
return 0.25*i*(2.0*(Ei(-i*z) - Ei(i*z))
170-
+ std::log(i/z) - std::log(-i/z) - std::log(-i*z)
171-
+ std::log(i*z));
219+
std::complex<Real> Si(const std::complex<Real>& z) {
220+
if (std::abs(z) <= 0.2) {
221+
std::complex<Real> s(0), nn(z);
222+
Size k;
223+
for (k=2; k < 100 && s != s+nn; ++k) {
224+
s += nn;
225+
nn *= -z*z/((2.0*k-2)*(2*k-1)*(2*k-1))*(2.0*k-3);
226+
}
227+
QL_REQUIRE(k < 100, "series conversion issue for Si(" << z << ")");
228+
229+
return s;
230+
}
231+
else {
232+
const std::complex<Real> i(0.0, 1.0);
233+
return 0.5*i*(E1(-i*z) - E1(i*z)
234+
- std::complex<Real>(0.0, ((z.real() >= 0 && z.imag() >= 0)
235+
|| (z.real() > 0 && z.imag() < 0) )? M_PI : -M_PI));
236+
}
172237
}
173238

174-
std::complex<Real> Ci(std::complex<Real> z) {
239+
std::complex<Real> Ci(const std::complex<Real>& z) {
175240
const std::complex<Real> i(0.0, 1.0);
176241

177-
return 0.25*(2.0*(Ei(-i*z) + Ei(i*z))
178-
+ std::log(i/z) + std::log(-i/z) - std::log(-i*z)
179-
- std::log(i*z)) + std::log(z);
242+
std::complex<Real> acc(0.0);
243+
if (z.real() < 0.0 && z.imag() >= 0.0)
244+
acc.imag(M_PI);
245+
else if (z.real() <= 0.0 && z.imag() <= 0.0)
246+
acc.imag(-M_PI);
247+
248+
return -0.5*(E1(-i*z) + E1(i*z)) + acc;
180249
}
181250
}
182251
}

0 commit comments

Comments
 (0)