Coverage Report

Created: 2025-08-05 06:45

/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
}