Coverage Report

Created: 2025-10-14 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/methods/finitedifferences/meshers/fdmhestonvariancemesher.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2008 Andreas Gaida
5
 Copyright (C) 2008 Ralph Schreyer
6
 Copyright (C) 2008, 2019 Klaus Spanderen
7
8
 This file is part of QuantLib, a free-software/open-source library
9
 for financial quantitative analysts and developers - http://quantlib.org/
10
11
 QuantLib is free software: you can redistribute it and/or modify it
12
 under the terms of the QuantLib license.  You should have received a
13
 copy of the license along with this program; if not, please email
14
 <quantlib-dev@lists.sf.net>. The license is also available online at
15
 <https://www.quantlib.org/license.shtml>.
16
17
 This program is distributed in the hope that it will be useful, but WITHOUT
18
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 FOR A PARTICULAR PURPOSE.  See the license for more details.
20
*/
21
22
#include <ql/math/functional.hpp>
23
#include <ql/math/distributions/normaldistribution.hpp>
24
#include <ql/math/distributions/chisquaredistribution.hpp>
25
#include <ql/math/interpolations/linearinterpolation.hpp>
26
#include <ql/math/integrals/gausslobattointegral.hpp>
27
#include <ql/termstructures/volatility/equityfx/localvoltermstructure.hpp>
28
#include <ql/methods/finitedifferences/meshers/fdmhestonvariancemesher.hpp>
29
#include <boost/accumulators/accumulators.hpp>
30
#include <boost/accumulators/statistics/mean.hpp>
31
#include <boost/accumulators/statistics/stats.hpp>
32
#include <set>
33
#include <algorithm>
34
35
namespace QuantLib {
36
37
    namespace {
38
        struct interpolated_volatility {
39
            interpolated_volatility(const std::vector<Real>& pGrid,
40
                                    const std::vector<Real>& vGrid)
41
0
            : variance(pGrid.begin(), pGrid.end(), vGrid.begin()) {}
42
0
            Real operator()(Real x) const {
43
0
                return std::sqrt(variance(x, true));
44
0
            }
45
            LinearInterpolation variance;
46
        };
47
    }
48
49
    FdmHestonVarianceMesher::FdmHestonVarianceMesher(
50
        Size size,
51
        const ext::shared_ptr<HestonProcess> & process,
52
        Time maturity, Size tAvgSteps, Real epsilon,
53
        Real mixingFactor)
54
0
        : Fdm1dMesher(size) {
55
56
0
        std::vector<Real> vGrid(size, 0.0), pGrid(size, 0.0);
57
0
        const Real mixedSigma = process->sigma()*mixingFactor;
58
0
        const Real df  = 4*process->theta()*process->kappa()/squared(mixedSigma);
59
0
        try {
60
0
            std::multiset<std::pair<Real, Real> > grid;
61
            
62
0
            for (Size l=1; l<=tAvgSteps; ++l) {
63
0
                const Real t = (maturity*l)/tAvgSteps;
64
0
                const Real ncp = 4*process->kappa()*std::exp(-process->kappa()*t)/(squared(mixedSigma)
65
0
                    *(1-std::exp(-process->kappa()*t)))*process->v0();
66
0
                const Real k = squared(mixedSigma)
67
0
                    *(1-std::exp(-process->kappa()*t))/(4*process->kappa());
68
69
0
                const Real qMin = 0.0; // v_min = 0.0;
70
0
                const Real qMax = std::max(process->v0(),
71
0
                    k*InverseNonCentralCumulativeChiSquareDistribution(
72
0
                                            df, ncp, 100, 1e-8)(1-epsilon));
73
74
0
                const Real minVStep=(qMax-qMin)/(50*size);
75
0
                Real ps,p = 0.0;
76
77
0
                Real vTmp = qMin;
78
0
                grid.insert(std::pair<Real, Real>(qMin, epsilon));
79
                
80
0
                for (Size i=1; i < size; ++i) {
81
0
                    ps = (1 - epsilon - p)/(size-i);
82
0
                    p += ps;
83
0
                    const Real tmp = k*InverseNonCentralCumulativeChiSquareDistribution(
84
0
                        df, ncp, 100, 1e-8)(p);
85
86
0
                    const Real vx = std::max(vTmp+minVStep, tmp);
87
0
                    p = NonCentralCumulativeChiSquareDistribution(df, ncp)(vx/k);
88
0
                    vTmp=vx;
89
0
                    grid.insert(std::pair<Real, Real>(vx, p));
90
0
                }
91
0
            }
92
0
            QL_REQUIRE(grid.size() == size*tAvgSteps, 
93
0
                       "something wrong with the grid size");
94
            
95
0
            const std::vector<std::pair<Real, Real> > tp(grid.begin(), grid.end());
96
97
0
            for (Size i=0; i < size; ++i) {
98
0
                const Size b = (i*tp.size())/size;
99
0
                const Size e = ((i+1)*tp.size())/size;
100
0
                for (Size j=b; j < e; ++j) {
101
0
                    vGrid[i]+=tp[j].first/(e-b);
102
0
                    pGrid[i]+=tp[j].second/(e-b);
103
0
                }
104
0
            }
105
0
        } 
106
0
        catch (const Error&) {
107
            // use default mesh
108
0
            const Real vol = mixedSigma*
109
0
                std::sqrt(process->theta()/(2*process->kappa()));
110
111
0
            const Real mean = process->theta();
112
0
            const Real upperBound = std::max(process->v0()+4*vol, mean+4*vol);
113
0
            const Real lowerBound
114
0
                = std::max(0.0, std::min(process->v0()-4*vol, mean-4*vol));
115
116
0
            for (Size i=0; i < size; ++i) {
117
0
                pGrid[i] = i/(size-1.0);
118
0
                vGrid[i] = lowerBound + i*(upperBound-lowerBound)/(size-1.0);
119
0
            }
120
0
        }
121
122
0
        Real skewHint = ((process->kappa() != 0.0) 
123
0
                ? Real(std::max(1.0, mixedSigma/process->kappa())) : 1.0);
124
125
0
        std::sort(pGrid.begin(), pGrid.end());
126
0
        volaEstimate_ = GaussLobattoIntegral(100000, 1e-4)(
127
0
            interpolated_volatility(pGrid, vGrid),
128
0
                pGrid.front(), pGrid.back())*std::pow(skewHint, 1.5);
129
130
0
        const Real v0 = process->v0();
131
0
        for (Size i=1; i<vGrid.size(); ++i) {
132
0
            if (vGrid[i-1] <= v0 && vGrid[i] >= v0) {
133
0
                if (std::fabs(vGrid[i-1] - v0) < std::fabs(vGrid[i] - v0))
134
0
                    vGrid[i-1] = v0;
135
0
                else
136
0
                    vGrid[i] = v0;
137
0
            }
138
0
        }
139
140
0
        std::copy(vGrid.begin(), vGrid.end(), locations_.begin());
141
142
0
        for (Size i=0; i < size-1; ++i) {
143
0
            dminus_[i+1] = dplus_[i] = vGrid[i+1] - vGrid[i];
144
0
        }
145
0
        dplus_.back() = dminus_.front() = Null<Real>();
146
0
    }
147
148
149
    FdmHestonLocalVolatilityVarianceMesher::FdmHestonLocalVolatilityVarianceMesher(
150
        Size size,
151
        const ext::shared_ptr<HestonProcess>& process,
152
        const ext::shared_ptr<LocalVolTermStructure>& leverageFct,
153
        Time maturity, Size tAvgSteps, Real epsilon,
154
        Real mixingFactor)
155
0
     : Fdm1dMesher(size) {
156
157
0
        const FdmHestonVarianceMesher mesher(
158
0
            size, process, maturity, tAvgSteps, epsilon, mixingFactor);
159
160
0
        for (Size i=0; i < size; ++i) {
161
0
            dplus_[i] = mesher.dplus(i);
162
0
            dminus_[i] = mesher.dminus(i);
163
0
            locations_[i] = mesher.location(i);
164
0
        }
165
166
0
        volaEstimate_ = mesher.volaEstimate();
167
168
0
        if (leverageFct != nullptr) {
169
0
            typedef boost::accumulators::accumulator_set<
170
0
                Real, boost::accumulators::stats<
171
0
                    boost::accumulators::tag::mean> >
172
0
                accumulator_set;
173
174
0
            accumulator_set acc;
175
176
0
            const Real s0 = process->s0()->value();
177
178
0
            acc(leverageFct->localVol(0, s0, true));
179
180
0
            const Handle<YieldTermStructure> rTS = process->riskFreeRate();
181
0
            const Handle<YieldTermStructure> qTS = process->dividendYield();
182
183
0
            for (Size l=1; l <= tAvgSteps; ++l) {
184
0
                const Real t = (maturity*l)/tAvgSteps;
185
0
                const Real vol = volaEstimate_ * boost::accumulators::mean(acc);
186
187
0
                const Real fwd = s0*qTS->discount(t)/rTS->discount(t);
188
189
0
                const Size sAvgSteps = 50;
190
191
0
                std::vector<Real> u(sAvgSteps), sig(sAvgSteps);
192
193
0
                for (Size i=0; i < sAvgSteps; ++i) {
194
0
                    u[i] = epsilon + ((1-2*epsilon)/(sAvgSteps-1))*i;
195
0
                    const Real x = InverseCumulativeNormal()(u[i]);
196
197
0
                    const Real gf = x*vol*std::sqrt(t);
198
0
                    const Real f = fwd*std::exp(gf);
199
200
0
                    sig[i] = squared(leverageFct->localVol(t, f, true));
201
0
                }
202
203
0
                const Real leverageAvg =
204
0
                    GaussLobattoIntegral(10000, 1e-4)(
205
0
                        interpolated_volatility(u, sig), u.front(), u.back())
206
0
                    / (1-2*epsilon);
207
208
0
                acc(leverageAvg);
209
0
            }
210
0
            volaEstimate_ *= boost::accumulators::mean(acc);
211
0
        }
212
0
    }
213
}