Coverage Report

Created: 2025-08-05 06:45

/src/quantlib/ql/methods/finitedifferences/operators/fdmhestonop.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) 2008 Andreas Gaida
5
 Copyright (C) 2008 Ralph Schreyer
6
 Copyright (C) 2008, 2014, 2015 Klaus Spanderen
7
 Copyright (C) 2015 Johannes Göttker-Schnetmann
8
9
 This file is part of QuantLib, a free-software/open-source library
10
 for financial quantitative analysts and developers - http://quantlib.org/
11
12
 QuantLib is free software: you can redistribute it and/or modify it
13
 under the terms of the QuantLib license.  You should have received a
14
 copy of the license along with this program; if not, please email
15
 <quantlib-dev@lists.sf.net>. The license is also available online at
16
 <http://quantlib.org/license.shtml>.
17
18
 This program is distributed in the hope that it will be useful, but WITHOUT
19
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20
 FOR A PARTICULAR PURPOSE.  See the license for more details.
21
*/
22
23
#include <ql/methods/finitedifferences/meshers/fdmmesher.hpp>
24
#include <ql/methods/finitedifferences/operators/fdmhestonop.hpp>
25
#include <ql/methods/finitedifferences/operators/fdmlinearoplayout.hpp>
26
#include <ql/methods/finitedifferences/operators/secondderivativeop.hpp>
27
#include <ql/methods/finitedifferences/operators/secondordermixedderivativeop.hpp>
28
#include <utility>
29
30
namespace QuantLib {
31
32
    FdmHestonEquityPart::FdmHestonEquityPart(const ext::shared_ptr<FdmMesher>& mesher,
33
                                             ext::shared_ptr<YieldTermStructure> rTS,
34
                                             ext::shared_ptr<YieldTermStructure> qTS,
35
                                             ext::shared_ptr<FdmQuantoHelper> quantoHelper,
36
                                             ext::shared_ptr<LocalVolTermStructure> leverageFct)
37
0
    : varianceValues_(0.5 * mesher->locations(1)), dxMap_(FirstDerivativeOp(0, mesher)),
38
0
      dxxMap_(SecondDerivativeOp(0, mesher).mult(0.5 * mesher->locations(1))), mapT_(0, mesher),
39
0
      mesher_(mesher), rTS_(std::move(rTS)), qTS_(std::move(qTS)),
40
0
      quantoHelper_(std::move(quantoHelper)), leverageFct_(std::move(leverageFct)) {
41
42
        // on the boundary s_min and s_max the second derivative
43
        // d^2V/dS^2 is zero and due to Ito's Lemma the variance term
44
        // in the drift should vanish.
45
0
        for (const auto& iter : *mesher_->layout()) {
46
0
            if (   iter.coordinates()[0] == 0
47
0
                || iter.coordinates()[0] == mesher_->layout()->dim()[0]-1) {
48
0
                varianceValues_[iter.index()] = 0.0;
49
0
            }
50
0
        }
51
0
        volatilityValues_ = Sqrt(2*varianceValues_);
52
0
    }
53
54
0
    void FdmHestonEquityPart::setTime(Time t1, Time t2) {
55
0
        const Rate r = rTS_->forwardRate(t1, t2, Continuous).rate();
56
0
        const Rate q = qTS_->forwardRate(t1, t2, Continuous).rate();
57
58
0
        L_ = getLeverageFctSlice(t1, t2);
59
0
        const Array Lsquare = L_*L_;
60
61
0
        if (quantoHelper_ != nullptr) {
62
0
            mapT_.axpyb(r - q - varianceValues_*Lsquare
63
0
                - quantoHelper_->quantoAdjustment(
64
0
                    volatilityValues_*L_, t1, t2),
65
0
                dxMap_, dxxMap_.mult(Lsquare), Array(1, -0.5*r));
66
0
        } else {
67
0
            mapT_.axpyb(r - q - varianceValues_*Lsquare, dxMap_,
68
0
                        dxxMap_.mult(Lsquare), Array(1, -0.5*r));
69
0
        }
70
0
    }
71
72
0
    Array FdmHestonEquityPart::getLeverageFctSlice(Time t1, Time t2) const {
73
74
0
        Array v(mesher_->layout()->size(), 1.0);
75
76
0
        if (!leverageFct_) {
77
0
            return v;
78
0
        }
79
0
        const Real t = 0.5*(t1+t2);
80
0
        const Time time = std::min(leverageFct_->maxTime(), t);
81
82
0
        for (const auto& iter : *mesher_->layout()) {
83
0
            const Size nx = iter.coordinates()[0];
84
85
0
            if (iter.coordinates()[1] == 0) {
86
0
                const Real x = std::exp(mesher_->location(iter, 0));
87
0
                const Real spot = std::min(leverageFct_->maxStrike(),
88
0
                                           std::max(leverageFct_->minStrike(), x));
89
0
                v[nx] = std::max(0.01, leverageFct_->localVol(time, spot, true));
90
0
            }
91
0
            else {
92
0
                v[iter.index()] = v[nx];
93
0
            }
94
0
        }
95
0
        return v;
96
0
    }
97
98
99
0
    const TripleBandLinearOp& FdmHestonEquityPart::getMap() const {
100
0
        return mapT_;
101
0
    }
102
103
    FdmHestonVariancePart::FdmHestonVariancePart(const ext::shared_ptr<FdmMesher>& mesher,
104
                                                 ext::shared_ptr<YieldTermStructure> rTS,
105
                                                 Real mixedSigma,
106
                                                 Real kappa,
107
                                                 Real theta)
108
0
    : dyMap_(SecondDerivativeOp(1, mesher)
109
0
                 .mult(0.5 * mixedSigma * mixedSigma * mesher->locations(1))
110
0
                 .add(FirstDerivativeOp(1, mesher).mult(kappa * (theta - mesher->locations(1))))),
111
0
      mapT_(1, mesher), rTS_(std::move(rTS)) {}
112
113
0
    void FdmHestonVariancePart::setTime(Time t1, Time t2) {
114
0
        const Rate r = rTS_->forwardRate(t1, t2, Continuous).rate();
115
0
        mapT_.axpyb(Array(), dyMap_, dyMap_, Array(1,-0.5*r));
116
0
    }
117
118
0
    const TripleBandLinearOp& FdmHestonVariancePart::getMap() const {
119
0
        return mapT_;
120
0
    }
121
122
    FdmHestonOp::FdmHestonOp(
123
        const ext::shared_ptr<FdmMesher>& mesher,
124
        const ext::shared_ptr<HestonProcess> & hestonProcess,
125
        const ext::shared_ptr<FdmQuantoHelper>& quantoHelper,
126
        const ext::shared_ptr<LocalVolTermStructure>& leverageFct,
127
        const Real mixingFactor)
128
0
    : correlationMap_(SecondOrderMixedDerivativeOp(0, 1, mesher)
129
0
                        .mult(hestonProcess->rho()*hestonProcess->sigma()
130
0
                                *mixingFactor
131
0
                                *mesher->locations(1))),
132
0
      dyMap_(mesher, hestonProcess->riskFreeRate().currentLink(),
133
0
              hestonProcess->sigma()*mixingFactor,
134
0
              hestonProcess->kappa(), 
135
0
              hestonProcess->theta()),
136
0
      dxMap_(mesher,
137
0
             hestonProcess->riskFreeRate().currentLink(), 
138
0
             hestonProcess->dividendYield().currentLink(),
139
0
             quantoHelper, leverageFct) {
140
0
    }
141
142
143
0
    void FdmHestonOp::setTime(Time t1, Time t2) {
144
0
        dxMap_.setTime(t1, t2);
145
0
        dyMap_.setTime(t1, t2);
146
0
    }
147
148
0
    Size FdmHestonOp::size() const {
149
0
        return 2;
150
0
    }
151
152
0
    Array FdmHestonOp::apply(const Array& u) const {
153
0
        return dyMap_.getMap().apply(u) + dxMap_.getMap().apply(u)
154
0
              + dxMap_.getL()*correlationMap_.apply(u);
155
0
    }
156
157
    Array FdmHestonOp::apply_direction(Size direction,
158
0
                                       const Array& r) const {
159
0
        if (direction == 0)
160
0
            return dxMap_.getMap().apply(r);
161
0
        else if (direction == 1)
162
0
            return dyMap_.getMap().apply(r);
163
0
        else
164
0
            QL_FAIL("direction too large");
165
0
    }
166
167
0
    Array FdmHestonOp::apply_mixed(const Array& r) const {
168
0
        return dxMap_.getL()*correlationMap_.apply(r);
169
0
    }
170
171
    Array FdmHestonOp::solve_splitting(Size direction,
172
0
                                       const Array& r, Real a) const {
173
174
0
        if (direction == 0) {
175
0
            return dxMap_.getMap().solve_splitting(r, a, 1.0);
176
0
        }
177
0
        else if (direction == 1) {
178
0
            return dyMap_.getMap().solve_splitting(r, a, 1.0);
179
0
        }
180
0
        else
181
0
            QL_FAIL("direction too large");
182
0
    }
183
184
0
    Array FdmHestonOp::preconditioner(const Array& r, Real dt) const {
185
0
        return solve_splitting(1, solve_splitting(0, r, dt), dt) ;
186
0
    }
187
188
0
    std::vector<SparseMatrix> FdmHestonOp::toMatrixDecomp() const {
189
0
        return {
190
0
            dxMap_.getMap().toMatrix(),
191
0
            dyMap_.getMap().toMatrix(),
192
0
            correlationMap_.toMatrix()
193
0
        };
194
0
    }
195
196
}