/src/quantlib/ql/experimental/credit/lossdistribution.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2008 Roland Lichters |
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/experimental/credit/lossdistribution.hpp> |
21 | | #include <ql/math/randomnumbers/mt19937uniformrng.hpp> |
22 | | |
23 | | using namespace std; |
24 | | |
25 | | namespace QuantLib { |
26 | | |
27 | | //-------------------------------------------------------------------------- |
28 | 0 | Real LossDist::binomialProbabilityOfNEvents(int n, vector<Real>& p) { |
29 | | //-------------------------------------------------------------------------- |
30 | 0 | BinomialDistribution binomial (p[0], p.size()); |
31 | 0 | return binomial(n); |
32 | 0 | } |
33 | | |
34 | | //-------------------------------------------------------------------------- |
35 | 0 | Real LossDist::binomialProbabilityOfAtLeastNEvents(int n, vector<Real>& p) { |
36 | | //-------------------------------------------------------------------------- |
37 | 0 | CumulativeBinomialDistribution binomial(p[0], p.size()); |
38 | 0 | return 1.0 - binomial(n-1); |
39 | | /* |
40 | | Real defp = 0; |
41 | | for (Size i = n; i <= p.size(); i++) |
42 | | defp += binomialProbabilityOfNEvents (i, p); |
43 | | |
44 | | return defp; |
45 | | */ |
46 | 0 | } |
47 | | |
48 | | //-------------------------------------------------------------------------- |
49 | 0 | vector<Real> LossDist::probabilityOfNEvents(vector<Real>& p) { |
50 | | //-------------------------------------------------------------------------- |
51 | 0 | Size n = p.size(); |
52 | 0 | vector<Real> probability(n+1, 0.0); |
53 | 0 | vector<Real> prev; |
54 | 0 | probability[0] = 1.0; |
55 | 0 | for (Size j = 0; j < n; j++) { |
56 | 0 | prev = probability; |
57 | 0 | probability[0] = prev[0] * (1.0 - p[j]); |
58 | 0 | for (Size i = 1; i <= j; i++) |
59 | 0 | probability[i] = prev[i-1] * p[j] + prev[i] * (1.0 - p[j]); |
60 | 0 | probability[j+1] = prev[j] * p[j]; |
61 | 0 | } |
62 | |
|
63 | 0 | return probability; |
64 | 0 | } |
65 | | |
66 | | //-------------------------------------------------------------------------- |
67 | 0 | Real LossDist::probabilityOfNEvents(int k, vector<Real>& p) { |
68 | | //-------------------------------------------------------------------------- |
69 | 0 | return probabilityOfNEvents(p)[k]; |
70 | | |
71 | | // vector<Real> w (p.size(), 0); |
72 | | // vector<Real> u (k+1, 0); |
73 | | // vector<Real> v (k+1, 0); |
74 | | |
75 | | // Real pZero = 1.0; |
76 | | // for (Size i = 0; i < w.size(); i++) { |
77 | | // pZero *= (1.0 - p[i]); |
78 | | // w[i] = p[i] / (1.0 - p[i]); |
79 | | // } |
80 | | |
81 | | // if (k == 0) return pZero; |
82 | | |
83 | | // int kk = k; |
84 | | // Real prodw = 1.0; |
85 | | |
86 | | // Cumulated probability of up to n events: |
87 | | // Cut off when the cumulated probability reaches 1, |
88 | | // i.e. set all following probabilities of exactly n events to zero. |
89 | | // Real sum = 1.0; |
90 | | |
91 | | // u[0] = 1.0; |
92 | | // for (int i = 1; i <= kk; i++) { |
93 | | // v[i] = 0; |
94 | | // for (Size j = 0; j < w.size(); j++) |
95 | | // v[i] += pow (w[j], i); |
96 | | // u[i] = 0; |
97 | | // for (int j = 1; j <= i; j++) |
98 | | // u[i] += pow (-1.0, j+1) * v[j] * u[i-j]; |
99 | | // u[i] /= i; |
100 | | |
101 | | // cut off |
102 | | // if (sum * pZero >= 1.0 || u[i] < 0 || u[i] * pZero >= 1.0) |
103 | | // u[i] = 0; |
104 | | |
105 | | // sum += u[i]; |
106 | | // } |
107 | | |
108 | | // return pZero * prodw * u[kk]; |
109 | 0 | } |
110 | | |
111 | | //-------------------------------------------------------------------------- |
112 | 0 | Real LossDist::probabilityOfAtLeastNEvents (int k, vector<Real>& p) { |
113 | | //-------------------------------------------------------------------------- |
114 | 0 | vector<Real> probability = probabilityOfNEvents(p); |
115 | 0 | Real sum = 1.0; |
116 | 0 | for (int j = 0; j < k; j++) |
117 | 0 | sum -= probability[j]; |
118 | 0 | return sum; |
119 | | /* |
120 | | Real sum = 0; |
121 | | for (Size i = k; i <= p.size(); i++) |
122 | | sum += probabilityOfNEvents (i, p); |
123 | | return sum; |
124 | | */ |
125 | 0 | } |
126 | | |
127 | | //-------------------------------------------------------------------------- |
128 | 0 | Real ProbabilityOfNEvents::operator()(vector<Real> p) const { |
129 | | //-------------------------------------------------------------------------- |
130 | 0 | return LossDist::probabilityOfNEvents (n_, p); |
131 | 0 | } |
132 | | |
133 | | //-------------------------------------------------------------------------- |
134 | 0 | Real ProbabilityOfAtLeastNEvents::operator()(vector<Real> p) const { |
135 | | //-------------------------------------------------------------------------- |
136 | 0 | return LossDist::probabilityOfAtLeastNEvents (n_, p); |
137 | 0 | } |
138 | | |
139 | | //-------------------------------------------------------------------------- |
140 | 0 | Real BinomialProbabilityOfAtLeastNEvents::operator()(vector<Real> p) const { |
141 | | //-------------------------------------------------------------------------- |
142 | 0 | return LossDist::binomialProbabilityOfAtLeastNEvents(n_, p); |
143 | 0 | } |
144 | | |
145 | | //-------------------------------------------------------------------------- |
146 | | Distribution LossDistBinomial::operator()(Size n, Real volume, |
147 | 0 | Real probability) const { |
148 | | //-------------------------------------------------------------------------- |
149 | 0 | n_ = n; |
150 | 0 | probability_.clear(); |
151 | 0 | probability_.resize(n_+1, 0.0); |
152 | 0 | Distribution dist (nBuckets_, 0.0, maximum_); |
153 | 0 | BinomialDistribution binomial (probability, n); |
154 | 0 | for (Size i = 0; i <= n; i++) { |
155 | 0 | if (volume_ * i <= maximum_) { |
156 | 0 | probability_[i] = binomial(i); |
157 | 0 | Size bucket = dist.locate(volume * i); |
158 | 0 | dist.addDensity (bucket, probability_[i] / dist.dx(bucket)); |
159 | 0 | dist.addAverage (bucket, volume * i); |
160 | 0 | } |
161 | 0 | } |
162 | |
|
163 | 0 | excessProbability_.clear(); |
164 | 0 | excessProbability_.resize(n_+1, 0.0); |
165 | 0 | excessProbability_[n_] = probability_[n_]; |
166 | 0 | for (int k = n_-1; k >= 0; k--) |
167 | 0 | excessProbability_[k] = excessProbability_[k+1] + probability_[k]; |
168 | |
|
169 | 0 | dist.normalize(); |
170 | |
|
171 | 0 | return dist; |
172 | 0 | } |
173 | | |
174 | | //-------------------------------------------------------------------------- |
175 | | Distribution LossDistBinomial::operator()(const vector<Real>& nominals, |
176 | 0 | const vector<Real>& probabilities) const { |
177 | | //-------------------------------------------------------------------------- |
178 | 0 | return operator()(nominals.size(), nominals[0], probabilities[0]); |
179 | 0 | } |
180 | | |
181 | | //-------------------------------------------------------------------------- |
182 | | Distribution LossDistHomogeneous::operator()(Real volume, |
183 | 0 | const vector<Real>& p) const { |
184 | | //-------------------------------------------------------------------------- |
185 | 0 | volume_ = volume; |
186 | 0 | n_ = p.size(); |
187 | 0 | probability_.clear(); |
188 | 0 | probability_.resize(n_+1, 0.0); |
189 | 0 | vector<Real> prev; |
190 | 0 | probability_[0] = 1.0; |
191 | 0 | for (Size k = 0; k < n_; k++) { |
192 | 0 | prev = probability_; |
193 | 0 | probability_[0] = prev[0] * (1.0 - p[k]); |
194 | 0 | for (Size i = 1; i <= k; i++) |
195 | 0 | probability_[i] = prev[i-1] * p[k] + prev[i] * (1.0 - p[k]); |
196 | 0 | probability_[k+1] = prev[k] * p[k]; |
197 | 0 | } |
198 | |
|
199 | 0 | excessProbability_.clear(); |
200 | 0 | excessProbability_.resize(n_+1, 0.0); |
201 | 0 | excessProbability_[n_] = probability_[n_]; |
202 | 0 | for (int k = n_ - 1; k >= 0; k--) |
203 | 0 | excessProbability_[k] = excessProbability_[k+1] + probability_[k]; |
204 | |
|
205 | 0 | Distribution dist (nBuckets_, 0.0, maximum_); |
206 | 0 | for (Size i = 0; i <= n_; i++) { |
207 | 0 | if (volume * i <= maximum_) { |
208 | 0 | Size bucket = dist.locate(volume * i); |
209 | 0 | dist.addDensity (bucket, probability_[i] / dist.dx(bucket)); |
210 | 0 | dist.addAverage (bucket, volume*i); |
211 | 0 | } |
212 | 0 | } |
213 | |
|
214 | 0 | dist.normalize(); |
215 | |
|
216 | 0 | return dist; |
217 | 0 | } |
218 | | |
219 | | //-------------------------------------------------------------------------- |
220 | | Distribution LossDistHomogeneous::operator()(const vector<Real>& nominals, |
221 | 0 | const vector<Real>& probabilities) const { |
222 | | //-------------------------------------------------------------------------- |
223 | 0 | return operator()(nominals[0], probabilities); |
224 | 0 | } |
225 | | |
226 | | //-------------------------------------------------------------------------- |
227 | | Distribution LossDistBucketing::operator()(const vector<Real>& nominals, |
228 | 0 | const vector<Real>& probabilities) const { |
229 | | //-------------------------------------------------------------------------- |
230 | 0 | QL_REQUIRE (nominals.size() == probabilities.size(), "sizes differ: " |
231 | 0 | << nominals.size() << " vs " << probabilities.size()); |
232 | | |
233 | 0 | vector<Real> p (nBuckets_, 0.0); |
234 | 0 | vector<Real> a (nBuckets_, 0.0); |
235 | 0 | vector<Real> ap (nBuckets_, 0.0); |
236 | |
|
237 | 0 | p[0] = 1.0; |
238 | 0 | a[0] = 0.0; |
239 | 0 | Real dx = maximum_ / nBuckets_; |
240 | 0 | for (Size k = 1; k < nBuckets_; k++) |
241 | 0 | a[k] = dx * k + dx/2; |
242 | |
|
243 | 0 | for (Size i = 0; i < nominals.size(); i++) { |
244 | 0 | Real L = nominals[i]; |
245 | 0 | Real P = probabilities[i]; |
246 | 0 | for (int k = a.size()-1; k >= 0; k--) { |
247 | 0 | if (p[k] > 0) { |
248 | 0 | int u = locateTargetBucket (a[k] + L, k); |
249 | 0 | QL_REQUIRE (u >= 0, "u=" << u << " at i=" << i << " k=" << k); |
250 | 0 | QL_REQUIRE (u >= k, "u=" << u << "<k=" << k << " at i=" << i); |
251 | | |
252 | 0 | Real dp = p[k] * P; |
253 | 0 | if (u == k) |
254 | 0 | a[k] += P * L; |
255 | 0 | else { |
256 | | // no update of a[u] and p[u] if u is beyond grid end |
257 | 0 | if (u < int(nBuckets_)) { |
258 | | // a[u] remains unchanged, if dp = 0 |
259 | 0 | if (dp > 0.0) { |
260 | | // on Windows, p[u]/dp could cause a NaN for |
261 | | // some very small values of p[k]. |
262 | | // Writing the above as (p[u]/p[k])/P prevents |
263 | | // the NaN. What can I say? |
264 | 0 | Real f = 1.0 / (1.0 + (p[u]/p[k]) / P); |
265 | 0 | a[u] = (1.0 - f) * a[u] + f * (a[k] + L); |
266 | 0 | } |
267 | | /* formulation of Hull-White: |
268 | | if (p[u] + dp > 0) |
269 | | a[u] = (p[u] * a[u] + dp * (a[k] + L)) |
270 | | / (p[u] + dp); |
271 | | */ |
272 | 0 | p[u] += dp; |
273 | 0 | } |
274 | 0 | p[k] -= dp; |
275 | 0 | } |
276 | 0 | } |
277 | 0 | QL_REQUIRE(a[k] + epsilon_ >= dx * k && a[k] < dx * (k+1), |
278 | 0 | "a out of range at k=" << k << ", contract " << i); |
279 | 0 | } |
280 | 0 | } |
281 | | |
282 | 0 | Distribution dist (nBuckets_, 0.0, maximum_); |
283 | 0 | for (Size i = 0; i < nBuckets_; i++) { |
284 | 0 | dist.addDensity (i, p[i] / dx); |
285 | 0 | dist.addAverage (i, a[i]); |
286 | 0 | } |
287 | |
|
288 | 0 | return dist; |
289 | 0 | } |
290 | | |
291 | | //-------------------------------------------------------------------------- |
292 | 0 | int LossDistBucketing::locateTargetBucket (Real loss, Size i0) const { |
293 | | //-------------------------------------------------------------------------- |
294 | 0 | QL_REQUIRE (loss >= 0, "loss " << loss << " must be >= 0"); |
295 | 0 | Real dx = maximum_ / nBuckets_; |
296 | 0 | for (Size i = i0; i < nBuckets_; i++) |
297 | 0 | if (dx * i > loss + epsilon_) return i - 1; |
298 | 0 | return nBuckets_; |
299 | 0 | } |
300 | | |
301 | | //-------------------------------------------------------------------------- |
302 | | Distribution LossDistMonteCarlo::operator()(const vector<Real>& nominals, |
303 | 0 | const vector<Real>& probabilities) const { |
304 | | //-------------------------------------------------------------------------- |
305 | 0 | Distribution dist (nBuckets_, 0.0, maximum_); |
306 | | // KnuthUniformRng rng(seed_); |
307 | | // LecuyerUniformRng rng(seed_); |
308 | 0 | MersenneTwisterUniformRng rng(seed_); |
309 | 0 | for (Size i = 0; i < simulations_; i++) { |
310 | 0 | Real e = 0; |
311 | 0 | for (Size j = 0; j < nominals.size(); j++) { |
312 | 0 | Real r = rng.next().value; |
313 | 0 | if (r <= probabilities[j]) |
314 | 0 | e += nominals[j]; |
315 | 0 | } |
316 | 0 | dist.add (e + epsilon_); |
317 | 0 | } |
318 | |
|
319 | 0 | dist.normalize(); |
320 | |
|
321 | 0 | return dist; |
322 | 0 | } |
323 | | |
324 | | } |