19#ifndef quantext_pool_loss_model_hpp
20#define quantext_pool_loss_model_hpp
22#include <ql/experimental/credit/lossdistribution.hpp>
37template <
class CopulaPolicy>
44 QuantLib::Size nBuckets,
45 QuantLib::Real
max = 5.0,
46 QuantLib::Real
min = -5.0,
47 QuantLib::Size nSteps = 50,
48 bool useQuadrature =
false,
49 bool useStochasticRecovery =
false);
51 QuantLib::Real
expectedTrancheLoss(
const QuantLib::Date& d, Real recoveryRate = Null<Real>())
const override;
63 QuantLib::ext::shared_ptr<ExtendedConstantLossLatentModel<CopulaPolicy>>
copula_;
80 mutable std::vector<QuantLib::Real>
lgd_;
82 mutable std::vector<std::vector<QuantLib::Real>>
q_;
84 mutable std::vector<std::vector<QuantLib::Real>>
c_;
86 mutable std::vector<std::vector<QuantLib::Real>>
lgdVV_;
88 mutable std::vector<std::vector<QuantLib::Real>>
cprVV_;
90 QuantLib::Distribution
lossDistrib(
const QuantLib::Date& d, Real recoveryRate = Null<Real>())
const;
92 void updateLGDs(Real recoveryRate = Null<Real>())
const;
94 void updateThresholds(QuantLib::Date d, Real recoveryRate = Null<Real>())
const;
96 std::vector<Real>
updateCPRs(std::vector<QuantLib::Real> factor, Real recoveryRate = Null<Real>())
const;
102 QuantLib::Real
expectedTrancheLoss2(
const QuantLib::Date& d,
const Array& p,
const Array& A)
const;
109template <
class CopulaPolicy>
113 const QuantLib::ext::shared_ptr<QuantLib::LossDist>& bucketing,
114 const std::vector<QuantLib::Real>& marginalDps,
115 const std::vector<QuantLib::Real>& lgds);
121 const QuantLib::ext::shared_ptr<ExtendedConstantLossLatentModel<CopulaPolicy>>
copula_;
127 std::pair<Distribution, QuantLib::Real>
distribution(QuantLib::Real factor);
131 bool operator()(QuantLib::Real x_1, QuantLib::Real x_2)
const {
132 return !close(x_1, x_2) && x_1 < x_2;
138template <
class CopulaPolicy>
142 QuantLib::Size nBuckets,
145 QuantLib::Size nSteps,
147 bool useStochasticRecovery)
148 : homogeneous_(homogeneous),
154 useQuadrature_(useQuadrature),
155 useStochasticRecovery_(useStochasticRecovery),
156 delta_((
max -
min) / nSteps),
163 QL_REQUIRE(copula->numFactors() == 1,
"Multifactor PoolLossModel not yet implemented.");
166template <
class CopulaPolicy>
169 QuantLib::Distribution dist = lossDistrib(d, recoveryRate);
173 QuantLib::Real expectedLoss = 0;
175 for (QuantLib::Size i = 0; i < dist.size(); i++) {
177 QuantLib::Real x = dist.average(i);
178 if (x < attachAmount_)
180 if (x > detachAmount_)
182 expectedLoss += (x - attachAmount_) * dist.dx(i) * dist.density(i);
184 expectedLoss += (detachAmount_ - attachAmount_) * (1.0 - dist.cumulativeDensity(detachAmount_));
189template <
class CopulaPolicy>
194 QuantLib::Real expectedLoss = 0;
195 for (QuantLib::Size i = 0; i < dist.size(); i++) {
197 QuantLib::Real x = dist.average(i);
198 if (x < attachAmount_)
200 if (x > detachAmount_)
202 expectedLoss += (x - attachAmount_) * dist.dx(i) * dist.density(i);
204 expectedLoss += (detachAmount_ - attachAmount_) * (1.0 - dist.cumulativeDensity(detachAmount_));
209template <
class CopulaPolicy>
212 QuantLib::Real expectedLoss = 0;
213 QuantLib::Real cumulative = 0.0;
214 for (QuantLib::Size i = 0; i < p.size(); i++) {
215 QuantLib::Real x = A[i];
216 if (x < attachAmount_)
218 if (x > detachAmount_)
220 expectedLoss += (x - attachAmount_) * p[i];
223 expectedLoss += (detachAmount_ - attachAmount_) * (1.0 - cumulative);
228template <
class CopulaPolicy>
230 QuantLib::Real portfLoss = lossDistrib(d).confidenceLevel(percentile);
231 return std::min(std::max(portfLoss - attachAmount_, 0.), detachAmount_ - attachAmount_);
234template <
class CopulaPolicy>
236 const QuantLib::Date& d, QuantLib::Probability percentile)
const {
238 QuantLib::Distribution dist = lossDistrib(d);
239 dist.tranche(attachAmount_, detachAmount_);
240 return dist.expectedShortfall(percentile);
243template <
class CopulaPolicy>
246 const std::vector<std::vector<QuantLib::Real> >& weights = copula_->factorWeights();
248 if (weights.empty() || weights[0].size() != 1) {
249 return QuantLib::Null<QuantLib::Real>();
252 QuantLib::Real res = weights[0][0];
253 for (QuantLib::Size i = 1; i < weights.size(); ++i) {
254 if (weights[i].size() != 1 || !close(res, weights[i][0])) {
255 return QuantLib::Null<QuantLib::Real>();
262template <
class CopulaPolicy>
267 const std::vector<Real>& recoveries = copula_->recoveries();
268 for (QuantLib::Size i = 0; i < notionals_.size(); ++i) {
269 if (recoveryRate != Null<Real>())
270 lgd_[i] *= (1 - recoveryRate);
272 lgd_[i] *= (1 - recoveries[i]);
277 lgdVV_.resize(notionals_.size(), std::vector<Real>());
278 for (Size i = 0; i < notionals_.size(); ++i) {
279 if (recoveryRate != Null<Real>())
280 lgdVV_[i].push_back(notionals_[i] * (1.0 - recoveryRate));
281 else if (!useStochasticRecovery_)
282 lgdVV_[i].push_back(notionals_[i] * (1.0 - recoveries[i]));
284 const std::vector<Real>& rrGrid = copula_->recoveryRateGrids()[i];
285 for (Size j = 0; j < rrGrid.size(); ++j) {
286 QL_REQUIRE(j == 0 || rrGrid[j] <= rrGrid[j-1],
"recovery rates need to be sorted in decreasing order");
288 lgdVV_[i].push_back(notionals_[i] * (1.0 - rrGrid[j]));
295template <
class CopulaPolicy>
300 std::vector<QuantLib::Real> prob = basket_->remainingProbabilities(d);
302 q_.resize(notionals_.size());
303 c_.resize(notionals_.size());
305 if (useStochasticRecovery_ && recoveryRate == Null<Real>()) {
307 for (Size i = 0; i < notionals_.size(); ++i) {
308 QL_REQUIRE(copula_->recoveryProbabilities().size() == notionals_.size(),
309 "number of rec rate probability vectors does not match number of notionals");
310 std::vector<Real> rrProbs = copula_->recoveryProbabilities()[i];
311 q_[i] = std::vector<Real>(rrProbs.size() + 1, prob[i]);
312 c_[i] = std::vector<Real>(rrProbs.size() + 1, copula_->inverseCumulativeY(q_[i][0], i));
314 for (Size j = 0; j < rrProbs.size(); ++j) {
316 q_[i][j+1] = q_[i][0] * (1.0 -
sum);
317 if (QuantLib::close_enough(q_[i][j+1], 0.0))
318 c_[i][j+1] = QL_MIN_REAL;
320 c_[i][j+1] = copula_->inverseCumulativeY(q_[i][j+1], i);
322 QL_REQUIRE(fabs(q_[i].back()) < tiny,
"expected zero qij, but found " << q_[i].back() <<
" for i=" << i);
326 for (QuantLib::Size i = 0; i < prob.size(); i++) {
327 q_[i] = std::vector<Real>(1, prob[i]);
328 c_[i] = std::vector<Real>(1, copula_->inverseCumulativeY(prob[i], i));
333template <
class CopulaPolicy>
336 std::vector<QuantLib::Real> prob = basket_->remainingProbabilities(d);
337 std::vector<std::vector<QuantLib::Real>> probVV(notionals_.size(), std::vector<Real>());
338 if (!useStochasticRecovery_ || recoveryRate == Null<Real>()) {
339 for (Size i = 0; i < notionals_.size(); ++i)
340 probVV[i].resize(1, prob[i]);
343 QL_REQUIRE(copula_->recoveryProbabilities().size() == notionals_.size(),
344 "number of rec rate probability vectors does not match number of notionals");
345 for (Size i = 0; i < notionals_.size(); ++i) {
347 std::vector<Real> rrProbs = copula_->recoveryProbabilities()[i];
348 probVV[i].resize(rrProbs.size(), 0.0);
349 for (Size j = 0; j < rrProbs.size(); ++j)
350 probVV[i][j] = pd * rrProbs[j];
356template <
class CopulaPolicy>
361 if (useStochasticRecovery_ && recoveryRate == Null<Real>()) {
362 cprVV_.resize(notionals_.size(), std::vector<Real>());
363 for (Size i = 0; i < c_.size(); ++i) {
364 cprVV_[i].resize(c_[i].size() - 1, 0.0);
365 Real pd = copula_->conditionalDefaultProbabilityInvP(c_[i][0], i, factor);
367 for (Size j = 1; j < c_[i].size(); ++j) {
369 cprVV_[i][j-1] = copula_->conditionalDefaultProbabilityInvP(c_[i][j-1], i, factor)
370 - copula_->conditionalDefaultProbabilityInvP(c_[i][j], i, factor);
371 sum += cprVV_[i][j-1];
373 QL_REQUIRE(fabs(
sum - pd) < tiny,
"probability check failed for factor0 " << factor[0]);
377 cprVV_.resize(notionals_.size(), std::vector<Real>(1, 0));
378 for (Size i = 0; i < c_.size(); ++i) {
379 cprVV_[i][0] = copula_->conditionalDefaultProbabilityInvP(c_[i][0], i, factor);
384 std::vector<Real> probs;
385 for (Size i = 0; i < c_.size(); i++)
386 probs.push_back(copula_->conditionalDefaultProbabilityInvP(c_[i][0], i, factor));
391template <
class CopulaPolicy>
394 attach_ = std::min(basket_->remainingAttachmentAmount() / basket_->remainingNotional(), 1.);
395 detach_ = std::min(basket_->remainingDetachmentAmount() / basket_->remainingNotional(), 1.);
396 notional_ = basket_->remainingNotional();
397 notionals_ = basket_->remainingNotionals();
398 attachAmount_ = basket_->remainingAttachmentAmount();
399 detachAmount_ = basket_->remainingDetachmentAmount();
400 copula_->resetBasket(basket_.currentLink());
403template <
class CopulaPolicy>
408 Real maximum = detachAmount_;
412 updateLGDs(recoveryRate);
415 updateThresholds(d, recoveryRate);
422 QuantLib::Distribution dist(nBuckets_, minimum, maximum);
425 QuantLib::ext::shared_ptr<QuantLib::LossDist> bucketing;
427 bucketing = QuantLib::ext::make_shared<QuantLib::LossDistHomogeneous>(nBuckets_, maximum);
429 bucketing = QuantLib::ext::make_shared<QuantLib::LossDistBucketing>(nBuckets_, maximum);
432 bool useQlBucketing =
false;
435 if (useQuadrature_ && !useStochasticRecovery_) {
439 QuantLib::GaussHermiteIntegration Integrator(nSteps_);
441 std::vector<QuantLib::Real> prob = basket_->remainingProbabilities(d);
444 for (QuantLib::Size j = 0; j < nBuckets_; j++) {
445 std::function<QuantLib::Real(QuantLib::Real)> densityFunc = std::bind(
447 std::function<QuantLib::Real(QuantLib::Real)> averageFunc = std::bind(
449 dist.addDensity(j, Integrator(densityFunc));
450 dist.addAverage(j, Integrator(averageFunc));
453 useQlBucketing =
true;
457 std::vector<QuantLib::Real> factor{ min_ + delta_ / 2.0 };
459 for (QuantLib::Size k = 0; k < nSteps_; k++) {
461 std::vector<Real> cpr = updateCPRs(factor, recoveryRate);
464 Distribution conditionalDist;
465 if (useStochasticRecovery_) {
474 else if (homogeneous_) {
478 conditionalDist = (*bucketing)(lgd_, cpr);
479 useQlBucketing =
true;
484 hwb.
compute(cpr.begin(), cpr.end(), lgd_.begin());
488 Real densitydm = delta_ * copula_->density(factor);
490 if (useQlBucketing) {
491 for (Size j = 0; j < nBuckets_; j++) {
492 dist.addDensity(j, conditionalDist.density(j) * densitydm);
493 dist.addAverage(j, conditionalDist.average(j) * densitydm);
502 if (!QuantLib::close_enough(p0, 0.0)) {
505 p[0] += p0 * densitydm;
506 A[0] += A0 * densitydm;
508 for (Size j = 2; j < hwb.
buckets(); j++) {
518 if (!useQlBucketing) {
520 for (Size j = 0; j < nBuckets_; j++) {
521 dist.addDensity(j, p[j] / dist.dx(j));
522 dist.addAverage(j, A[j]);
527 if (
check && !useQlBucketing) {
531 Real etl1 = expectedTrancheLoss1(d, dist);
532 Real etl2 = expectedTrancheLoss2(d, p, A);
534 QL_REQUIRE(fabs((etl1 - etl2)/etl2) < tiny,
"expected tranche loss failed, " << etl1 <<
" vs " << etl2);
540template <
class CopulaPolicy>
543 const QuantLib::ext::shared_ptr<QuantLib::LossDist>& bucketing,
544 const std::vector<QuantLib::Real>& marginalDps,
545 const std::vector<QuantLib::Real>& lgds)
546 : copula_(copula), bucketing_(bucketing),
547 inverseMarginalDps_(marginalDps.size()), lgds_(lgds) {
549 for (Size i = 0; i < marginalDps.size(); i++) {
554template <
class CopulaPolicy>
556 QuantLib::Real factor, QuantLib::Size bucket) {
557 auto p = distribution(factor);
558 return p.first.density(bucket) * p.second;
561template <
class CopulaPolicy>
563 QuantLib::Real factor, QuantLib::Size bucket) {
564 auto p = distribution(factor);
565 return p.first.average(bucket) * p.second;
568template <
class CopulaPolicy>
571 auto it = conditionalDists_.find(factor);
572 if (it != conditionalDists_.end())
577 std::vector<QuantLib::Real> cps(inverseMarginalDps_.size());
578 std::vector<QuantLib::Real> vFactor{ factor };
579 for (Size i = 0; i < cps.size(); i++) {
580 cps[i] = copula_->conditionalDefaultProbabilityInvP(inverseMarginalDps_[i], i, vFactor);
583 it = conditionalDists_.emplace(factor, std::make_pair((*bucketing_)(lgds_, cps), copula_->density(vFactor))).first;
basket of issuers and related notionals
void computeMultiState(I1 pBegin, I1 pEnd, I2 lossesBegin)
const Array & probability() const
void compute(I1 pdBegin, I1 pdEnd, I2 lossesBegin)
const Array & averageLoss() const
std::map< QuantLib::Real, std::pair< Distribution, QuantLib::Real >, keyCmp > conditionalDists_
std::vector< QuantLib::Real > lgds_
std::pair< Distribution, QuantLib::Real > distribution(QuantLib::Real factor)
const QuantLib::ext::shared_ptr< ExtendedConstantLossLatentModel< CopulaPolicy > > copula_
QuantLib::ext::shared_ptr< QuantLib::LossDist > bucketing_
LossModelConditionalDist(const QuantLib::ext::shared_ptr< ExtendedConstantLossLatentModel< CopulaPolicy > > &copula, const QuantLib::ext::shared_ptr< QuantLib::LossDist > &bucketing, const std::vector< QuantLib::Real > &marginalDps, const std::vector< QuantLib::Real > &lgds)
std::vector< QuantLib::Real > inverseMarginalDps_
QuantLib::Real conditionalDensity(QuantLib::Real factor, QuantLib::Size bucket)
QuantLib::Real conditionalAverage(QuantLib::Real factor, QuantLib::Size bucket)
std::vector< std::vector< QuantLib::Real > > cprVV_
std::vector< std::vector< Real > > marginalProbabilitiesVV(Date d, Real recoveryRate=Null< Real >()) const
std::vector< Real > updateCPRs(std::vector< QuantLib::Real > factor, Real recoveryRate=Null< Real >()) const
QuantLib::Real correlation() const override
QuantLib::ext::shared_ptr< ExtendedConstantLossLatentModel< CopulaPolicy > > copula_
std::vector< QuantLib::Real > notionals_
QuantLib::Distribution lossDistrib(const QuantLib::Date &d, Real recoveryRate=Null< Real >()) const
QuantLib::Real expectedTrancheLoss2(const QuantLib::Date &d, const Array &p, const Array &A) const
void updateThresholds(QuantLib::Date d, Real recoveryRate=Null< Real >()) const
PoolLossModel(bool homogeneous, const QuantLib::ext::shared_ptr< ExtendedConstantLossLatentModel< CopulaPolicy > > &copula, QuantLib::Size nBuckets, QuantLib::Real max=5.0, QuantLib::Real min=-5.0, QuantLib::Size nSteps=50, bool useQuadrature=false, bool useStochasticRecovery=false)
QuantLib::Real expectedShortfall(const QuantLib::Date &d, QuantLib::Probability percentile) const override
QuantLib::Real expectedTrancheLoss(const QuantLib::Date &d, Real recoveryRate=Null< Real >()) const override
QuantLib::Real detachAmount_
QuantLib::Real expectedTrancheLoss1(const QuantLib::Date &d, Distribution &dist) const
QuantLib::Real percentile(const QuantLib::Date &d, QuantLib::Real percentile) const override
std::vector< std::vector< QuantLib::Real > > q_
void resetModel() override
Concrete models do now any updates/inits they need on basket reset.
std::vector< std::vector< QuantLib::Real > > lgdVV_
std::vector< QuantLib::Real > lgd_
bool useStochasticRecovery_
void updateLGDs(Real recoveryRate=Null< Real >()) const
std::vector< std::vector< QuantLib::Real > > c_
QuantLib::Real attachAmount_
probability bucketing as in Valuation of a CDO and an nth to Default CDS without Monte Carlo Simulati...
PoolLossModel< TCopulaPolicy > StudentPoolLossModel
PoolLossModel< GaussianCopulaPolicy > GaussPoolLossModel
CompiledFormula min(CompiledFormula x, const CompiledFormula &y)
CompiledFormula max(CompiledFormula x, const CompiledFormula &y)
Real sum(const Cash &c, const Cash &d)
bool operator()(QuantLib::Real x_1, QuantLib::Real x_2) const