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/operators/fdmhestonhullwhiteop.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, 2011 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/methods/finitedifferences/meshers/fdmmesher.hpp>
23
#include <ql/methods/finitedifferences/operators/fdmhestonhullwhiteop.hpp>
24
#include <ql/methods/finitedifferences/operators/fdmlinearoplayout.hpp>
25
#include <ql/methods/finitedifferences/operators/secondderivativeop.hpp>
26
#include <ql/methods/finitedifferences/operators/secondordermixedderivativeop.hpp>
27
#include <ql/models/shortrate/onefactormodels/hullwhite.hpp>
28
#include <utility>
29
30
namespace QuantLib {
31
32
    FdmHestonHullWhiteEquityPart::FdmHestonHullWhiteEquityPart(
33
        const ext::shared_ptr<FdmMesher>& mesher,
34
        ext::shared_ptr<HullWhite> hwModel,
35
        ext::shared_ptr<YieldTermStructure> qTS)
36
0
    : x_(mesher->locations(2)), varianceValues_(0.5 * mesher->locations(1)),
37
0
      dxMap_(FirstDerivativeOp(0, mesher)),
38
0
      dxxMap_(SecondDerivativeOp(0, mesher).mult(0.5 * mesher->locations(1))), mapT_(0, mesher),
39
0
      hwModel_(std::move(hwModel)), mesher_(mesher), qTS_(std::move(qTS)) {
40
41
        // on the boundary s_min and s_max the second derivative
42
        // d²V/dS² is zero and due to Ito's Lemma the variance term
43
        // in the drift should vanish.
44
0
        for (const auto& iter : *mesher_->layout()) {
45
0
            if (   iter.coordinates()[0] == 0
46
0
                || iter.coordinates()[0] == mesher_->layout()->dim()[0]-1) {
47
0
                varianceValues_[iter.index()] = 0.0;
48
0
            }
49
0
        }
50
0
        volatilityValues_ = Sqrt(2*varianceValues_);
51
0
    }
52
53
0
    void FdmHestonHullWhiteEquityPart::setTime(Time t1, Time t2) {
54
0
        const ext::shared_ptr<OneFactorModel::ShortRateDynamics> dynamics =
55
0
            hwModel_->dynamics();
56
57
0
        const Real phi = 0.5*(  dynamics->shortRate(t1, 0.0)
58
0
                              + dynamics->shortRate(t2, 0.0));
59
60
0
        const Rate q = qTS_->forwardRate(t1, t2, Continuous).rate();
61
62
0
        mapT_.axpyb(x_+phi-varianceValues_-q, dxMap_, dxxMap_, Array());
63
0
    }
64
65
0
    const TripleBandLinearOp& FdmHestonHullWhiteEquityPart::getMap() const {
66
0
        return mapT_;
67
0
    }
68
69
    FdmHestonHullWhiteOp::FdmHestonHullWhiteOp(const ext::shared_ptr<FdmMesher>& mesher,
70
                                               const ext::shared_ptr<HestonProcess>& hestonProcess,
71
                                               const ext::shared_ptr<HullWhiteProcess>& hwProcess,
72
                                               Real equityShortRateCorrelation)
73
0
    : v0_(hestonProcess->v0()), kappa_(hestonProcess->kappa()), theta_(hestonProcess->theta()),
74
0
      sigma_(hestonProcess->sigma()), rho_(hestonProcess->rho()),
75
0
      hwModel_(ext::make_shared<HullWhite>(
76
0
          hestonProcess->riskFreeRate(), hwProcess->a(), hwProcess->sigma())),
77
      hestonCorrMap_(
78
0
          SecondOrderMixedDerivativeOp(0, 1, mesher).mult(rho_ * sigma_ * mesher->locations(1))),
79
      equityIrCorrMap_(
80
0
          SecondOrderMixedDerivativeOp(0, 2, mesher)
81
0
              .mult(Sqrt(mesher->locations(1)) * hwProcess->sigma() * equityShortRateCorrelation)),
82
0
      dyMap_(SecondDerivativeOp(1U, mesher)
83
0
                 .mult(0.5 * sigma_ * sigma_ * mesher->locations(1))
84
0
                 .add(FirstDerivativeOp(1, mesher).mult(kappa_ * (theta_ - mesher->locations(1))))),
85
0
      dxMap_(mesher, hwModel_, hestonProcess->dividendYield().currentLink()),
86
0
      hullWhiteOp_(mesher, hwModel_, 2) {
87
88
0
        QL_REQUIRE(  equityShortRateCorrelation*equityShortRateCorrelation
89
0
                   + hestonProcess->rho()*hestonProcess->rho() <= 1.0,
90
0
                   "correlation matrix has negative eigenvalues");
91
0
    }
92
93
0
    void FdmHestonHullWhiteOp::setTime(Time t1, Time t2) {
94
0
        dxMap_.setTime(t1, t2);
95
0
        hullWhiteOp_.setTime(t1, t2);
96
0
    }
97
98
0
    Size FdmHestonHullWhiteOp::size() const {
99
0
        return 3;
100
0
    }
101
102
0
    Array FdmHestonHullWhiteOp::apply(const Array& u) const {
103
0
        return  dyMap_.apply(u) + dxMap_.getMap().apply(u)
104
0
              + hullWhiteOp_.apply(u)
105
0
              + hestonCorrMap_.apply(u) + equityIrCorrMap_.apply(u);
106
0
    }
107
108
    Array FdmHestonHullWhiteOp::apply_direction(Size direction,
109
0
                                                const Array& r) const {
110
0
        if (direction == 0)
111
0
            return dxMap_.getMap().apply(r);
112
0
        else if (direction == 1)
113
0
            return dyMap_.apply(r);
114
0
        else if (direction == 2)
115
0
            return hullWhiteOp_.apply(r);
116
0
        else
117
0
            QL_FAIL("direction too large");
118
0
    }
119
120
0
    Array FdmHestonHullWhiteOp::apply_mixed(const Array& r) const {
121
0
        return hestonCorrMap_.apply(r) + equityIrCorrMap_.apply(r);
122
0
    }
123
124
    Array FdmHestonHullWhiteOp::solve_splitting(Size direction, const Array& r,
125
0
                                                Real a) const {
126
0
        if (direction == 0) {
127
0
            return dxMap_.getMap().solve_splitting(r, a, 1.0);
128
0
        }
129
0
        else if (direction == 1) {
130
0
            return dyMap_.solve_splitting(r, a, 1.0);
131
0
        }
132
0
        else if (direction == 2) {
133
0
            return hullWhiteOp_.solve_splitting(2, r, a);
134
0
        }
135
0
        else
136
0
            QL_FAIL("direction too large");
137
0
    }
138
    
139
    Array FdmHestonHullWhiteOp::preconditioner(const Array& r, 
140
0
                                               Real dt) const {
141
0
        return solve_splitting(0, r, dt);
142
0
    }
143
144
0
    std::vector<SparseMatrix> FdmHestonHullWhiteOp::toMatrixDecomp() const {
145
0
        return {
146
0
            dxMap_.getMap().toMatrix(),
147
0
            dyMap_.toMatrix(),
148
0
            hullWhiteOp_.toMatrixDecomp().front(),
149
0
            hestonCorrMap_.toMatrix() + equityIrCorrMap_.toMatrix()
150
0
        };
151
0
    }
152
153
}