Coverage Report

Created: 2025-12-08 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/methods/finitedifferences/operators/fdmhestonfwdop.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2012, 2013 Klaus Spanderen
5
 Copyright (C) 2014 Johannes Göttker-Schnetmann
6
7
 This file is part of QuantLib, a free-software/open-source library
8
 for financial quantitative analysts and developers - http://quantlib.org/
9
10
 QuantLib is free software: you can redistribute it and/or modify it
11
 under the terms of the QuantLib license.  You should have received a
12
 copy of the license along with this program; if not, please email
13
 <quantlib-dev@lists.sf.net>. The license is also available online at
14
 <https://www.quantlib.org/license.shtml>.
15
16
 This program is distributed in the hope that it will be useful, but WITHOUT
17
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 FOR A PARTICULAR PURPOSE.  See the license for more details.
19
*/
20
21
/*! \file fdmhestonfwdop.cpp
22
*/
23
24
#include <ql/methods/finitedifferences/operators/fdmhestonfwdop.hpp>
25
#include <ql/methods/finitedifferences/operators/modtriplebandlinearop.hpp>
26
#include <ql/methods/finitedifferences/meshers/fdmmesher.hpp>
27
#include <ql/methods/finitedifferences/operators/fdmlinearoplayout.hpp>
28
#include <ql/methods/finitedifferences/operators/firstderivativeop.hpp>
29
#include <ql/methods/finitedifferences/operators/secondderivativeop.hpp>
30
#include <ql/methods/finitedifferences/operators/secondordermixedderivativeop.hpp>
31
#include <ql/processes/hestonprocess.hpp>
32
#include <cmath>
33
#include <utility>
34
35
using std::exp;
36
37
namespace QuantLib {
38
39
    FdmHestonFwdOp::FdmHestonFwdOp(const ext::shared_ptr<FdmMesher>& mesher,
40
                                   const ext::shared_ptr<HestonProcess>& process,
41
                                   FdmSquareRootFwdOp::TransformationType type,
42
                                   ext::shared_ptr<LocalVolTermStructure> leverageFct,
43
                                   const Real mixingFactor)
44
0
    : type_(type), kappa_(process->kappa()), theta_(process->theta()), sigma_(process->sigma()),
45
0
      rho_(process->rho()), v0_(process->v0()), mixedSigma_(mixingFactor * sigma_),
46
0
      rTS_(process->riskFreeRate().currentLink()), qTS_(process->dividendYield().currentLink()),
47
0
      varianceValues_(0.5 * mesher->locations(1)),
48
0
      dxMap_(ext::make_shared<FirstDerivativeOp>(0, mesher)),
49
0
      dxxMap_(ext::make_shared<ModTripleBandLinearOp>(TripleBandLinearOp(
50
0
          type == FdmSquareRootFwdOp::Log ?
51
0
              SecondDerivativeOp(0, mesher).mult(0.5 * Exp(mesher->locations(1))) :
52
0
              SecondDerivativeOp(0, mesher).mult(0.5 * mesher->locations(1))))),
53
0
      boundary_(ext::make_shared<ModTripleBandLinearOp>(TripleBandLinearOp(
54
0
          SecondDerivativeOp(0, mesher).mult(Array(mesher->locations(0).size(), 0.0))))),
55
0
      mapX_(ext::make_shared<TripleBandLinearOp>(0, mesher)),
56
0
      mapY_(ext::make_shared<FdmSquareRootFwdOp>(mesher, kappa_, theta_, mixedSigma_, 1, type)),
57
0
      correlation_(ext::make_shared<NinePointLinearOp>(
58
0
          type == FdmSquareRootFwdOp::Log ?
59
0
              SecondOrderMixedDerivativeOp(0, 1, mesher)
60
0
                  .mult(Array(mesher->layout()->size(), rho_ * mixedSigma_)) :
61
0
              SecondOrderMixedDerivativeOp(0, 1, mesher)
62
0
                  .mult(rho_ * mixedSigma_ * mesher->locations(1)))),
63
0
      leverageFct_(std::move(leverageFct)), mesher_(mesher) {
64
        // zero flux boundary condition
65
0
        const Size n = mesher->layout()->dim()[1];
66
0
        const Real lowerBoundaryFactor = mapY_->lowerBoundaryFactor(type);
67
0
        const Real upperBoundaryFactor = mapY_->upperBoundaryFactor(type);
68
69
0
        const Real logFacLow = type == FdmSquareRootFwdOp::Log ? Real(exp(mapY_->v(0))) : 1.0;
70
0
        const Real logFacUpp = type == FdmSquareRootFwdOp::Log ? Real(exp(mapY_->v(n+1))) : 1.0;
71
72
0
        const Real alpha = -2*rho_/mixedSigma_*lowerBoundaryFactor*logFacLow;
73
0
        const Real beta  = -2*rho_/mixedSigma_*upperBoundaryFactor*logFacUpp;
74
75
0
        ModTripleBandLinearOp fDx(FirstDerivativeOp(0, mesher));
76
77
0
        for (const auto& iter : *mesher->layout()) {
78
0
            if (iter.coordinates()[1] == 0) {
79
0
                const Size idx = iter.index();
80
0
                if (!leverageFct_) {
81
0
                    dxxMap_->upper(idx) += alpha*fDx.upper(idx);
82
0
                    dxxMap_->diag(idx) += alpha*fDx.diag(idx);
83
0
                    dxxMap_->lower(idx) += alpha*fDx.lower(idx);
84
0
                }
85
0
                boundary_->upper(idx)= alpha*fDx.upper(idx);
86
0
                boundary_->diag(idx) = alpha*fDx.diag(idx);
87
0
                boundary_->lower(idx) = alpha*fDx.lower(idx);
88
0
            }
89
0
            else if (iter.coordinates()[1] == n-1) {
90
0
                const Size idx = iter.index();
91
92
0
                if (!leverageFct_) {
93
0
                    dxxMap_->upper(idx)+= beta*fDx.upper(idx);
94
0
                    dxxMap_->diag(idx) += beta*fDx.diag(idx);
95
0
                    dxxMap_->lower(idx) += beta*fDx.lower(idx);
96
0
                }
97
0
                boundary_->upper(idx)= beta*fDx.upper(idx);
98
0
                boundary_->diag(idx) = beta*fDx.diag(idx);
99
0
                boundary_->lower(idx) = beta*fDx.lower(idx);
100
0
            }
101
0
        }
102
0
    }
103
104
0
    Size FdmHestonFwdOp::size() const {
105
0
        return 2;
106
0
    }
107
108
0
    void FdmHestonFwdOp::setTime(Time t1, Time t2){
109
0
        const Rate r = rTS_->forwardRate(t1, t2, Continuous).rate();
110
0
        const Rate q = qTS_->forwardRate(t1, t2, Continuous).rate();
111
0
        if (leverageFct_ != nullptr) {
112
0
            L_ = getLeverageFctSlice(t1, t2);
113
0
            Array Lsquare = L_*L_;
114
0
            if (type_ == FdmSquareRootFwdOp::Plain) {
115
0
                mapX_->axpyb( Array(1, -r + q), *dxMap_,
116
0
                    dxxMap_->multR(Lsquare).add(boundary_->multR(L_))
117
0
                    .add(dxMap_->multR(rho_*mixedSigma_*L_))
118
0
                    .add(dxMap_->mult(varianceValues_).multR(Lsquare)),
119
0
                              Array());
120
0
            } else if (type_ == FdmSquareRootFwdOp::Power) {
121
0
                mapX_->axpyb( Array(1, -r + q), *dxMap_,
122
0
                    dxxMap_->multR(Lsquare).add(boundary_->multR(L_))
123
0
                    .add(dxMap_->multR(rho_*2.0*kappa_*theta_/(mixedSigma_)*L_))
124
0
                    .add(dxMap_->mult(varianceValues_).multR(Lsquare)), Array());
125
0
            } else if (type_ == FdmSquareRootFwdOp::Log) {
126
0
                mapX_->axpyb( Array(1, -r + q), *dxMap_,
127
0
                    dxxMap_->multR(Lsquare).add(boundary_->multR(L_))
128
0
                    .add(dxMap_->mult(0.5*Exp(2.0*varianceValues_)).multR(Lsquare)),
129
0
                              Array());
130
0
            }
131
0
        } else {
132
0
            if (type_ == FdmSquareRootFwdOp::Plain) {
133
0
                mapX_->axpyb( - r + q + rho_*mixedSigma_ + varianceValues_, *dxMap_,
134
0
                        *dxxMap_, Array());
135
0
            } else if (type_ == FdmSquareRootFwdOp::Power) {
136
0
                mapX_->axpyb( - r + q + rho_*2.0*kappa_*theta_/(mixedSigma_) + varianceValues_,
137
0
                              *dxMap_, *dxxMap_, Array());
138
0
            } else if (type_ == FdmSquareRootFwdOp::Log) {
139
0
                mapX_->axpyb( - r + q + 0.5*Exp(2.0*varianceValues_), *dxMap_,
140
0
                        *dxxMap_, Array());
141
0
            }
142
0
        }
143
0
    }
144
145
0
    Array FdmHestonFwdOp::apply(const Array& u) const {
146
0
        if (leverageFct_ != nullptr) {
147
0
            return mapX_->apply(u)
148
0
                    + mapY_->apply(u)
149
0
                    + correlation_->apply(L_*u);
150
0
        } else {
151
0
            return mapX_->apply(u)
152
0
                    + mapY_->apply(u)
153
0
                    + correlation_->apply(u);
154
0
        }
155
0
    }
156
157
0
    Array FdmHestonFwdOp::apply_mixed(const Array& u) const{
158
0
        if (leverageFct_ != nullptr) {
159
0
            return correlation_->apply(L_*u);
160
0
        } else {
161
0
            return correlation_->apply(u);
162
0
        }
163
0
    }
164
165
    Array FdmHestonFwdOp::apply_direction(
166
0
        Size direction, const Array& u) const {
167
168
0
        if (direction == 0)
169
0
            return mapX_->apply(u) ;
170
0
        else if (direction == 1)
171
0
            return mapY_->apply(u) ;
172
0
        else
173
0
            QL_FAIL("direction too large");
174
0
    }
175
176
    Array FdmHestonFwdOp::solve_splitting(
177
0
        Size direction, const Array& u, Real s) const{
178
0
        if (direction == 0) {
179
0
            return mapX_->solve_splitting(u, s, 1.0);
180
0
        }
181
0
        else if (direction == 1) {
182
0
            return mapY_->solve_splitting(1, u, s);
183
0
        }
184
0
        else
185
0
            QL_FAIL("direction too large");
186
0
    }
187
188
    Array FdmHestonFwdOp::preconditioner(
189
0
        const Array& u, Real dt) const{
190
0
        return solve_splitting(1, u, dt);
191
0
    }
192
193
0
    Array FdmHestonFwdOp::getLeverageFctSlice(Time t1, Time t2) const {
194
0
        Array v(mesher_->layout()->size(), 1.0);
195
196
0
        if (!leverageFct_)
197
0
            return v;
198
199
0
        const Real t = 0.5*(t1+t2);
200
0
        const Time time = std::min(leverageFct_->maxTime(), t);
201
                                   //std::max(leverageFct_->minTime(), t));
202
203
0
        for (const auto& iter : *mesher_->layout()) {
204
0
            const Size nx = iter.coordinates()[0];
205
206
0
            if (iter.coordinates()[1] == 0) {
207
0
                const Real x = std::exp(mesher_->location(iter, 0));
208
0
                const Real spot = std::min(leverageFct_->maxStrike(),
209
0
                                           std::max(leverageFct_->minStrike(), x));
210
0
                v[nx] = std::max(0.01, leverageFct_->localVol(time, spot, true));
211
0
            }
212
0
            else {
213
0
                v[iter.index()] = v[nx];
214
0
            }
215
0
        }
216
0
        return v;
217
0
    }
218
219
0
    std::vector<SparseMatrix> FdmHestonFwdOp::toMatrixDecomp() const {
220
221
0
        std::vector<SparseMatrix> retVal(3);
222
223
0
        retVal[0] = mapX_->toMatrix();
224
0
        retVal[1] = mapY_->toMatrix();
225
0
        retVal[2] = correlation_->toMatrix();
226
227
0
        return retVal;
228
0
    }
229
230
}