/src/quantlib/ql/processes/jointstochasticprocess.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) 2007 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 | | /*! \file jointstochasticprocess.cpp |
21 | | \brief multi model process for hybrid products |
22 | | */ |
23 | | |
24 | | #include <ql/math/matrixutilities/pseudosqrt.hpp> |
25 | | #include <ql/math/matrixutilities/svd.hpp> |
26 | | #include <ql/processes/jointstochasticprocess.hpp> |
27 | | #include <utility> |
28 | | |
29 | | namespace QuantLib { |
30 | | |
31 | | JointStochasticProcess::JointStochasticProcess( |
32 | | std::vector<ext::shared_ptr<StochasticProcess> > l, Size factors) |
33 | 0 | : l_(std::move(l)), factors_(factors) { |
34 | |
|
35 | 0 | for (const auto& iter : l_) { |
36 | 0 | registerWith(iter); |
37 | 0 | } |
38 | |
|
39 | 0 | vsize_.reserve (l_.size()+1); |
40 | 0 | vfactors_.reserve(l_.size()+1); |
41 | |
|
42 | 0 | for (const auto& iter : l_) { |
43 | 0 | vsize_.push_back(size_); |
44 | 0 | size_ += iter->size(); |
45 | |
|
46 | 0 | vfactors_.push_back(modelFactors_); |
47 | 0 | modelFactors_ += iter->factors(); |
48 | 0 | } |
49 | |
|
50 | 0 | vsize_.push_back(size_); |
51 | 0 | vfactors_.push_back(modelFactors_); |
52 | |
|
53 | 0 | if (factors_ == Null<Size>()) { |
54 | 0 | factors_ = modelFactors_; |
55 | 0 | } else { |
56 | 0 | QL_REQUIRE(factors_ <= size_, "too many factors given"); |
57 | 0 | } |
58 | 0 | } |
59 | | |
60 | 0 | Size JointStochasticProcess::size() const { |
61 | 0 | return size_; |
62 | 0 | } |
63 | | |
64 | 0 | Size JointStochasticProcess::factors() const { |
65 | 0 | return factors_; |
66 | 0 | } |
67 | | |
68 | | Array JointStochasticProcess::slice(const Array& x, |
69 | 0 | Size i) const { |
70 | | // cut out the ith process' variables |
71 | 0 | Size n = vsize_[i+1]-vsize_[i]; |
72 | 0 | Array y(n); |
73 | 0 | std::copy(x.begin()+vsize_[i], x.begin()+vsize_[i+1], y.begin()); |
74 | 0 | return y; |
75 | 0 | } |
76 | | |
77 | 0 | Array JointStochasticProcess::initialValues() const { |
78 | 0 | Array retVal(size()); |
79 | |
|
80 | 0 | for (auto iter = l_.begin(); iter != l_.end(); ++iter) { |
81 | 0 | const Array& pInitValues = (*iter)->initialValues(); |
82 | |
|
83 | 0 | std::copy(pInitValues.begin(), pInitValues.end(), |
84 | 0 | retVal.begin()+vsize_[iter - l_.begin()]); |
85 | 0 | } |
86 | |
|
87 | 0 | return retVal; |
88 | 0 | } |
89 | | |
90 | | |
91 | | Array JointStochasticProcess::drift(Time t, |
92 | 0 | const Array& x) const { |
93 | 0 | Array retVal(size()); |
94 | |
|
95 | 0 | for (Size i=0; i < l_.size(); ++i) { |
96 | |
|
97 | 0 | const Array& pDrift = l_[i]->drift(t, slice(x,i)); |
98 | |
|
99 | 0 | std::copy(pDrift.begin(), pDrift.end(), |
100 | 0 | retVal.begin()+vsize_[i]); |
101 | 0 | } |
102 | |
|
103 | 0 | return retVal; |
104 | 0 | } |
105 | | |
106 | | Array JointStochasticProcess::expectation(Time t0, |
107 | | const Array& x0, |
108 | 0 | Time dt) const { |
109 | 0 | Array retVal(size()); |
110 | |
|
111 | 0 | for (Size i=0; i < l_.size(); ++i) { |
112 | |
|
113 | 0 | const Array& pExpectation = l_[i]->expectation(t0, slice(x0,i), dt); |
114 | |
|
115 | 0 | std::copy(pExpectation.begin(), pExpectation.end(), |
116 | 0 | retVal.begin()+ vsize_[i]); |
117 | 0 | } |
118 | |
|
119 | 0 | return retVal; |
120 | 0 | } |
121 | | |
122 | | |
123 | 0 | Matrix JointStochasticProcess::diffusion(Time t, const Array& x) const { |
124 | | // might need some improvement in the future |
125 | 0 | const Time dt = 0.001; |
126 | 0 | return pseudoSqrt(covariance(t, x, dt)/dt); |
127 | 0 | } |
128 | | |
129 | | |
130 | | Matrix JointStochasticProcess::covariance(Time t0, |
131 | | const Array& x0, |
132 | 0 | Time dt) const { |
133 | | |
134 | | // get the model intrinsic covariance matrix |
135 | 0 | Matrix retVal(size(), size(), 0.0); |
136 | |
|
137 | 0 | for (Size j=0; j < l_.size(); ++j) { |
138 | |
|
139 | 0 | const Size vs = vsize_[j]; |
140 | 0 | const Matrix& pCov = l_[j]->covariance(t0, slice(x0,j), dt); |
141 | |
|
142 | 0 | for (Size i=0; i < pCov.rows(); ++i) { |
143 | 0 | std::copy(pCov.row_begin(i), pCov.row_end(i), |
144 | 0 | retVal.row_begin(vs+i) + vs); |
145 | 0 | } |
146 | 0 | } |
147 | | |
148 | | // add the cross model covariance matrix |
149 | 0 | const Array& volatility = Sqrt(retVal.diagonal()); |
150 | 0 | Matrix crossModelCovar = this->crossModelCorrelation(t0, x0); |
151 | |
|
152 | 0 | for (Size i=0; i < size(); ++i) { |
153 | 0 | for (Size j=0; j < size(); ++j) { |
154 | 0 | crossModelCovar[i][j] *= volatility[i]*volatility[j]; |
155 | 0 | } |
156 | 0 | } |
157 | |
|
158 | 0 | retVal += crossModelCovar; |
159 | |
|
160 | 0 | return retVal; |
161 | 0 | } |
162 | | |
163 | | |
164 | | Matrix JointStochasticProcess::stdDeviation(Time t0, |
165 | | const Array& x0, |
166 | 0 | Time dt) const { |
167 | 0 | return pseudoSqrt(covariance(t0, x0, dt)); |
168 | 0 | } |
169 | | |
170 | | |
171 | | Array JointStochasticProcess::apply(const Array& x0, |
172 | 0 | const Array& dx) const { |
173 | 0 | Array retVal(size()); |
174 | |
|
175 | 0 | for (Size i=0; i < l_.size(); ++i) { |
176 | 0 | const Array& pApply = l_[i]->apply(slice(x0,i), slice(dx,i)); |
177 | |
|
178 | 0 | std::copy(pApply.begin(), pApply.end(), |
179 | 0 | retVal.begin()+vsize_[i]); |
180 | 0 | } |
181 | |
|
182 | 0 | return retVal; |
183 | 0 | } |
184 | | |
185 | | Array JointStochasticProcess::evolve( |
186 | 0 | Time t0, const Array& x0, Time dt, const Array& dw) const { |
187 | 0 | Array dv(modelFactors_); |
188 | |
|
189 | 0 | if ( correlationIsStateDependent() |
190 | 0 | || correlationCache_.count(CachingKey(t0, dt)) == 0) { |
191 | 0 | Matrix cov = covariance(t0, x0, dt); |
192 | |
|
193 | 0 | const Array& sqrtDiag = Sqrt(cov.diagonal()); |
194 | 0 | for (Size i=0; i < cov.rows(); ++i) { |
195 | 0 | for (Size j=i; j < cov.columns(); ++j) { |
196 | 0 | const Real div = sqrtDiag[i]*sqrtDiag[j]; |
197 | |
|
198 | 0 | cov[i][j] = cov[j][i] = ( div > 0) ? Real(cov[i][j]/div) : 0.0; |
199 | 0 | } |
200 | 0 | } |
201 | |
|
202 | 0 | Matrix diff(size(), modelFactors_, 0.0); |
203 | |
|
204 | 0 | for (Size j = 0; j < l_.size(); ++j) { |
205 | 0 | const Size vs = vsize_ [j]; |
206 | 0 | const Size vf = vfactors_[j]; |
207 | |
|
208 | 0 | Matrix stdDev = l_[j]->stdDeviation(t0, slice(x0,j), dt); |
209 | |
|
210 | 0 | for (Size i=0; i < stdDev.rows(); ++i) { |
211 | 0 | const Volatility vol = std::sqrt( |
212 | 0 | std::inner_product(stdDev.row_begin(i), |
213 | 0 | stdDev.row_end(i), |
214 | 0 | stdDev.row_begin(i), Real(0.0))); |
215 | 0 | if (vol > 0.0) { |
216 | 0 | std::transform(stdDev.row_begin(i), stdDev.row_end(i), |
217 | 0 | stdDev.row_begin(i), |
218 | 0 | [=](Real x) -> Real { return x / vol; }); |
219 | 0 | } |
220 | 0 | else { |
221 | | // keep the svd happy |
222 | 0 | std::fill(stdDev.row_begin(i), stdDev.row_end(i), |
223 | 0 | 100*i*QL_EPSILON); |
224 | 0 | } |
225 | 0 | } |
226 | |
|
227 | 0 | SVD svd(stdDev); |
228 | 0 | const Array& s = svd.singularValues(); |
229 | 0 | Matrix w(s.size(), s.size(), 0.0); |
230 | 0 | for (Size i=0; i < s.size(); ++i) { |
231 | 0 | if (std::fabs(s[i]) > std::sqrt(QL_EPSILON)) { |
232 | 0 | w[i][i] = 1.0/s[i]; |
233 | 0 | } |
234 | 0 | } |
235 | |
|
236 | 0 | const Matrix inv = svd.U() * w * transpose(svd.V()); |
237 | |
|
238 | 0 | for (Size i=0; i < stdDev.rows(); ++i) { |
239 | 0 | std::copy(inv.row_begin(i), inv.row_end(i), |
240 | 0 | diff.row_begin(i+vs)+vf); |
241 | 0 | } |
242 | 0 | } |
243 | |
|
244 | 0 | Matrix rs = rankReducedSqrt(cov, factors_, 1.0, |
245 | 0 | SalvagingAlgorithm::Spectral); |
246 | |
|
247 | 0 | if (rs.columns() < factors_) { |
248 | | // less eigenvalues than expected factors. |
249 | | // fill the rest with zero's. |
250 | 0 | Matrix tmp = Matrix(cov.rows(), factors_, 0.0); |
251 | 0 | for (Size i=0; i < cov.rows(); ++i) { |
252 | 0 | std::copy(rs.row_begin(i), rs.row_end(i), |
253 | 0 | tmp.row_begin(i)); |
254 | 0 | } |
255 | 0 | rs = tmp; |
256 | 0 | } |
257 | |
|
258 | 0 | const Matrix m = transpose(diff) * rs; |
259 | |
|
260 | 0 | if (!correlationIsStateDependent()) { |
261 | 0 | correlationCache_[CachingKey(t0,dt)] = m; |
262 | 0 | } |
263 | 0 | dv = m*dw; |
264 | 0 | } |
265 | 0 | else { |
266 | 0 | if (!correlationIsStateDependent()) { |
267 | 0 | dv = correlationCache_[CachingKey(t0,dt)] * dw; |
268 | 0 | } |
269 | 0 | } |
270 | |
|
271 | 0 | this->preEvolve(t0, x0, dt, dv); |
272 | | |
273 | |
|
274 | 0 | Array retVal(size()); |
275 | 0 | for (auto iter = l_.begin(); iter != l_.end(); ++iter) { |
276 | 0 | const Size i = iter - l_.begin(); |
277 | |
|
278 | 0 | Array dz((*iter)->factors()); |
279 | 0 | std::copy(dv.begin()+vfactors_[i], |
280 | 0 | dv.begin()+vfactors_[i] + (*iter)->factors(), |
281 | 0 | dz.begin()); |
282 | 0 | Array x((*iter)->size()); |
283 | 0 | std::copy(x0.begin()+vsize_[i], |
284 | 0 | x0.begin()+vsize_[i] + (*iter)->size(), |
285 | 0 | x.begin()); |
286 | 0 | const Array r = (*iter)->evolve(t0, x, dt, dz); |
287 | 0 | std::copy(r.begin(), r.end(), retVal.begin()+vsize_[i]); |
288 | 0 | } |
289 | |
|
290 | 0 | return this->postEvolve(t0, x0, dt, dv, retVal); |
291 | 0 | } |
292 | | |
293 | | const std::vector<ext::shared_ptr<StochasticProcess> > & |
294 | 0 | JointStochasticProcess::constituents() const { |
295 | 0 | return l_; |
296 | 0 | } |
297 | | |
298 | 0 | Time JointStochasticProcess::time(const Date& date) const { |
299 | 0 | QL_REQUIRE(!l_.empty(), "process list is empty"); |
300 | | |
301 | 0 | return l_[0]->time(date); |
302 | 0 | } |
303 | | |
304 | 0 | void JointStochasticProcess::update() { |
305 | | // clear all caches |
306 | 0 | correlationCache_.clear(); |
307 | |
|
308 | 0 | this->StochasticProcess::update(); |
309 | 0 | } |
310 | | } |