/src/quantlib/ql/pricingengines/basket/fdndimblackscholesvanillaengine.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) 2024 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 | | <http://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/exercise.hpp> |
21 | | #include <ql/processes/blackscholesprocess.hpp> |
22 | | #include <ql/math/matrixutilities/getcovariance.hpp> |
23 | | #include <ql/math/distributions/normaldistribution.hpp> |
24 | | #include <ql/math/matrixutilities/symmetricschurdecomposition.hpp> |
25 | | #include <ql/methods/finitedifferences/meshers/fdmmeshercomposite.hpp> |
26 | | #include <ql/methods/finitedifferences/meshers/predefined1dmesher.hpp> |
27 | | #include <ql/methods/finitedifferences/operators/fdmwienerop.hpp> |
28 | | #include <ql/methods/finitedifferences/solvers/fdmndimsolver.hpp> |
29 | | #include <ql/methods/finitedifferences/utilities/fdminnervaluecalculator.hpp> |
30 | | #include <ql/methods/finitedifferences/stepconditions/fdmstepconditioncomposite.hpp> |
31 | | #include <ql/pricingengines/basket/fdndimblackscholesvanillaengine.hpp> |
32 | | #include <ql/pricingengines/basket/vectorbsmprocessextractor.hpp> |
33 | | #include <boost/preprocessor/iteration/local.hpp> |
34 | | #include <utility> |
35 | | |
36 | | namespace QuantLib { |
37 | | |
38 | | namespace detail { |
39 | | class FdmPCABasketInnerValue: public FdmInnerValueCalculator { |
40 | | public: |
41 | | FdmPCABasketInnerValue( |
42 | | ext::shared_ptr<BasketPayoff> payoff, |
43 | | ext::shared_ptr<FdmMesher> mesher, |
44 | | Array logS0, const Array& vols, |
45 | | std::vector<ext::shared_ptr<YieldTermStructure>> qTS, |
46 | | ext::shared_ptr<YieldTermStructure> rTS, |
47 | | Matrix Q, Array l) |
48 | 0 | : n_(logS0.size()), |
49 | 0 | payoff_(std::move(payoff)), |
50 | 0 | mesher_(std::move(mesher)), |
51 | 0 | logS0_(std::move(logS0)), v_(vols*vols), |
52 | 0 | qTS_(std::move(qTS)), |
53 | 0 | rTS_(std::move(rTS)), |
54 | 0 | Q_(std::move(Q)), l_(std::move(l)), |
55 | 0 | cachedT_(Null<Real>()), |
56 | 0 | qf_(n_) { } |
57 | | |
58 | 0 | Real innerValue(const FdmLinearOpIterator& iter, Time t) override { |
59 | 0 | if (!close_enough(t, cachedT_)) { |
60 | 0 | rf_ = rTS_->discount(t); |
61 | 0 | for (Size i=0; i < n_; ++i) |
62 | 0 | qf_[i] = qTS_[i]->discount(t); |
63 | 0 | } |
64 | 0 | Array x(n_); |
65 | 0 | for (Size i=0; i < n_; ++i) |
66 | 0 | x[i] = mesher_->location(iter, i); |
67 | |
|
68 | 0 | const Array S = Exp(Q_*x - 0.5*v_*t + logS0_)*qf_/rf_; |
69 | |
|
70 | 0 | return (*payoff_)(S); |
71 | 0 | } |
72 | 0 | Real avgInnerValue(const FdmLinearOpIterator& iter, Time t) override { |
73 | 0 | return innerValue(iter, t); |
74 | 0 | } |
75 | | |
76 | | private: |
77 | | const Size n_; |
78 | | const ext::shared_ptr<BasketPayoff> payoff_; |
79 | | const ext::shared_ptr<FdmMesher> mesher_; |
80 | | const Array logS0_, v_; |
81 | | const std::vector<ext::shared_ptr<YieldTermStructure>> qTS_; |
82 | | const ext::shared_ptr<YieldTermStructure> rTS_; |
83 | | const Matrix Q_; |
84 | | const Array l_; |
85 | | Time cachedT_; |
86 | | Array qf_; |
87 | | DiscountFactor rf_; |
88 | | }; |
89 | | } |
90 | | |
91 | | FdndimBlackScholesVanillaEngine::FdndimBlackScholesVanillaEngine( |
92 | | std::vector<ext::shared_ptr<GeneralizedBlackScholesProcess> > processes, |
93 | | Matrix rho, |
94 | | std::vector<Size> xGrids, |
95 | | Size tGrid, Size dampingSteps, |
96 | | const FdmSchemeDesc& schemeDesc) |
97 | 0 | : processes_(std::move(processes)), |
98 | 0 | rho_(std::move(rho)), |
99 | 0 | xGrids_(std::move(xGrids)), |
100 | 0 | tGrid_(tGrid), |
101 | 0 | dampingSteps_(dampingSteps), |
102 | 0 | schemeDesc_(schemeDesc) { |
103 | |
|
104 | 0 | QL_REQUIRE(!processes_.empty(), "no Black-Scholes process is given."); |
105 | 0 | QL_REQUIRE(rho_.size1() == rho_.size2() |
106 | 0 | && rho_.size1() == processes_.size(), |
107 | 0 | "correlation matrix has the wrong size."); |
108 | 0 | QL_REQUIRE(xGrids_.size() == 1 || xGrids_.size() == processes_.size(), |
109 | 0 | "wrong number of xGrids is given."); |
110 | | |
111 | 0 | std::for_each(processes_.begin(), processes_.end(), |
112 | 0 | [this](const auto& p) { registerWith(p); }); |
113 | 0 | } |
114 | | |
115 | | FdndimBlackScholesVanillaEngine::FdndimBlackScholesVanillaEngine( |
116 | | std::vector<ext::shared_ptr<GeneralizedBlackScholesProcess> > processes, |
117 | | Matrix rho, Size xGrid, Size tGrid, Size dampingSteps, |
118 | | const FdmSchemeDesc& schemeDesc) |
119 | 0 | : FdndimBlackScholesVanillaEngine( |
120 | 0 | std::move(processes), std::move(rho), std::vector<Size>(1, xGrid), tGrid, dampingSteps, schemeDesc) |
121 | 0 | {} |
122 | | |
123 | | |
124 | 0 | void FdndimBlackScholesVanillaEngine::calculate() const { |
125 | 0 | #ifndef PDE_MAX_SUPPORTED_DIM |
126 | 0 | #define PDE_MAX_SUPPORTED_DIM 4 |
127 | 0 | #endif |
128 | 0 | QL_REQUIRE(processes_.size() <= PDE_MAX_SUPPORTED_DIM, |
129 | 0 | "This engine does not support " << processes_.size() << " underlyings. " |
130 | 0 | << "Max number of underlyings is " << PDE_MAX_SUPPORTED_DIM << ". " |
131 | 0 | << "Please change preprocessor constant PDE_MAX_SUPPORTED_DIM and recompile " |
132 | 0 | << "if a larger number of underlyings is needed."); |
133 | | |
134 | 0 | const Date maturityDate = arguments_.exercise->lastDate(); |
135 | 0 | const Time maturity = processes_[0]->time(maturityDate); |
136 | 0 | const Real sqrtT = std::sqrt(maturity); |
137 | |
|
138 | 0 | const detail::VectorBsmProcessExtractor pExtractor(processes_); |
139 | 0 | const Array s = pExtractor.getSpot(); |
140 | 0 | const Array dq = pExtractor.getDividendYieldDf(maturityDate); |
141 | 0 | const Array stdDev = Sqrt(pExtractor.getBlackVariance(maturityDate)); |
142 | 0 | const Array vols = stdDev / sqrtT; |
143 | |
|
144 | 0 | const SymmetricSchurDecomposition schur( |
145 | 0 | getCovariance(vols.begin(), vols.end(), rho_)); |
146 | 0 | const Matrix& Q = schur.eigenvectors(); |
147 | 0 | const Array& l = schur.eigenvalues(); |
148 | |
|
149 | 0 | const Real eps = 1e-4; |
150 | 0 | std::vector<ext::shared_ptr<Fdm1dMesher> > meshers; |
151 | |
|
152 | 0 | for (Size i=0; i < processes_.size(); ++i) { |
153 | 0 | const Size xGrid = (xGrids_.size() > 1) |
154 | 0 | ? xGrids_[i] |
155 | 0 | : std::max(Size(4), Size(xGrids_[0]*std::pow(l[i]/l[0], 0.1))); |
156 | 0 | QL_REQUIRE(xGrid >= 4, "minimum grid size is four"); |
157 | | |
158 | 0 | const Real xStepStize = (1.0-2*eps)/(xGrid-1); |
159 | |
|
160 | 0 | std::vector<Real> x(xGrid); |
161 | 0 | for (Size j=0; j < xGrid; ++j) |
162 | 0 | x[j] = 1.3*std::sqrt(l[i])*sqrtT |
163 | 0 | *InverseCumulativeNormal()(eps + j*xStepStize); |
164 | |
|
165 | 0 | meshers.emplace_back(ext::make_shared<Predefined1dMesher>(x)); |
166 | 0 | } |
167 | | |
168 | 0 | const ext::shared_ptr<FdmMesherComposite> mesher = |
169 | 0 | ext::make_shared<FdmMesherComposite>(meshers); |
170 | |
|
171 | 0 | const ext::shared_ptr<BasketPayoff> payoff |
172 | 0 | = ext::dynamic_pointer_cast<BasketPayoff>(arguments_.payoff); |
173 | 0 | QL_REQUIRE(payoff, "basket payoff expected"); |
174 | | |
175 | 0 | const ext::shared_ptr<YieldTermStructure> rTS = |
176 | 0 | processes_[0]->riskFreeRate().currentLink(); |
177 | |
|
178 | 0 | std::vector<ext::shared_ptr<YieldTermStructure>> qTS(processes_.size()); |
179 | 0 | for (Size i=0; i < processes_.size(); ++i) |
180 | 0 | qTS[i] = processes_[i]->dividendYield().currentLink(); |
181 | |
|
182 | 0 | const ext::shared_ptr<FdmInnerValueCalculator> calculator = |
183 | 0 | ext::make_shared<detail::FdmPCABasketInnerValue>( |
184 | 0 | payoff, mesher, |
185 | 0 | Log(s), stdDev/sqrtT, |
186 | 0 | qTS, rTS, |
187 | 0 | Q, l |
188 | 0 | ); |
189 | |
|
190 | 0 | const ext::shared_ptr<FdmStepConditionComposite> conditions |
191 | 0 | = FdmStepConditionComposite::vanillaComposite( |
192 | 0 | DividendSchedule(), arguments_.exercise, |
193 | 0 | mesher, calculator, |
194 | 0 | rTS->referenceDate(), rTS->dayCounter()); |
195 | |
|
196 | 0 | const FdmBoundaryConditionSet boundaries; |
197 | 0 | const FdmSolverDesc solverDesc |
198 | 0 | = { mesher, boundaries, conditions, calculator, |
199 | 0 | maturity, tGrid_, dampingSteps_ }; |
200 | |
|
201 | 0 | const bool isEuropean = |
202 | 0 | ext::dynamic_pointer_cast<EuropeanExercise>(arguments_.exercise) != nullptr; |
203 | 0 | const ext::shared_ptr<FdmWienerOp> op = |
204 | 0 | ext::make_shared<FdmWienerOp>( |
205 | 0 | mesher, |
206 | 0 | (isEuropean)? ext::shared_ptr<YieldTermStructure>() : rTS, |
207 | 0 | l); |
208 | |
|
209 | 0 | switch(processes_.size()) { |
210 | 0 | #define BOOST_PP_LOCAL_MACRO(n) \ |
211 | 0 | case n : \ |
212 | 0 | results_.value = ext::make_shared<FdmNdimSolver<n>>( \ |
213 | 0 | solverDesc, schemeDesc_, op)->interpolateAt( \ |
214 | 0 | std::vector<Real>(processes_.size(), 0.0)); \ |
215 | 0 | break; |
216 | 0 | #define BOOST_PP_LOCAL_LIMITS (1, PDE_MAX_SUPPORTED_DIM) |
217 | 0 | #include BOOST_PP_LOCAL_ITERATE() |
218 | 0 | default: |
219 | 0 | QL_FAIL("Not implemented for " << processes_.size() << " processes"); |
220 | 0 | } |
221 | | |
222 | 0 | if (isEuropean) |
223 | 0 | results_.value *= pExtractor.getInterestRateDf(maturityDate); |
224 | 0 | } |
225 | | } |