/src/quantlib/ql/methods/finitedifferences/utilities/cevrndcalculator.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2018 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 cevrndcalculator.cpp */ |
21 | | |
22 | | #include <ql/errors.hpp> |
23 | | #include <ql/math/functional.hpp> |
24 | | #include <ql/math/solvers1d/brent.hpp> |
25 | | #include <ql/math/distributions/normaldistribution.hpp> |
26 | | #include <ql/methods/finitedifferences/utilities/cevrndcalculator.hpp> |
27 | | #include <boost/math/special_functions/gamma.hpp> |
28 | | #include <boost/math/distributions/non_central_chi_squared.hpp> |
29 | | |
30 | | namespace QuantLib { |
31 | | |
32 | | CEVRNDCalculator::CEVRNDCalculator(Real f0, Real alpha, Real beta) |
33 | 0 | : f0_(f0), |
34 | 0 | alpha_(alpha), |
35 | 0 | beta_(beta), |
36 | 0 | delta_((1.0-2.0*beta)/(1.0-beta)), |
37 | 0 | x0_(X(f0)) { |
38 | 0 | QL_REQUIRE(beta != 1.0, "beta can not be one"); |
39 | 0 | } |
40 | | |
41 | 0 | Real CEVRNDCalculator::massAtZero(Time t) const { |
42 | 0 | if (delta_ < 2.0) |
43 | 0 | return 1.0-boost::math::gamma_p(-0.5*delta_+1.0,x0_/(2.0*t)); |
44 | 0 | else |
45 | 0 | return 0.0; |
46 | 0 | } |
47 | | |
48 | 0 | Real CEVRNDCalculator::X(Real f) const { |
49 | 0 | return std::pow(f, 2.0*(1.0-beta_))/squared(alpha_*(1.0-beta_)); |
50 | 0 | } |
51 | | |
52 | 0 | Real CEVRNDCalculator::invX(Real x) const { |
53 | 0 | return std::pow(x*squared(alpha_*(1.0-beta_)), |
54 | 0 | 1.0/(2.0*(1.0-beta_))); |
55 | 0 | } |
56 | | |
57 | 0 | Real CEVRNDCalculator::pdf(Real f, Time t) const { |
58 | 0 | const Real y = X(f); |
59 | |
|
60 | 0 | if (delta_ < 2.0) { |
61 | 0 | return boost::math::pdf( |
62 | 0 | boost::math::non_central_chi_squared_distribution<Real>( |
63 | 0 | 4.0-delta_, y/t), x0_/t)/t * 2.0*(1.0-beta_)*y/f; |
64 | 0 | } |
65 | 0 | else { |
66 | 0 | return boost::math::pdf( |
67 | 0 | boost::math::non_central_chi_squared_distribution<Real>( |
68 | 0 | delta_, x0_/t), y/t)/t * 2.0*(beta_-1.0)*y/f; |
69 | 0 | } |
70 | 0 | } |
71 | | |
72 | 0 | Real CEVRNDCalculator::cdf(Real f, Time t) const { |
73 | 0 | const Real y = X(f); |
74 | |
|
75 | 0 | if (delta_ < 2.0) |
76 | 0 | return 1.0 - boost::math::cdf( |
77 | 0 | boost::math::non_central_chi_squared_distribution<Real>( |
78 | 0 | 2.0-delta_, y/t), x0_/t); |
79 | 0 | else |
80 | 0 | return 1.0 - boost::math::cdf( |
81 | 0 | boost::math::non_central_chi_squared_distribution<Real>( |
82 | 0 | delta_, x0_/t), y/t); |
83 | 0 | } |
84 | | |
85 | 0 | Real CEVRNDCalculator::sankaranApprox(Real c, Time t, Real x) const { |
86 | 0 | const Real a = x0_/t; |
87 | 0 | const Real b = 2.0 - delta_; |
88 | |
|
89 | 0 | c = std::max(c, -0.45*b); |
90 | |
|
91 | 0 | const Real h = 1 - 2*(b+c)*(b+3*c)/(3*squared(b+2*c)); |
92 | 0 | const Real p = (b+2*c)/squared(b+c); |
93 | 0 | const Real m = (h-1)*(1-3*h); |
94 | |
|
95 | 0 | const Real u = (std::pow(a/(b+c), h) - (1 + h*p*(h-1-0.5*(2-h)*m*p)))/ |
96 | 0 | (h*std::sqrt(2*p)*(1+0.5*m*p)); |
97 | |
|
98 | 0 | return u - x; |
99 | 0 | } |
100 | | |
101 | 0 | Real CEVRNDCalculator::invcdf(Real q, Time t) const { |
102 | 0 | if (delta_ < 2.0) { |
103 | 0 | if (f0_ < QL_EPSILON || q < massAtZero(t)) |
104 | 0 | return 0.0; |
105 | | |
106 | 0 | const Real x = InverseCumulativeNormal()(1-q); |
107 | |
|
108 | 0 | auto cdfApprox = [&](Real _c){ return sankaranApprox(_c, t, x); }; |
109 | |
|
110 | 0 | const Real y0 = X(f0_)/t; |
111 | |
|
112 | 0 | try { |
113 | 0 | Brent brent; |
114 | 0 | brent.setMaxEvaluations(20); |
115 | 0 | const Real guess = |
116 | 0 | invX(brent.solve(cdfApprox, 1e-8, y0, 0.02*y0) * t); |
117 | |
|
118 | 0 | return InvCDFHelper(this, guess, 1e-8, 100).inverseCDF(q, t); |
119 | 0 | } |
120 | 0 | catch (...) { |
121 | 0 | return InvCDFHelper(this, f0_, 1e-8, 100).inverseCDF(q, t); |
122 | 0 | } |
123 | 0 | } |
124 | 0 | else { |
125 | 0 | const Real x = t * boost::math::quantile( |
126 | 0 | boost::math::non_central_chi_squared_distribution<Real>( |
127 | 0 | delta_, x0_/t), 1-q); |
128 | 0 | return invX(x); |
129 | 0 | } |
130 | 0 | } |
131 | | } |