417 {
418 QL_REQUIRE(l.size() == 3, "three losses required");
419
420 for (Size i = 1; i < l.size(); ++i)
421 QL_REQUIRE(l[i] >= l[i-1], "increasing losses required");
422
423 BOOST_TEST_MESSAGE("Testing Multi State Hull White Bucketing, expected tranche loss for stylized CDX: " << l[0] << " " << l[1] << " " << l[2]);
424
425 Size bucketsFullBasket = 400;
426 Size bucketsTranche = 100;
427 Size L = 100;
428 Real rho = 0.75;
429 Real attachmentRatio = 0.0;
430 Real a = attachmentRatio * L;
431 Real d = detachmentRatio * L;
432 Real pd0 = 0.04;
433 Real cutoff = 1.0 * L;
434 std::vector<Real> pd = {pd0 * 0.35, pd0 * 0.3, pd0 * 0.35};
435
436
437
438
439 std::vector<std::vector<Real>> pds(L, pd), losses(L, l);
440
441
442
444 hw0.computeMultiState(pds.begin(), pds.end(), losses.begin());
445 Array p0 = hw0.probability();
446 Array A0 = hw0.averageLoss();
447
448
449
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]);
457
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;
463 else
464 c[i][j+1] = ICN(q[i][j+1]);
465 }
466 QL_REQUIRE(fabs(q[i].back()) < 1e-10, "expected zero qij, but found " << q[i].back() << " for i=" << i);
467 }
468
469 Size mSteps = 63;
470 Real mMin = -5.0;
471 Real mMax = 5.0;
472 Real dm = (mMax - mMin) / mSteps;
473 std::vector<std::vector<Real>> cpds = pds;
474
475 Array p(p0.size(), 0.0);
476 Array A(A0.size(), 0.0);
477
478 Array pTranche(bucketsTranche + 2, 0.0);
479 Array ATranche(bucketsTranche + 2, 0.0);
480
481 Array pref(p0.size(), 0.0);
482 Real norm = 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;
489
490
491
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) {
496
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));
499 }
500 QL_REQUIRE(fabs(
sum - cpd0) < 1e-10,
"probability check failed for factor " << m <<
": " <<
sum <<
" vs " << cpd0);
501 }
502
503
504
506 hwm.computeMultiState(cpds.begin(), cpds.end(), losses.begin());
507 Array pm = hwm.probability();
508 Array Am = hwm.averageLoss();
509
511 hwmTranche.computeMultiState(cpds.begin(), cpds.end(), losses.begin());
512 Array pmTranche = hwmTranche.probability();
513 Array AmTranche = hwmTranche.averageLoss();
514
515
516
517 Array pmc(pm.size(), 0.0);
518 MersenneTwisterUniformRng mt(42);
519 Size paths = 50000;
520 double mLossMC = 0;
521 for (Size kk = 0; kk < paths; ++kk) {
522 Real loss = 0.0;
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];
531 break;
532 }
533 }
534 }
535 mLossMC += std::min(loss, d);
536 Size idx = hwm.index(loss);
537 pmc[idx] += 1.0;
538 }
539 pmc /= paths;
540 trancheLossMC += mLossMC * dm * mDensity / paths;
541
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;
547
548 pref[j] += pmc[j] * mDensity * dm;
549 }
550 for (Size j = 0; j < pTranche.size(); ++j) {
551 pTranche[j] += pmTranche[j] * mDensity * dm;
552 ATranche[j] += AmTranche[j] * mDensity * dm;
553 }
554
555 }
556 BOOST_CHECK_CLOSE(norm, 1.0, 0.1);
557
558
559
560
561 Real el0 = 0.0, el = 0.0, sum0 = 0.0,
sum = 0.0;
562
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]);
570 }
571
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]);
576 }
577
579 double calculatedLossTrancheHWTrancheBucketing =
581
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);
587
588 BOOST_CHECK_CLOSE(trancheLossMC, calculatedLossTrancheHWFullBucketing, 0.25);
589 BOOST_CHECK_CLOSE(trancheLossMC, calculatedLossTrancheHWTrancheBucketing, 0.25);
590
591
592 for (Size i = 0; i < p.size(); ++i) {
593 el0 += p0[i] * A0[i];
594 el += p[i] * A[i];
595 sum0 += p0[i];
597 }
598
599 std::ofstream file;
600 if (fileName != "")
601 file.open(fileName.c_str());
602
603 Array cumu0(p0.size(), 0.0);
604 Array cumu(p.size(), 0.0);
605 Array cumuref(p.size(), 0.0);
606
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]);
611 if (file.is_open())
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;
616 }
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;
629 file.close();
630 }
631
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);
645}
RandomVariable sqrt(RandomVariable x)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
CompiledFormula exp(CompiledFormula x)
Real sum(const Cash &c, const Cash &d)
double expectedTrancheLoss(const std::map< double, double > &dist, double detachmentPoint)
Real expectedTrancheLoss(Real attach, Real detach, const Array &p, const Array &cumu, const Array &loss)