QuantLib: a free/open-source library for quantitative finance
fully annotated source code - version 1.34
Loading...
Searching...
No Matches
recursivelossmodel.hpp
Go to the documentation of this file.
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2009, 2014 Jose Aparicio
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#ifndef quantlib_recursive_loss_model_hpp
21#define quantlib_recursive_loss_model_hpp
22
25#include <map>
26#include <algorithm>
27
28namespace QuantLib {
29
30 /*! Recursive STCDO default loss model for a heterogeneous pool of names.
31 The pool names are heterogeneous in their default probabilities, notionals
32 and recovery rates. Correlations are given by the latent model.
33 The recursive pricing algorithm used here is described in Andersen, Sidenius
34 and Basu; "All your hedges in one basket", Risk, November 2003, pages 67-72
35
36 Notice that using copulas other than Gaussian it is only an
37 approximation (see remark on p.68).
38
39 \todo Make the loss unit equal to some small fraction depending on the
40 portfolio loss weights (notionals and recoveries). As it is now this
41 is ok for pricing but not for risk metrics. See the discussion in O'Kane
42 18.3.2
43 \todo Intengrands should all use the inverted probabilities for
44 performance instead of calling the copula inversion with the same vals.
45 */
46 template<class copulaPolicy>
48 public:
50 const ext::shared_ptr<ConstantLossLatentmodel<copulaPolicy> >& m,
51 // nope! use max common divisor. See O'Kane. Or give both options at least.
52 Size nbuckets = 1)
53 : copula_(m), nBuckets_(nbuckets) {}
54
55 private:
56 /*!
57 @param pDefDate Vector of unconditional default probabilities for each
58 live name (at the current evaluation date). This is passed instead of
59 the date for performance reasons (if in the future other magnitudes
60 -e.g. lgd- are contingent on the date they shouldd be passed too).
61 */
62 std::map<Real, Probability> conditionalLossDistrib(const std::vector<Probability>& pDefDate,
63 const std::vector<Real>& mktFactor) const;
64 Real expectedConditionalLoss(const std::vector<Probability>& pDefDate, //<< never used!!
65 const std::vector<Real>& mktFactor) const;
66 std::vector<Real> conditionalLossProb(const std::vector<Probability>& pDefDate,
67 // const Date& date,
68 const std::vector<Real>& mktFactor) const;
69 // versions using the P-inverse, deprecate the former
70 std::map<Real, Probability> conditionalLossDistribInvP(const std::vector<Real>& pDefDate,
71 // const Date& date,
72 const std::vector<Real>& mktFactor) const;
73 Real expectedConditionalLossInvP(const std::vector<Real>& pDefDate,
74 // const Date& date,
75 const std::vector<Real>& mktFactor) const;
76 protected:
77 void resetModel() override;
78
79 public:
80 /* Expected tranche Loss calculation.
81 This is computed from the first equation on page 70 (not numbered)
82 Notice that while we want to compute:
83 \f[
84 EL(t) = \sum_{l_k}l_k P(l;t) =
85 \sum_{l_k}l_k \int P(l_k;t|\omega) d\omega q(\omega)
86 \f]
87 One can invert the sumation and the integral order to:
88 \f[
89 EL(t) = \int\,q(\omega)\,d\omega\,\sum_{l_k}\,l_k\,P(l_k;t|\omega) =
90 \int\,q(\omega)\,d\omega\,EL(t|\omega)
91 \f]
92 and this is the way it is integrated here. The recursion formula
93 makes it easier this way.
94 */
95 Real expectedTrancheLoss(const Date& date) const override;
96 std::vector<Real> lossProbability(const Date& date) const;
97 // REMEBER THIS HAS TO BE MOVED TO A DISTRIBUTION OBJECT.............
98 std::map<Real, Probability> lossDistribution(const Date& d) const override;
99 // INTEGRATE THEN SEARCH RATHER THAN SEARCH AND THEN INTEGRATE:
100 // Here I am not using a search because the point might not be attainable
101 // (loss distrib is not continuous)
102 Real percentile(const Date& d, Real percentile) const override;
103 Real expectedShortfall(const Date& d, Real perctl) const override;
104
105 protected:
106 const ext::shared_ptr<ConstantLossLatentmodel<copulaPolicy> > copula_;
107 private:
108 // loss model descriptor members
110 mutable std::vector<Real> wk_;
112 //! name to name factor. In the single factor copula:
113 // correl = beta * beta
114 // When constructing through a single correlation number the factor is
115 // taken to be the positive swuare root of this number in the copula.
116 ////////in the latent model now: mutable std::vector<Real> oneFactorCorrels_;
117 // cached remaining basket magnitudes:
122 mutable std::vector<Real> notionals_;
123 };
124
125
127
128 // Inlines ------------------------------------------------
129
130 template<class CP>
132 const Date& date) const
133 {
134/*
135 std::map<Real, Probability> dist = lossDistribution(date);
136
137 Real expLoss = 0.;
138 std::map<Real, Probability>::iterator distIt = dist.begin();
139
140 while(distIt != dist.end()) {
141 Real loss = distIt->first * lossUnit_;
142 loss = std::max(std::min(loss, detachAmount_)-attachAmount_, 0.);
143 // MIN MAX BUGS ....??
144 expLoss += loss * distIt->second;
145 distIt++;
146 }
147 return expLoss ;
148
149
150
151
152 ///////////////////////////////////////////////////////////////////////
153
154 // calculate inverted unconditional Ps first so we save the inversion:
155 // TO DO : turn to STL algorithm code
156 std::vector<Probability> uncDefProb =
157 basket_->remainingProbabilities(date);
158
159 return copula_->integratedExpectedValue(
160 [&](const std::vector<Real>& v1) {
161 return expectedConditionalLoss(uncDefProb, v1);
162 });
163 */
164
165 std::vector<Probability> uncDefProb =
166 basket_->remainingProbabilities(date);
167 std::vector<Real> invProb;
168 for(Size i=0; i<uncDefProb.size(); ++i)
169 invProb.push_back(copula_->inverseCumulativeY(uncDefProb[i], i));
170 /// invProb.push_back(CP::inverseCumulativeY(uncDefProb[i], i));//<-static call
171 return copula_->integratedExpectedValue(
172 [&](const std::vector<Real>& v1) {
173 return expectedConditionalLossInvP(invProb, v1);
174 });
175 }
176
177 template<class CP>
178 inline std::vector<Real> RecursiveLossModel<CP>::lossProbability(const Date& date) const {
179
180 std::vector<Probability> uncDefProb =
181 basket_->remainingProbabilities(date);
182 return copula_->integratedExpectedValueV(
183 [&](const std::vector<Real>& v1) {
184 return conditionalLossProb(uncDefProb, v1);
185 });
186 }
187
188 // -------------------------------------------------------------------
189
190 template<class CP>
192 // basket update:
193 notionals_ = basket_->remainingNotionals();
194 notional_ = basket_->remainingNotional();
195 attachAmount_ = basket_->remainingAttachmentAmount();
196 detachAmount_ = basket_->remainingDetachmentAmount();
197 // model parameters:
198 remainingBsktSize_ = notionals_.size();
199
200 copula_->resetBasket(basket_.currentLink());
201
202 std::vector<Real> lgdsTmp, lgds;
203 for(Size i=0; i<remainingBsktSize_; ++i)
204 lgds.push_back(notionals_[i]*(1.-copula_->recoveries()[i]));
205 lgdsTmp = lgds;
206 ///////////////std::remove(lgds.begin(), lgds.end(), 0.);
207 lgds.erase(std::remove(lgds.begin(), lgds.end(), 0.), lgds.end());
208 lossUnit_ = *(std::min_element(lgds.begin(), lgds.end()))
209 / nBuckets_;
210 for(Size i=0; i<remainingBsktSize_; ++i)
211 wk_.push_back(std::floor(lgdsTmp[i]/lossUnit_ + .5));
212 }
213
214 // make it return a distribution object?
215 template<class CP>
216 std::map<Real, Probability> RecursiveLossModel<CP>::lossDistribution(const Date& d) const
217 {
218 std::map<Real, Probability> distrib;
219 std::vector<Real> values = lossProbability(d);
220 Real sum = 0.;
221 for(Size i=0; i<values.size(); ++i) {
222 distrib.insert(std::make_pair<Real, Probability>(i * lossUnit_,
223 sum + values[i]));
224 sum += values[i];
225 }
226 return distrib;
227 }
228
229 // Integrate then search rather than search and then integrate?
230 // Here I am not using a search because the point might be not attainable
231 // (loss distrib is not continuous)
232 template<class CP>
234 Real percentile) const
235 {
236 std::map<Real, Probability> dist = lossDistribution(d);
237
238 if(dist.begin()->second >=1.) return dist.begin()->first;
239
240 // deterministic case (e.g. date requested is todays date)
241 if(dist.size() == 1) return dist.begin()->first;
242
243 if(percentile == 1.) return dist.rbegin()->second;
244 if(percentile == 0.) return dist.begin()->second;
245 std::map<Real, Probability>::const_iterator itdist = dist.begin();
246 while (itdist->second <= percentile) ++itdist;
247 Real valPlus = itdist->second;
248 Real xPlus = itdist->first;
249 --itdist; //we're never 1st or last, because of tests above
250 Real valMin = itdist->second;
251 Real xMin = itdist->first;
252
253 // return xPlus-(xPlus-xMin)*(valPlus-percentile)/(valPlus-valMin);
254 Real portfLoss = xPlus-(xPlus-xMin)*(valPlus-percentile)
255 /(valPlus-valMin);
256 return //remainingNotional_ *
257 std::min(std::max(portfLoss - attachAmount_, 0.),
258 detachAmount_ - attachAmount_);/////(detach_ - attach_);
259 }
260
261 template<class CP>
263 Real perctl) const
264 {
265 if(d == Settings::instance().evaluationDate()) return 0.;
266 std::map<Real, Probability> distrib = lossDistribution(d);
267
268 std::map<Real, Probability>::iterator itNxt, itDist =
269 distrib.begin();
270 for(; itDist != distrib.end(); ++itDist)
271 if(itDist->second >= perctl) break;
272 itNxt = itDist;
273 --itDist; // what if we are on the first one?!!!
274
275 // One could linearly triangulate the exact point and get extra
276 // precission on the first(broken) period.
277 if(itNxt != distrib.end()) {
278 Real lossNxt = std::min(std::max(itNxt->first - attachAmount_,
279 0.), detachAmount_ - attachAmount_);
280 Real lossHere = std::min(std::max(itDist->first - attachAmount_,
281 0.), detachAmount_ - attachAmount_);
282
283 Real val = lossNxt - (itNxt->second - perctl) *
284 (lossNxt - lossHere) / (itNxt->second - itDist->second);
285 Real suma = (itNxt->second - perctl) * (lossNxt + val) * .5;
286 ++itDist; ++itNxt;
287 do{
288 lossNxt = std::min(std::max(itNxt->first - attachAmount_,
289 0.), detachAmount_ - attachAmount_);
290 lossHere = std::min(std::max(itDist->first - attachAmount_,
291 0.), detachAmount_ - attachAmount_);
292 suma += .5 * (lossHere + lossNxt) * (itNxt->second -
293 itDist->second);
294 ++itDist; ++itNxt;
295 }while(itNxt != distrib.end());
296 return suma / (1.-perctl);
297 }
298 return 0.;// well, we are in error.... fix: FAIL
299 }
300
301 template<class CP>
303 const std::vector<Probability>& pDefDate,
304 //const Date& date,
305 const std::vector<Real>& mktFactor) const
306 {
307 //eq. 10 p.68
308 //attainable losses distribution, recursive algorithm
309 const std::vector<Probability>& uncDefProb = pDefDate;// alias, remove
310
311 std::map<Real, Probability> pIndepDistrib;
312 //////// K=0
313 pIndepDistrib.insert(std::make_pair(0., 1.));
314 for(Size iName=0; iName<remainingBsktSize_; ++iName) {
315 Probability pDef =
316 copula_->conditionalDefaultProbability(uncDefProb[iName], iName,
317 mktFactor);
318 ////// iterate on all possible losses in the distribution:
319 std::map<Real, Probability> pDistTemp;
320 auto distIt = pIndepDistrib.begin();
321 while(distIt != pIndepDistrib.end()) {
322 /// update prob if this name does not default
323 auto matchIt = pDistTemp.find(distIt->first);
324 if (matchIt != pDistTemp.end()) {
325 matchIt->second += distIt->second * (1. - pDef);
326 }else{
327 pDistTemp.insert(std::make_pair(distIt->first,
328 distIt->second * (1.-pDef)));
329 }
330 //// and if it does
331 matchIt = pDistTemp.find(distIt->first + wk_[iName]);
332 if(matchIt != pDistTemp.end()) {
333 matchIt->second += distIt->second * pDef;
334 }else{
335 pDistTemp.insert(std::make_pair(
336 distIt->first+wk_[iName], distIt->second * pDef));
337 }
338 ++distIt;
339 }
340 ///// copy back
341 pIndepDistrib = pDistTemp;
342 }
343 /* Apply tranche limits now .... mind you this could be done outside*/
344 //// to be done....
345 return pIndepDistrib;
346 }
347
348 // twice?! rewrite one in terms of the other, this is a duplicate!
349 template<class CP>
351 const std::vector<Real>& invpDefDate,
352 //const Date& date,
353 const std::vector<Real>& mktFactor) const
354 {
355 // eq. 10 p.68
356 // attainable losses distribution, recursive algorithm
357
358 std::map<Real, Probability> pIndepDistrib;
359 // K=0
360 pIndepDistrib.insert(std::make_pair(0., 1.));
361 for(Size iName=0; iName<remainingBsktSize_; ++iName) {
362 Probability pDef =
363 copula_->conditionalDefaultProbabilityInvP(invpDefDate[iName],
364 iName, mktFactor);
365
366 // iterate on all possible losses in the distribution:
367 std::map<Real, Probability> pDistTemp;
368 auto distIt = pIndepDistrib.begin();
369 while(distIt != pIndepDistrib.end()) {
370 // update prob if this name does not default
371 auto matchIt = pDistTemp.find(distIt->first);
372 if(matchIt != pDistTemp.end()) {
373 matchIt->second += distIt->second * (1.-pDef);
374 }else{
375 pDistTemp.insert(std::make_pair(distIt->first,
376 distIt->second * (1.-pDef)));
377 }
378 // and if it does
379 matchIt = pDistTemp.find(distIt->first + wk_[iName]);
380 if(matchIt != pDistTemp.end()) {
381 matchIt->second += distIt->second * pDef;
382 }else{
383 pDistTemp.insert(std::make_pair(
384 distIt->first+wk_[iName], distIt->second * pDef));
385 }
386 ++distIt;
387 }
388 // copy back
389 pIndepDistrib = pDistTemp;
390 }
391 /* Apply tranche limits now .... mind you this could be done outside*/
392 return pIndepDistrib;
393 }
394
395
396
397
398 /*
399 Bugs here???. The max min on the tranche looks
400 wrong. It is better to have a tranche function since that way we can avoid
401 adding up losses over all the posible losses rather than just over the
402 tranche limits.
403 */
404 //! Portfolio loss conditional to the market factor value
405 template<class CP>
407 const std::vector<Probability>& pDefDate,
408 //const Date& date,
409 const std::vector<Real>& mktFactor) const
410 {
411 std::map<Real, Probability> pIndepDistrib =
412 conditionalLossDistrib(pDefDate, mktFactor);
413
414 // get the expected value subject to the value of the market
415 // factor.
416 Real expLoss = 0.;
417 //---------------------------------------------------------------
418 /* This is the original (easy to read) loop which I have partially
419 unroll below to take profit of the fact that once we go over
420 the tranche top the loss amount is fixed:
421 */
422 auto distIt = pIndepDistrib.begin();
423
424 while(distIt != pIndepDistrib.end()) {
425 Real loss = distIt->first * lossUnit_;
426 // loss = std::max(std::min(loss, detachAmount_)-attachAmount_, 0.);
427 loss = std::min(std::max(loss - attachAmount_, 0.),
428 detachAmount_ - attachAmount_);
429 // MIN MAX BUGS ....??
430 expLoss += loss * distIt->second;
431 ++distIt;
432 }
433 return expLoss ;
434 }
435
436 template<class CP>
437 // again, I am duplicating code.
439 const std::vector<Real>& invPDefDate,
440 //const Date& date,
441 const std::vector<Real>& mktFactor) const
442 {
443 std::map<Real, Probability> pIndepDistrib =
444 conditionalLossDistribInvP(invPDefDate, mktFactor);
445
446 // get the expected value subject to the value of the market
447 // factor.
448 Real expLoss = 0.;
449 //---------------------------------------------------------------
450 /* This is the original (easy to read) loop which I have partially
451 unroll below to take profit of the fact that once we go over
452 the tranche top the loss amount is fixed:
453 */
454 auto distIt = pIndepDistrib.begin();
455
456 while(distIt != pIndepDistrib.end()) {
457 Real loss = distIt->first * lossUnit_;
458 // loss = std::max(std::min(loss, detachAmount_)-attachAmount_, 0.);
459 loss = std::min(std::max(loss - attachAmount_, 0.),
460 detachAmount_ - attachAmount_);
461 // MIN MAX BUGS ....???
462 expLoss += loss * distIt->second;
463 ++distIt;
464 }
465 return expLoss ;
466 }
467
468 template<class CP>
470 const std::vector<Probability>& pDefDate,
471 //const Date& date,
472 const std::vector<Real>& mktFactor) const
473 {
474 std::map<Real, Probability> pIndepDistrib =
475 conditionalLossDistrib(pDefDate, mktFactor);
476
477 std::vector<Real> results;
478 auto distIt = pIndepDistrib.begin();
479 while(distIt != pIndepDistrib.end()) {
480 //Real loss = distIt->first * loss_unit_
481 // ;
482 //loss = std::max(std::min(loss,
483 // results_.xMax)-results_.xMin, 0.);
484 //expLoss += loss * distIt->second;
485
486 results.push_back(distIt->second);
487 ++distIt;
488 }
489 return results;
490 }
491
492}
493
494#endif
Concrete date class.
Definition: date.hpp:125
std::vector< Real > conditionalLossProb(const std::vector< Probability > &pDefDate, const std::vector< Real > &mktFactor) const
std::map< Real, Probability > conditionalLossDistrib(const std::vector< Probability > &pDefDate, const std::vector< Real > &mktFactor) const
Real expectedConditionalLoss(const std::vector< Probability > &pDefDate, const std::vector< Real > &mktFactor) const
Portfolio loss conditional to the market factor value.
Real attachAmount_
name to name factor. In the single factor copula:
Real percentile(const Date &d, Real percentile) const override
Value at Risk given a default loss percentile.
std::map< Real, Probability > conditionalLossDistribInvP(const std::vector< Real > &pDefDate, const std::vector< Real > &mktFactor) const
Real expectedConditionalLossInvP(const std::vector< Real > &pDefDate, const std::vector< Real > &mktFactor) const
Real expectedTrancheLoss(const Date &date) const override
const ext::shared_ptr< ConstantLossLatentmodel< copulaPolicy > > copula_
RecursiveLossModel(const ext::shared_ptr< ConstantLossLatentmodel< copulaPolicy > > &m, Size nbuckets=1)
std::vector< Real > lossProbability(const Date &date) const
void resetModel() override
Concrete models do now any updates/inits they need on basket reset.
Real expectedShortfall(const Date &d, Real perctl) const override
Expected shortfall given a default loss percentile.
std::map< Real, Probability > lossDistribution(const Date &d) const override
Full loss distribution.
static Settings & instance()
access to the unique instance
Definition: singleton.hpp:104
Date d
QL_REAL Real
real number
Definition: types.hpp:50
Real Probability
probability
Definition: types.hpp:82
std::size_t Size
size of a container
Definition: types.hpp:58
Definition: any.hpp:35
RecursiveLossModel< GaussianCopulaPolicy > RecursiveGaussLossModel