20#include <boost/test/unit_test.hpp>
21#include <oret/datapaths.hpp>
25#include <ql/math/comparison.hpp>
26#include <ql/math/integrals/all.hpp>
27#include <ql/math/randomnumbers/mt19937uniformrng.hpp>
28#include <ql/experimental/credit/lossdistribution.hpp>
29#include <ql/math/distributions/normaldistribution.hpp>
30#include <ql/experimental/credit/distribution.hpp>
31#include <boost/math/distributions/binomial.hpp>
34#include <boost/timer/timer.hpp>
40using std::setprecision;
47 std::vector<std::vector<double>>::iterator endPDs,
48 std::vector<std::vector<double>>::iterator beginLGDs,
double runningDensity,
double runningLoss,
49 std::map<double, double>& dist) {
50 if (beginPDs == endPDs) {
51 dist[runningLoss] = dist[runningLoss] + runningDensity;
53 for (
size_t eventId = 0; eventId < beginPDs->size(); eventId++) {
54 double loss = runningLoss + beginLGDs->at(eventId);
55 double density = runningDensity * beginPDs->at(eventId);
61std::map<double, double>
lossDistribution(
const std::vector<double>& pds,
const std::vector<double>& lgds) {
63 QL_REQUIRE(pds.size() == lgds.size(),
"Mismatch number of pds and lgds");
65 vector<vector<double>> statePDs;
66 vector<vector<double>> stateLGDs;
67 for (
const auto& pd : pds) {
68 statePDs.push_back({pd, 1 - pd});
70 for (
const auto& lgd : lgds) {
71 stateLGDs.push_back({lgd, 0});
73 std::map<double, double> dist;
79 const std::vector<vector<double>>& lgds) {
81 QL_REQUIRE(pds.size() == lgds.size(),
"Mismatch number of pds and lgds");
83 vector<vector<double>> statePDs;
84 vector<vector<double>> stateLGDs;
85 for (
const auto& pd : pds) {
86 statePDs.push_back(pd);
87 statePDs.back().push_back(1.0-std::accumulate(pd.begin(), pd.end(), 0.0));
89 for (
const auto& lgd : lgds) {
90 stateLGDs.push_back(lgd);
91 stateLGDs.back().push_back(0);
94 std::map<double, double> dist;
101 : upperBound(hwb.upperBucketBound()), p(hwb.buckets(), 0.0), A(hwb.buckets(), 0.0){
102 lowerBound.push_back(QL_MIN_REAL);
103 lowerBound.insert(lowerBound.end(), upperBound.begin(), upperBound.end()-1);
105 for (
const auto& [l, d] : dist) {
106 size_t idx = hwb.
index(l);
108 A[idx] = (A[idx] * p[idx] + d * l) / (d + p[idx]);
115 : upperBound(hwb.upperBucketBound()), p(hwb.probability().begin(), hwb.probability().end()),
116 A(hwb.averageLoss().begin(), hwb.averageLoss().end()) {
117 lowerBound.push_back(QL_MIN_REAL);
118 lowerBound.insert(lowerBound.end(), upperBound.begin(), upperBound.end()-1);
121 std::vector<double> lowerBound;
122 std::vector<double> upperBound;
126 QuantLib::Distribution
lossDistribution(
size_t nBuckets, Real minLoss, Real maxLoss) {
127 QuantLib::Distribution dist(nBuckets, minLoss, maxLoss);
129 for (
size_t i = 0; i < nBuckets; i++) {
130 dist.addDensity(i, p[i+1] / dist.dx(i));
131 dist.addAverage(i,A[i+1]);
139 os <<
"#\tLB\tUB\tPD\tAvg" << std::endl;
140 for (Size i = 0; i < dist.A.size(); ++i) {
142 os << i <<
"\t" << dist.lowerBound[i] <<
"\t" << dist.upperBound[i] <<
"\t" << dist.p[i] <<
"\t"
143 << dist.A[i] << std::endl;
149 double expectedLoss = 0.0;
150 for (
const auto& [L, pd] : dist) {
151 if (L <= detachmentPoint) {
152 expectedLoss += L * pd;
154 expectedLoss += detachmentPoint * pd;
160double expectedTrancheLoss(QuantLib::Distribution dist,
double attachmentAmount,
double detachmentAmount) {
161 QuantLib::Real expectedLoss = 0;
163 for (QuantLib::Size i = 0; i < dist.size(); i++) {
165 QuantLib::Real x = dist.average(i);
166 if (x < attachmentAmount)
168 if (x > detachmentAmount)
170 expectedLoss += (x - attachmentAmount) * dist.dx(i) * dist.density(i);
172 expectedLoss += (detachmentAmount - attachmentAmount) * (1.0 - dist.cumulativeDensity(detachmentAmount));
178 QuantLib::Real expectedLoss = 0;
179 for (
size_t i = 0; i < prob.size(); ++i) {
180 expectedLoss += std::min(loss[i], detachment) * prob[i];
189BOOST_AUTO_TEST_SUITE(HullWhiteBucketingTestSuite)
193 BOOST_TEST_MESSAGE(
"Testing Hull White Bucketing...");
195 boost::timer::cpu_timer timer;
201 std::vector<Real> buckets;
202 for (Size i = 0; i <= N; ++i) {
203 buckets.push_back(0.5 +
static_cast<Real
>(i));
206 std::vector<Real> pds(L, pd), losses(L, 1.0);
209 hw.
compute(pds.begin(), pds.end(), losses.begin());
214 boost::math::binomial ref(L, pd);
216 for (Size i = 0; i < Size(std::min(Size(p.size()),Size(15))); ++i) {
217 if (i < p.size() - 1) {
218 BOOST_TEST_MESSAGE(
"Bucket " << i <<
" ..." << hw.
upperBucketBound()[i] <<
": p = " << p[i]
219 <<
" A = " << A[i] <<
" ref = " << boost::math::pdf(ref, i));
220 BOOST_CHECK_CLOSE(p[i], boost::math::pdf(ref, i), 1E-10);
221 BOOST_CHECK_CLOSE(A[i],
static_cast<Real
>(i), 1E-10);
223 BOOST_TEST_MESSAGE(
"Bucket " << i <<
" ..." << hw.
upperBucketBound()[i] <<
": p = " << p[i]
224 <<
" A = " << A[i] <<
" ref = " << 1.0 - boost::math::cdf(ref, i - 1));
225 BOOST_CHECK_CLOSE(p[i], 1.0 - boost::math::cdf(ref, i - 1), 1E-6);
229 BOOST_TEST_MESSAGE(
"Elapsed: " << timer.elapsed().wall * 1e-9);
234 BOOST_TEST_MESSAGE(
"Testing Hull White Bucketing in QuantLib...");
236 boost::timer::cpu_timer timer;
243 QuantLib::ext::shared_ptr<QuantLib::LossDist> bucketing = QuantLib::ext::make_shared<QuantLib::LossDistBucketing>(N+1, N+1);
245 std::vector<Real> pds(L, pd), losses(L, 1.0);
247 QuantLib::Distribution dist = (*bucketing)(losses, pds);
249 boost::math::binomial ref(L, pd);
251 for (Size i = 0; i <= Size(std::min(Size(N),Size(15))); ++i) {
252 Real p = dist.density(i) * dist.dx(i);
253 Real A = dist.average(i);
255 BOOST_TEST_MESSAGE(
"Bucket " << i <<
" ..." << x <<
": p = " << p
256 <<
" A = " << A <<
" ref = " << boost::math::pdf(ref, i));
257 BOOST_CHECK_CLOSE(p, boost::math::pdf(ref, i), 1E-10);
258 BOOST_CHECK_CLOSE(A,
static_cast<Real
>(i), 1E-10);
261 BOOST_TEST_MESSAGE(
"Elapsed: " << timer.elapsed().wall * 1e-9);
265 BOOST_TEST_MESSAGE(
"Testing Multi State Hull White Bucketing...");
270 std::vector<Real> buckets;
271 for (Size i = 0; i <= N; ++i) {
272 buckets.push_back(0.5 +
static_cast<Real
>(i));
275 std::vector<Real> pd = {0.01, 0.02};
276 std::vector<Real> l = {1.0, 2.0};
277 std::vector<std::vector<Real>> pds(L, pd), losses(L, l);
286 Array ref(p.size(), 0.0);
287 MersenneTwisterUniformRng mt(42);
288 Size paths = 1000000;
289 for (Size k = 0; k < paths; ++k) {
291 for (Size ll = 0; ll < L; ++ll) {
292 Real r = mt.nextReal();
295 else if (r < pd[0] + pd[1])
298 Size idx = hw.
index(loss);
304 for (Size i = 0; i < p.size(); ++i) {
305 Real diff = p[i] - ref[i];
306 BOOST_TEST_MESSAGE(
"Bucket " << i <<
" ..." << hw.
upperBucketBound()[i] <<
": p = " << p[i] <<
" A = " << A[i]
307 <<
" ref = " << ref[i] <<
" pdiff " << std::scientific << diff);
308 BOOST_CHECK_CLOSE(p[i], ref[i], 1.5);
309 if (i < p.size() - 1)
310 BOOST_CHECK_CLOSE(A[i],
static_cast<Real
>(i), 1E-10);
315 BOOST_TEST_MESSAGE(
"Testing Multi State Hull White Bucketing, edge case with different probabilities and identical losses...");
320 std::vector<Real> buckets;
321 for (Size i = 0; i <= N; ++i) {
322 buckets.push_back(0.5 +
static_cast<Real
>(i));
325 std::vector<Real> pd = {0.005, 0.01, 0.005};
326 std::vector<Real> l = {1.0, 1.0, 1.0};
327 std::vector<std::vector<Real>> pds(L, pd), losses(L, l);
336 std::vector<Real> pds1(L, 0.02);
337 std::vector<Real> losses1(L, 1.0);
340 hw1.
compute(pds1.begin(), pds1.end(), losses1.begin());
346 for (Size i = 0; i < p.size(); ++i) {
347 Real diffp = p[i] - p1[i];
348 Real diffA = A[i] - A1[i];
349 BOOST_TEST_MESSAGE(
"Bucket " << i <<
" ..." << hw.
upperBucketBound()[i] <<
": p = " << p[i] <<
" A = " << A[i]
350 <<
" pdiff " << std::scientific << diffp <<
" Adiff = " << diffA);
351 BOOST_CHECK_CLOSE(p[i], p1[i], 0.01);
352 if (i < p.size() - 1) {
353 BOOST_CHECK_CLOSE(A[i],
static_cast<Real
>(i), 1E-10);
354 BOOST_CHECK_CLOSE(A1[i],
static_cast<Real
>(i), 1E-10);
360 BOOST_TEST_MESSAGE(
"Testing Multi State Hull White Bucketing, expected loss...");
365 std::vector<Real> buckets;
366 for (Size i = 0; i <= N; ++i) {
367 buckets.push_back(0.5 +
static_cast<Real
>(i));
370 std::vector<Real> pd = {0.02, 0.01, 0.02};
371 std::vector<Real> l1 = {2.0, 2.0, 2.0};
372 std::vector<Real> l2 = {1.0, 2.0, 3.0};
373 std::vector<std::vector<Real>> pds(L, pd), losses1(L, l1), losses2(L, l2);
385 Real el1 = 0.0, el2 = 0.0;
386 for (Size i = 0; i < p1.size(); ++i) {
387 el1 += p1[i] * A1[i];
388 el2 += p2[i] * A2[i];
391 for (Size i = 0; i < p1.size(); ++i) {
392 BOOST_TEST_MESSAGE(
"Bucket " << i <<
" ..." << hw1.
upperBucketBound()[i] <<
":"
393 << std::scientific <<
" p = " << p1[i] <<
" " << p2[i] <<
" " << p1[i] - p2[i]);
397 BOOST_TEST_MESSAGE(
"Expected loss: " << std::scientific << el1 <<
" " << el2 <<
" " << el1 - el2);
398 BOOST_CHECK_CLOSE(el1, el2, 1e-12);
402 Real expectedLoss = 0;
403 QL_REQUIRE((p.size() == cumu.size()) && (p.size() == loss.size()),
"array size mismatch");
405 for (i = 0; i < p.size(); ++i) {
411 expectedLoss += (x - attach) * p[i];
413 expectedLoss += (detach - attach) * (1.0 - cumu[i-1]);
417void run_case(std::vector<Real> l, std::string fileName,
double detachmentRatio = 0.2) {
418 QL_REQUIRE(l.size() == 3,
"three losses required");
420 for (Size i = 1; i < l.size(); ++i)
421 QL_REQUIRE(l[i] >= l[i-1],
"increasing losses required");
423 BOOST_TEST_MESSAGE(
"Testing Multi State Hull White Bucketing, expected tranche loss for stylized CDX: " << l[0] <<
" " << l[1] <<
" " << l[2]);
425 Size bucketsFullBasket = 400;
426 Size bucketsTranche = 100;
429 Real attachmentRatio = 0.0;
430 Real a = attachmentRatio * L;
431 Real d = detachmentRatio * L;
433 Real cutoff = 1.0 * L;
434 std::vector<Real> pd = {pd0 * 0.35, pd0 * 0.3, pd0 * 0.35};
439 std::vector<std::vector<Real>> pds(L, pd), losses(L, l);
450 InverseCumulativeNormal ICN;
451 CumulativeNormalDistribution CN;
452 std::vector<std::vector<Real>> c(L, std::vector<Real>(pd.size() + 1, 0.0));
453 std::vector<std::vector<Real>> q(L, std::vector<Real>(pd.size() + 1, pd0));
454 for (Size i = 0; i < c.size(); ++i) {
455 c[i][0] = ICN(q[i][0]);
458 for (Size j = 0; j < pd.size(); ++j) {
460 q[i][j+1] = q[i][0] * (1.0 -
sum);
462 c[i][j+1] = QL_MIN_REAL;
464 c[i][j+1] = ICN(q[i][j+1]);
466 QL_REQUIRE(fabs(q[i].back()) < 1e-10,
"expected zero qij, but found " << q[i].back() <<
" for i=" << i);
472 Real dm = (mMax - mMin) / mSteps;
473 std::vector<std::vector<Real>> cpds = pds;
475 Array p(p0.size(), 0.0);
476 Array A(A0.size(), 0.0);
478 Array pTranche(bucketsTranche + 2, 0.0);
479 Array ATranche(bucketsTranche + 2, 0.0);
481 Array pref(p0.size(), 0.0);
483 double trancheLossMC = 0;
484 for (Size k = 0; k <= mSteps; ++k) {
485 BOOST_TEST_MESSAGE(
"Copula loop " << k <<
"/" << mSteps);
486 Real m = mMin + dm * k;
487 Real mDensity =
exp(-m * m / 2) /
sqrt(2.0 * M_PI);
488 norm += mDensity * dm;
492 for (Size i = 0; i < c.size(); i++) {
493 Real cpd0 = CN((c[i][0] -
sqrt(rho) * m) /
sqrt(1.0 - rho));
495 for (Size j = 1; j < c[i].size(); ++j) {
497 cpds[i][j-1] = CN((c[i][j-1]-
sqrt(rho)*m)/
sqrt(1.0-rho)) - CN((c[i][j]-
sqrt(rho)*m)/
sqrt(1.0-rho));
500 QL_REQUIRE(fabs(
sum - cpd0) < 1e-10,
"probability check failed for factor " << m <<
": " <<
sum <<
" vs " << cpd0);
517 Array pmc(pm.size(), 0.0);
518 MersenneTwisterUniformRng mt(42);
521 for (Size kk = 0; kk < paths; ++kk) {
523 for (Size ll = 0; ll < L; ++ll) {
524 Real r = mt.nextReal();
526 Size n = cpds[ll].size();
527 for (Size mm = 0; mm < cpds[ll].size(); ++mm) {
528 sum += cpds[ll][n - 1 - mm];
530 loss += losses[ll][n - 1 - mm];
535 mLossMC += std::min(loss, d);
536 Size idx = hwm.
index(loss);
540 trancheLossMC += mLossMC * dm * mDensity / paths;
542 for (Size j = 0; j < p.size(); j++) {
543 BOOST_CHECK_MESSAGE(Am[j] >= 0.0,
"averageLoss[" << j <<
"] " << Am[j] <<
" at k=" << k);
544 BOOST_CHECK_MESSAGE(pm[j] >= 0.0 && pm[j] <= 1.0,
"probability[" << j <<
"] " << pm[j] <<
" at k=" << k);
545 p[j] += pm[j] * mDensity * dm;
546 A[j] += Am[j] * mDensity * dm;
548 pref[j] += pmc[j] * mDensity * dm;
550 for (Size j = 0; j < pTranche.size(); ++j) {
551 pTranche[j] += pmTranche[j] * mDensity * dm;
552 ATranche[j] += AmTranche[j] * mDensity * dm;
556 BOOST_CHECK_CLOSE(norm, 1.0, 0.1);
561 Real el0 = 0.0, el = 0.0, sum0 = 0.0,
sum = 0.0;
563 Distribution refDistribution(bucketsFullBasket, 0, cutoff);
564 Distribution hwDistribution(bucketsFullBasket, 0, cutoff);
565 for (Size i = 0; i < bucketsFullBasket; i++) {
566 hwDistribution.addDensity(i, p[i + 1] / hwDistribution.dx(i));
567 hwDistribution.addAverage(i, A[i + 1]);
568 refDistribution.addDensity(i, pref[i + 1] / hwDistribution.dx(i));
569 refDistribution.addAverage(i, A[i + 1]);
572 Distribution hwDistributionTranche(bucketsTranche, a, d);
573 for (Size i = 0; i < bucketsTranche; i++) {
574 hwDistributionTranche.addDensity(i, pTranche[i + 1] / hwDistributionTranche.dx(i));
575 hwDistributionTranche.addAverage(i, ATranche[i + 1]);
579 double calculatedLossTrancheHWTrancheBucketing =
582 BOOST_TEST_MESSAGE(
"Expected tranche loss (MC) " << trancheLossMC);
583 BOOST_TEST_MESSAGE(
"Calculated tranche loss (HW bucketing over full basket) "
584 << calculatedLossTrancheHWFullBucketing);
585 BOOST_TEST_MESSAGE(
"Calculated tranche loss (HW bucketing of the tranche) "
586 << calculatedLossTrancheHWTrancheBucketing);
588 BOOST_CHECK_CLOSE(trancheLossMC, calculatedLossTrancheHWFullBucketing, 0.25);
589 BOOST_CHECK_CLOSE(trancheLossMC, calculatedLossTrancheHWTrancheBucketing, 0.25);
592 for (Size i = 0; i < p.size(); ++i) {
593 el0 += p0[i] * A0[i];
601 file.open(fileName.c_str());
603 Array cumu0(p0.size(), 0.0);
604 Array cumu(p.size(), 0.0);
605 Array cumuref(p.size(), 0.0);
607 for (Size i = 0; i < p.size(); ++i) {
608 cumu0[i] = (i == 0 ? p0[0] : p0[i] + cumu0[i-1]);
609 cumu[i] = (i == 0 ? p[0] : p[i] + cumu[i-1]);
610 cumuref[i] = (i == 0 ? pref[0] : pref[i] + cumuref[i-1]);
612 file << i <<
" " << std::scientific
613 << A0[i] <<
" " << p0[i] <<
" " << cumu0[i] <<
" "
614 << A[i] <<
" " << p[i] <<
" " << cumu[i] <<
" "
615 << A[i] <<
" " << pref[i] <<
" " << cumuref[i] << std::endl;
617 if (file.is_open()) {
618 file <<
"# pd0: " << pd0 << std::endl
619 <<
"# losses: " << l[0] <<
" " << l[1] <<
" " << l[2] << std::endl
620 <<
"# attachment point: " << attachmentRatio << std::endl
621 <<
"# detachment point: " << detachmentRatio << std::endl
622 <<
"# correlation: " << rho << std::endl
623 <<
"# Expected basket loss, marginal: " << el0 << std::endl
624 <<
"# Expected basket loss, correlated: " << el << std::endl
625 <<
"# Expected tranche loss, marginal: " <<
expectedTrancheLoss(a, d, p0, cumu0, A0) << std::endl
626 <<
"# Expected tranche loss, correlated (full): " << calculatedLossTrancheHWFullBucketing << std::endl
627 <<
"# Expected tranche loss, correlated: " << calculatedLossTrancheHWTrancheBucketing << std::endl
628 <<
"# Expected tranche loss, correlated, ref: " << trancheLossMC << std::endl;
632 BOOST_TEST_MESSAGE(
"pd: " << pd0);
633 BOOST_TEST_MESSAGE(
"losses: " << l[0] <<
" " << l[1] <<
" " << l[2]);
634 BOOST_TEST_MESSAGE(
"attachment point: " << attachmentRatio);
635 BOOST_TEST_MESSAGE(
"detachment point: " << detachmentRatio);
636 BOOST_TEST_MESSAGE(
"correlation: " << rho);
637 BOOST_TEST_MESSAGE(
"Expected basket loss, marginal: " << el0);
638 BOOST_TEST_MESSAGE(
"Expected basket loss, correlated: " << el);
639 BOOST_TEST_MESSAGE(
"# Expected tranche loss, correlated (full): " << calculatedLossTrancheHWFullBucketing);
640 BOOST_TEST_MESSAGE(
"# Expected tranche loss, correlated: " << calculatedLossTrancheHWTrancheBucketing);
641 BOOST_TEST_MESSAGE(
"# Expected tranche loss, correlated, ref: " << trancheLossMC);
642 BOOST_CHECK_CLOSE(sum0, 1.0, 1e-4);
643 BOOST_CHECK_CLOSE(
sum, 1.0, 1e-4);
644 BOOST_CHECK_CLOSE(el0, el, 1.0);
649 run_case({0.6, 0.6, 0.6},
"data_1.txt", 0.03);
650 run_case({0.3, 0.6, 0.9},
"data_2.txt", 0.07);
651 run_case({0.3, 0.6, 0.9},
"data_3.txt", 0.15);
652 run_case({0.5, 0.7, 0.9},
"data_5.txt", 0.35);
657 BOOST_TEST_MESSAGE(
"Testing Hull White Bucketing with different PDs...");
661 double lowerlimit = 0;
662 double upperlimit = 5;
664 std::vector<Real> pds{0.1, 0.1, 0.05, 0.1, 0.05};
665 std::vector<Real> losses{1.0, 1.0, 1.0, 1.0, 1.0};
668 hw.
compute(pds.begin(), pds.end(), losses.begin());
670 std::vector<Real> expectedPDs;
673 std::vector<double> ref = {0., 0.6579225000000, 0.2885625000000, 0.0492750000000, 0.0040750000000, 0.0001625000000,
675 std::vector<double> refA = {0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
676 for (Size i = 0; i < hw.
buckets(); ++i) {
677 BOOST_TEST_MESSAGE(
"Bucket " << (i==0 ? QL_MIN_REAL : hw.
upperBucketBound()[i-1]) <<
" ..." << hw.
upperBucketBound()[i] <<
": p = " << p[i] <<
" A = " << A[i]
678 <<
" ref = " << ref[i]);
679 BOOST_CHECK_CLOSE(p[i], ref[i], 0.01);
680 BOOST_CHECK_CLOSE(A[i], refA[i], 0.01);
686 BOOST_TEST_MESSAGE(
"Testing Multistate Hull White Bucketing Inhomogeneous Portfolio");
688 double lowerlimit = 0;
689 std::vector<Real> pds {
690 0.0125, 0.0093, 0.0106, 0.0095, 0.0077, 0.0104, 0.0075, 0.0117, 0.0078, 0.009,
691 0.0092, 0.0088, 0.0107, 0.0085, 0.0089, 0.0115, 0.0092, 0.0093, 0.012, 0.0102
693 std::vector<Real> lgds{
694 0.45, 0.41, 0.35, 0.39, 0.39, 0.35, 0.42, 0.39, 0.45, 0.37,
695 0.4, 0.39, 0.42, 0.37, 0.36, 0.44, 0.44, 0.42, 0.38, 0.42
697 size_t nObligors = pds.size();
698 BOOST_TEST_MESSAGE(
"number of obligors " << nObligors);
699 BOOST_TEST_MESSAGE(
"number of Buckets " << N);
700 std::map<double, double> excactDistribution = lossDistribution(pds, lgds);
701 for (
auto detachmentPoint : {0.03, 0.07, 0.15, 0.35}) {
702 double upperlimit =
static_cast<double>(nObligors) * detachmentPoint;
703 BOOST_TEST_MESSAGE(
"detachment point " << detachmentPoint);
704 BOOST_TEST_MESSAGE(
"upperLimit " << upperlimit);
707 hw.
compute(pds.begin(), pds.end(), lgds.begin());
710 BOOST_TEST_MESSAGE(
"Expected Loss " << expectedLoss <<
" and calculated loss " << calculatedLoss);
711 BOOST_CHECK_CLOSE(expectedLoss, calculatedLoss, 1e-4);
719 BOOST_TEST_MESSAGE(
"Testing Multistate Hull White Bucketing Inhomogeneous Portfolio");
721 double lowerlimit = 0.0;
722 std::vector<std::vector<Real>> pds{
723 {0.0238, 0.0079}, {0.0223, 0.0074}, {0.0293, 0.0098}, {0.0106, 0.0035}, {0.012, 0.004},
724 {0.0175, 0.0058}, {0.0129, 0.0043}, {0.0091, 0.003}, {0.014, 0.0047}, {0.0138, 0.0046},
725 {0.023, 0.0077}, {0.0299, 0.01}, {0.0183, 0.0061}, {0.0291, 0.0097}
727 std::vector<std::vector<Real>> lgds{
728 {0.44, 0.48}, {0.34, 0.37}, {0.46, 0.51}, {0.47, 0.52}, {0.3, 0.33}, {0.42, 0.46}, {0.33, 0.36},
729 {0.3, 0.33}, {0.3, 0.33}, {0.42, 0.46}, {0.38, 0.42}, {0.4, 0.44}, {0.38, 0.42}, {0.44, 0.48}
732 size_t nObligors = pds.size();
733 BOOST_TEST_MESSAGE(
"number of obligors " << nObligors);
734 BOOST_TEST_MESSAGE(
"number of Buckets " << N);
735 std::map<double, double> excactDistribution = lossDistribution(pds, lgds);
736 for (
auto detachmentPoint : {0.03, 0.07, 0.15, 0.35}) {
737 double upperlimit =
static_cast<double>(nObligors) * detachmentPoint;
738 BOOST_TEST_MESSAGE(
"detachment point " << detachmentPoint);
739 BOOST_TEST_MESSAGE(
"upperLimit " << upperlimit);
745 BOOST_TEST_MESSAGE(
"Expected Loss " << expectedLoss <<
" and calculated loss " << calculatedLoss);
746 BOOST_CHECK_CLOSE(expectedLoss, calculatedLoss, 1e-4);
752 double upperlimit = 5;
753 BOOST_TEST_MESSAGE(
"Testing uniform bucket indexing");
755 std::map<double, size_t> testCases1{{-1, 0}, {0., 1}, {0.99, 1}, {1.0, 2}, {1.75, 2}, {2.0, 3}};
757 for (
const auto& [value, expectedIndex] : testCases1) {
758 BOOST_CHECK_EQUAL(expectedIndex, hw.
index(value));
762 BOOST_TEST_MESSAGE(
"Testing non uniform bucket indexing");
763 std::vector<Real> buckets{0., 0.25, 0.3, 0.5, 1};
767 std::map<double, size_t> testCases2{{-0.01, 0}, {0., 1}, {0.125, 1}, {0.25, 2}, {0.275, 2}, {0.3, 3}, {1.1, 5}};
769 for (
const auto& [value, expectedIndex] : testCases2) {
770 BOOST_CHECK_EQUAL(expectedIndex, hw2.
index(value));
774BOOST_AUTO_TEST_SUITE_END()
776BOOST_AUTO_TEST_SUITE_END()
Represents a bucketed probability distibution.
Size index(const Real x) const
const std::vector< Real > & upperBucketBound() const
void computeMultiState(I1 pBegin, I1 pEnd, I2 lossesBegin)
const Array & probability() const
void compute(I1 pdBegin, I1 pdEnd, I2 lossesBegin)
const Array & averageLoss() const
probability bucketing as in Valuation of a CDO and an nth to Default CDS without Monte Carlo Simulati...
RandomVariable sqrt(RandomVariable x)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
CompiledFormula exp(CompiledFormula x)
std::ostream & operator<<(std::ostream &out, EquityReturnType t)
Real sum(const Cash &c, const Cash &d)
void computeDiscreteDistribution(std::vector< std::vector< double > >::iterator beginPDs, std::vector< std::vector< double > >::iterator endPDs, std::vector< std::vector< double > >::iterator beginLGDs, double runningDensity, double runningLoss, std::map< double, double > &dist)
double expectedTrancheLoss(const std::map< double, double > &dist, double detachmentPoint)
std::ostream & operator<<(std::ostream &os, const BucketedDistribution &dist)
std::map< double, double > lossDistribution(const std::vector< double > &pds, const std::vector< double > &lgds)
Real expectedTrancheLoss(Real attach, Real detach, const Array &p, const Array &cumu, const Array &loss)
BOOST_AUTO_TEST_CASE(testHullWhiteBucketing)
void run_case(std::vector< Real > l, std::string fileName, double detachmentRatio=0.2)
Fixture that can be used at top level.