Core mechanism
The page compares a copula-based simulation with a recursive loss-distribution calculation on the same pool. Reconciliation separates model disagreement from an implementation error.
TeaVM Java computes the Andersen-Sidenius-Basu tranche loss table under three base correlations, then simulates the equity-tranche protection leg under a one-factor Gaussian copula and uses common-random-number central bumps for rho and default-probability sensitivities.
Java calculates the tranche table with composite Simpson integration of the one-factor conditional binomial distribution (512 intervals). The equity tranche uses the selected seeded path count; rho and PD sensitivities use central bumps with common random numbers. The archived TypeScript route remains available for the original Gauss–Hermite and adjoint comparison.
This exact source runs in TeaVM. Form changes update its Java literals and reset manual edits.
import java.util.Random;
public final class CdoTranchesRiskStudio {
static final double[][] TR = {
{
0, .03
}, {
.03, .07
}, {
.07, .10
}, {
.10, .15
}, {
.15, 1.0
}
};
public static void main(String[] args) {
double pd = 5 / 100.0, lgd = 60 / 100.0, rho = 0.3, rate = 2 / 100.0, maturity = 1;
int names = 50;
long paths = 10000L, seed = 42L;
double[][] d15 = distribution(pd, names, .15, lgd), d30 = distribution(pd, names, .30, lgd),
d45 = distribution(pd, names, .45, lgd);
for (double[] tr : TR) System.out.println("ROW|" + tr[0] + "|" + tr[1] + "|" + expectedFraction(d15,
names, lgd, tr[0], tr[1]) + "|" + expectedFraction(d30, names, lgd, tr[0], tr[1]) + "|" + expectedFraction(d45,
names, lgd, tr[0], tr[1]));
double discount = Math.exp(-rate * maturity), pv = price(names, lgd, pd, rho, paths, seed, discount),
h = 1e-3, hp = Math.min(1e-4, pd * .25);
double rhoDelta = (price(names, lgd, pd, rho + h, paths, seed, discount) - price(names, lgd,
pd, rho - h, paths, seed, discount)) / (2 * h);
double pdDelta = (price(names, lgd, pd + hp, rho, paths, seed, discount) - price(names, lgd,
pd - hp, rho, paths, seed, discount)) / (2 * hp);
System.out.println("RESULT|" + pv + "|" + rhoDelta + "|" + pdDelta);
}
private static double price(int n, double lgd, double pd, double rho, long paths, long seed, double discount) {
Random rng = new Random(seed);
double threshold = inv(pd), sr = Math.sqrt(rho), si = Math.sqrt(1 - rho), sum = 0;
for (long p = 0; p < paths; p++) {
double m = rng.nextGaussian();
int defaults = 0;
for (int j = 0; j < n; j++) if (sr * m + si * rng.nextGaussian() < threshold) defaults++;
double loss = defaults * lgd / n;
sum += Math.min(Math.max(loss, 0), .03) / .03;
}
return sum / paths * discount;
}
private static double[][] distribution(double pd, int n, double rho, double lgd) {
int intervals = 512;
double h = 16.0 / intervals;
double[][] out = new double[1][n + 1];
double threshold = inv(pd), sr = Math.sqrt(rho), si = Math.sqrt(1 - rho);
for (int g = 0; g <= intervals; g++) {
double m = -8 + g * h, p = cdf((threshold - sr * m) / si), w = (g == 0 || g == intervals ? 1 : (g % 2 == 0 ? 2 : 4)) * h / 3.0 * Math.exp(-.5 * m * m) / Math.sqrt(2 * Math.PI);
double[] q = new double[n + 1];
q[0] = 1;
for (int i = 0; i < n; i++) {
for (int k = i + 1; k >= 1; k--) q[k] = q[k] * (1 - p) + q[k - 1] * p;
q[0] *= 1 - p;
}
for (int k = 0; k <= n; k++) out[0][k] += w * q[k];
}
return out;
}
private static double expectedFraction(double[][] d, int n, double lgd, double a, double b) {
double sum = 0, w = b - a;
for (int k = 0; k <= n; k++) {
double loss = k * lgd / n;
sum += d[0][k] * Math.min(Math.max(loss - a, 0), w) / w;
}
return sum;
}
private static double cdf(double x) {
double a = Math.abs(x), t = 1.0 / (1.0 + 0.2316419 * a), d = 0.3989422804014327 * Math.exp(-a * a / 2),
q = d * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
return x >= 0 ? 1 - q : q;
}
private static double inv(double p) {
double[] a = {
-39.6968302866538, 220.946098424521, -275.928510446969, 138.357751867269, -30.6647980661472,
2.50662827745924
}, b = {
-54.4760987982241, 161.585836858041, -155.698979859887, 66.8013118877197, -13.2806815528857
}, c = {
-0.00778489400243029, -0.322396458041136, -2.40075827716184, -2.54973253934373, 4.37466414146497,
2.93816398269878
}, e = {
0.00778469570904146, 0.32246712907004, 2.445134137143, 3.75440866190742
};
double q, r;
if (p < 0.02425) {
q = Math.sqrt(-2 * Math.log(p));
return(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / ((((e[0] * q
+ e[1]) * q + e[2]) * q + e[3]) * q + 1);
}
if (p > 0.97575) return -inv(1 - p);
q = p - .5;
r = q * q;
return(((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q / (((((b[0] * r
+ b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1);
}
}
A tranche payoff depends on aggregate portfolio loss, so credit dependence and numerical method are central modelling choices.
The page compares a copula-based simulation with a recursive loss-distribution calculation on the same pool. Reconciliation separates model disagreement from an implementation error.
Specify names, default probabilities, recovery assumptions, dependence model, attachment and detachment. Validate the loss distribution and tranche outputs across independent methods where practical.
*Keywords: cdo tranche pricing java, gaussian copula java, andersen sidenius basu java, base correlation java, portfolio loss distribution java, correlation delta java*
Feature F9. Portfolio credit — the loss distribution of a pool and the tranches carved out of it — sits in the com.nablatensor.credit package of nablatensor-quant, separate from the counterparty-exposure code in nablatensor-cva.
Base-correlation bootstrap from index tranche quotes, and heterogeneous notionals / recoveries (a bucketed version of the same recursion).
A tranche is exposed only to a band of portfolio loss: losses below attachment are absorbed elsewhere, while losses above detachment no longer change that tranche. This non-linearity is why validating the full portfolio-loss distribution matters more than validating an average default probability alone.
The copula route generates dependent default times from marginal credit inputs and a dependence assumption. The recursive route instead constructs the loss distribution directly. Agreement under the same assumptions is a valuable numerical reconciliation; disagreement can reveal either implementation differences or sensitivity to the dependence specification.
Synthetic pools and stylised correlations are teaching inputs. Production tranche risk also requires market calibration, legal terms and model-risk governance.