Coverage Report

Created: 2025-09-04 07:11

/src/quantlib/ql/pricingengines/vanilla/fdhestonhullwhitevanillaengine.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) 2009 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
 <https://www.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
#include <ql/methods/finitedifferences/meshers/fdmblackscholesmesher.hpp>
21
#include <ql/methods/finitedifferences/meshers/fdmblackscholesmultistrikemesher.hpp>
22
#include <ql/methods/finitedifferences/meshers/fdmhestonvariancemesher.hpp>
23
#include <ql/methods/finitedifferences/meshers/fdmmeshercomposite.hpp>
24
#include <ql/methods/finitedifferences/meshers/fdmsimpleprocess1dmesher.hpp>
25
#include <ql/methods/finitedifferences/meshers/uniform1dmesher.hpp>
26
#include <ql/methods/finitedifferences/operators/fdmlinearoplayout.hpp>
27
#include <ql/methods/finitedifferences/stepconditions/fdmstepconditioncomposite.hpp>
28
#include <ql/methods/finitedifferences/utilities/fdminnervaluecalculator.hpp>
29
#include <ql/pricingengines/vanilla/analytichestonengine.hpp>
30
#include <ql/pricingengines/vanilla/fdhestonhullwhitevanillaengine.hpp>
31
#include <ql/pricingengines/vanilla/fdhestonvanillaengine.hpp>
32
#include <utility>
33
34
namespace QuantLib {
35
36
    FdHestonHullWhiteVanillaEngine::FdHestonHullWhiteVanillaEngine(
37
        const ext::shared_ptr<HestonModel>& hestonModel,
38
        ext::shared_ptr<HullWhiteProcess> hwProcess,
39
        Real corrEquityShortRate,
40
        Size tGrid,
41
        Size xGrid,
42
        Size vGrid,
43
        Size rGrid,
44
        Size dampingSteps,
45
        bool controlVariate,
46
        const FdmSchemeDesc& schemeDesc)
47
0
    : GenericModelEngine<HestonModel,
48
0
                         VanillaOption::arguments,
49
0
                         VanillaOption::results>(hestonModel),
50
0
      hwProcess_(std::move(hwProcess)),
51
0
      corrEquityShortRate_(corrEquityShortRate), tGrid_(tGrid),
52
0
      xGrid_(xGrid), vGrid_(vGrid), rGrid_(rGrid), dampingSteps_(dampingSteps),
53
0
      schemeDesc_(schemeDesc), controlVariate_(controlVariate) {}
54
55
    FdHestonHullWhiteVanillaEngine::FdHestonHullWhiteVanillaEngine(
56
        const ext::shared_ptr<HestonModel>& hestonModel,
57
        ext::shared_ptr<HullWhiteProcess> hwProcess,
58
        DividendSchedule dividends,
59
        Real corrEquityShortRate,
60
        Size tGrid,
61
        Size xGrid,
62
        Size vGrid,
63
        Size rGrid,
64
        Size dampingSteps,
65
        bool controlVariate,
66
        const FdmSchemeDesc& schemeDesc)
67
0
    : GenericModelEngine<HestonModel,
68
0
                         VanillaOption::arguments,
69
0
                         VanillaOption::results>(hestonModel),
70
0
      hwProcess_(std::move(hwProcess)), dividends_(std::move(dividends)),
71
0
      corrEquityShortRate_(corrEquityShortRate), tGrid_(tGrid),
72
0
      xGrid_(xGrid), vGrid_(vGrid), rGrid_(rGrid), dampingSteps_(dampingSteps),
73
0
      schemeDesc_(schemeDesc), controlVariate_(controlVariate) {}
74
75
0
    void FdHestonHullWhiteVanillaEngine::calculate() const {
76
  
77
        // 1. cache lookup for precalculated results
78
0
        for (auto& cachedArgs2result : cachedArgs2results_) {
79
0
            if (cachedArgs2result.first.exercise->type() == arguments_.exercise->type() &&
80
0
                cachedArgs2result.first.exercise->dates() == arguments_.exercise->dates()) {
81
0
                ext::shared_ptr<PlainVanillaPayoff> p1 =
82
0
                    ext::dynamic_pointer_cast<PlainVanillaPayoff>(
83
0
                                                            arguments_.payoff);
84
0
                ext::shared_ptr<PlainVanillaPayoff> p2 =
85
0
                    ext::dynamic_pointer_cast<PlainVanillaPayoff>(cachedArgs2result.first.payoff);
86
87
0
                if ((p1 != nullptr) && p1->strike() == p2->strike() &&
88
0
                    p1->optionType() == p2->optionType()) {
89
0
                    QL_REQUIRE(dividends_.empty(),
90
0
                               "multiple strikes engine does not work with discrete dividends");
91
0
                    results_ = cachedArgs2result.second;
92
0
                    return;
93
0
                }
94
0
            }
95
0
        }
96
97
        // 2. Mesher
98
0
        const ext::shared_ptr<HestonProcess> hestonProcess=model_->process();
99
0
        const Time maturity=hestonProcess->time(arguments_.exercise->lastDate());
100
101
        // 2.1 The variance mesher
102
0
        const Size tGridMin = 5;
103
0
        const ext::shared_ptr<FdmHestonVarianceMesher> varianceMesher(
104
0
            new FdmHestonVarianceMesher(vGrid_, hestonProcess,
105
0
                                        maturity,std::max(tGridMin,tGrid_/50)));
106
107
        // 2.2 The equity mesher
108
0
        const ext::shared_ptr<StrikedTypePayoff> payoff =
109
0
            ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);
110
0
        QL_REQUIRE(payoff, "wrong payoff type given");
111
112
0
        ext::shared_ptr<Fdm1dMesher> equityMesher;
113
0
        if (strikes_.empty()) {
114
0
            equityMesher = ext::shared_ptr<Fdm1dMesher>(
115
0
                new FdmBlackScholesMesher(
116
0
                    xGrid_, 
117
0
                    FdmBlackScholesMesher::processHelper(
118
0
                      hestonProcess->s0(), hestonProcess->dividendYield(), 
119
0
                      hestonProcess->riskFreeRate(), 
120
0
                      varianceMesher->volaEstimate()),
121
0
                      maturity, payoff->strike(),
122
0
                      Null<Real>(), Null<Real>(), 0.0001, 1.5, 
123
0
                      std::pair<Real, Real>(payoff->strike(), 0.1),
124
0
                      dividends_));
125
0
        }
126
0
        else {
127
0
            QL_REQUIRE(dividends_.empty(),
128
0
                       "multiple strikes engine does not work with discrete dividends");
129
0
            equityMesher = ext::shared_ptr<Fdm1dMesher>(
130
0
                new FdmBlackScholesMultiStrikeMesher(
131
0
                    xGrid_,
132
0
                    FdmBlackScholesMesher::processHelper(
133
0
                      hestonProcess->s0(), hestonProcess->dividendYield(), 
134
0
                      hestonProcess->riskFreeRate(), 
135
0
                      varianceMesher->volaEstimate()),
136
0
                    maturity, strikes_, 0.0001, 1.5,
137
0
                    std::pair<Real, Real>(payoff->strike(), 0.075)));            
138
0
        }
139
       
140
        //2.3 The short rate mesher        
141
0
        const ext::shared_ptr<OrnsteinUhlenbeckProcess> ouProcess(
142
0
            new OrnsteinUhlenbeckProcess(hwProcess_->a(),hwProcess_->sigma()));
143
0
        const ext::shared_ptr<Fdm1dMesher> shortRateMesher(
144
0
                   new FdmSimpleProcess1dMesher(rGrid_, ouProcess, maturity));
145
        
146
0
        const ext::shared_ptr<FdmMesher> mesher(
147
0
            new FdmMesherComposite(equityMesher, varianceMesher,
148
0
                                   shortRateMesher));
149
150
        // 3. Calculator
151
0
        const ext::shared_ptr<FdmInnerValueCalculator> calculator(
152
0
                            new FdmLogInnerValue(arguments_.payoff, mesher, 0));
153
154
        // 4. Step conditions
155
0
        const ext::shared_ptr<FdmStepConditionComposite> conditions = 
156
0
            FdmStepConditionComposite::vanillaComposite(
157
0
                                dividends_, arguments_.exercise, 
158
0
                                mesher, calculator, 
159
0
                                hestonProcess->riskFreeRate()->referenceDate(),
160
0
                                hestonProcess->riskFreeRate()->dayCounter());
161
162
        // 5. Boundary conditions
163
0
        const FdmBoundaryConditionSet boundaries;
164
165
        // 6. Solver
166
0
        const FdmSolverDesc solverDesc = { mesher, boundaries, conditions,
167
0
                                           calculator, maturity,
168
0
                                           tGrid_, dampingSteps_ };
169
170
0
        const ext::shared_ptr<FdmHestonHullWhiteSolver> solver(
171
0
            new FdmHestonHullWhiteSolver(Handle<HestonProcess>(hestonProcess),
172
0
                                         Handle<HullWhiteProcess>(hwProcess_),
173
0
                                         corrEquityShortRate_,
174
0
                                         solverDesc, schemeDesc_));
175
176
0
        const Real spot = hestonProcess->s0()->value();
177
0
        const Real v0   = hestonProcess->v0();
178
0
        results_.value = solver->valueAt(spot, v0, 0);
179
0
        results_.delta = solver->deltaAt(spot, v0, 0, spot*0.01);
180
0
        results_.gamma = solver->gammaAt(spot, v0, 0, spot*0.01);
181
0
        results_.theta = solver->thetaAt(spot, v0, 0);
182
183
0
        cachedArgs2results_.resize(strikes_.size());        
184
0
        for (Size i=0; i < strikes_.size(); ++i) {
185
0
            cachedArgs2results_[i].first.exercise = arguments_.exercise;
186
0
            cachedArgs2results_[i].first.payoff = 
187
0
                ext::make_shared<PlainVanillaPayoff>(
188
0
                    payoff->optionType(), strikes_[i]);
189
0
            const Real d = payoff->strike()/strikes_[i];
190
191
0
            VanillaOption::results& results = cachedArgs2results_[i].second;
192
0
            results.value = solver->valueAt(spot*d, v0, 0)/d;
193
0
            results.delta = solver->deltaAt(spot*d, v0, 0, spot*d*0.01);
194
0
            results.gamma = solver->gammaAt(spot*d, v0, 0, spot*d*0.01)*d;
195
0
            results.theta = solver->thetaAt(spot*d, v0, 0)/d;
196
0
        }
197
     
198
0
        if (controlVariate_) {
199
0
            ext::shared_ptr<PricingEngine> analyticEngine(
200
0
                                       new AnalyticHestonEngine(*model_, 164));
201
0
            ext::shared_ptr<Exercise> exercise(
202
0
                        new EuropeanExercise(arguments_.exercise->lastDate()));
203
            
204
0
            VanillaOption option(payoff, exercise);
205
0
            option.setPricingEngine(analyticEngine);
206
0
            Real analyticNPV = option.NPV();
207
208
0
            ext::shared_ptr<FdHestonVanillaEngine> fdEngine(
209
0
                    new FdHestonVanillaEngine(*model_, tGrid_, xGrid_,
210
0
                                              vGrid_, dampingSteps_, 
211
0
                                              schemeDesc_));
212
0
            fdEngine->enableMultipleStrikesCaching(strikes_);
213
0
            option.setPricingEngine(fdEngine);
214
            
215
0
            Real fdNPV = option.NPV();
216
0
            results_.value += analyticNPV - fdNPV;
217
0
            for (Size i=0; i < strikes_.size(); ++i) {
218
0
                VanillaOption controlVariateOption(
219
0
                    ext::shared_ptr<StrikedTypePayoff>(
220
0
                        new PlainVanillaPayoff(payoff->optionType(), 
221
0
                                               strikes_[i])), exercise);
222
0
                controlVariateOption.setPricingEngine(analyticEngine);
223
0
                analyticNPV = controlVariateOption.NPV();
224
                
225
0
                controlVariateOption.setPricingEngine(fdEngine);
226
0
                fdNPV = controlVariateOption.NPV();
227
0
                cachedArgs2results_[i].second.value += analyticNPV - fdNPV;
228
0
            }
229
0
        }
230
0
    }
231
    
232
0
    void FdHestonHullWhiteVanillaEngine::update() {
233
0
        cachedArgs2results_.clear();
234
0
        GenericModelEngine<HestonModel,
235
0
                           VanillaOption::arguments,
236
0
                           VanillaOption::results>::update();
237
0
    }
238
239
    void FdHestonHullWhiteVanillaEngine::enableMultipleStrikesCaching(
240
0
                                        const std::vector<Real>& strikes) {
241
0
        strikes_ = strikes;
242
0
        update();
243
0
    }
244
245
}