import {Plot} from "@observablehq/plot"
viewof pd = Inputs.range([0.05, 100], {value: 1.00, step: 0.01, label: "PD (%)"})
viewof lgd = Inputs.range([0, 100], {value: 25, step: 0.5, label: "LGD (%)"})
viewof ead = Inputs.range([0, 5_000_000], {value: 1_000_000, step: 10_000, label: "EAD"})
viewof m = Inputs.range([0.25, 20], {value: 2.5, step: 0.25, label: "Maturity (years)"})
viewof avcm = Inputs.select([1, 1.25], {value: 1, label: "AVCM"})
// --- Formulas ---
corr_R = (PD, AVCM) => {
const x = (1 - Math.exp(-50 * PD)) / (1 - Math.exp(-50));
const R = AVCM * (0.12 * x + 0.24 * (1 - x));
return Math.min(Math.max(R, 1e-6), 0.999);
};
b_maturity = PD => (0.11852 - 0.05478 * Math.log(PD)) ** 2;
// Standard normal CDF
function stdnorm_cdf(x) {
return (1 + erf(x / Math.sqrt(2))) / 2;
}
// Error function (Abramowitz & Stegun 7.1.26)
function erf(x) {
const sign = x < 0 ? -1 : 1;
x = Math.abs(x);
const a1 = 0.254829592, a2 = -0.284496736, a3 = 1.421413741;
const a4 = -1.453152027, a5 = 1.061405429, p = 0.3275911;
const t = 1 / (1 + p * x);
const y = 1 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
return sign * y;
}
// Inverse error function (Giles 2010)
function erfinv(x) {
const a = 0.147;
const ln = Math.log((1 - x) * (1 + x));
const sgn = x < 0 ? -1 : 1;
const part1 = 2 / (Math.PI * a) + ln / 2;
const part2 = ln / a;
return sgn * Math.sqrt(Math.sqrt(part1 * part1 - part2) - part1);
}
capital_K = (PD, LGD, M, AVCM) => {
const R = corr_R(PD, AVCM);
const b = b_maturity(PD);
const qnorm = p => Math.sqrt(2) * erfinv(2 * p - 1);
const term = (qnorm(PD) + Math.sqrt(R) * qnorm(0.999)) / Math.sqrt(1 - R);
const K = LGD * stdnorm_cdf(term) - PD * LGD;
const K_adj = K * ((1 + (M - 2.5) * b) / (1 - 1.5 * b));
return Math.max(K_adj, 0);
};
// --- Derived ---
PD = pd / 100;
LGD = lgd / 100;
EAD = ead;
M = m;
AVCM = avcm;
R = corr_R(PD, AVCM);
b = b_maturity(PD);
K = capital_K(PD, LGD, M, AVCM);
RWA = K * 12.5 * EAD;
// --- Sensitivity sequences ---
pd_min = 0.0005;
pd_max = 1.0;
pd_lower = Math.max(pd_min, PD * 0.5);
pd_upper = Math.min(pd_max, PD * 1.5);
pd_seq = Array.from({length: 50}, (_, i) => pd_lower + i * (pd_upper - pd_lower) / 49);
lgd_seq = Array.from({length: 50}, (_, i) => Math.max(0, LGD*0.5) + i*(Math.min(1, LGD*1.5)-Math.max(0, LGD*0.5))/49);
ead_seq = Array.from({length: 50}, (_, i) => EAD*0.5 + i*(EAD*1.5-EAD*0.5)/49);
m_min = 0.25;
m_max = 20.0;
m_lower = Math.max(m_min, M * 0.5);
m_upper = Math.min(m_max, M * 1.5);
m_seq = Array.from({length: 50}, (_, i) => m_lower + i * (m_upper - m_lower) / 49);
data_pd = pd_seq.map(p => ({PD: p*100, RWA: capital_K(p, LGD, M, AVCM) * 12.5 * EAD}));
data_lgd = lgd_seq.map(l => ({LGD: l*100, RWA: capital_K(PD, l, M, AVCM) * 12.5 * EAD}));
data_ead = ead_seq.map(e => ({EAD: e, RWA: capital_K(PD, LGD, M, AVCM) * 12.5 * e}));
data_m = m_seq.map(mv => ({M: mv, RWA: capital_K(PD, LGD, mv, AVCM) * 12.5 * EAD}));
function highlight(x, y, xlab, ylab) {
return [
Plot.ruleX([x], {stroke: "gray", strokeDasharray: "4,2"}),
Plot.ruleY([y], {stroke: "gray", strokeDasharray: "4,2"}),
Plot.dot([{[xlab]: x, [ylab]: y}], {x: xlab, y: ylab, fill: "black", r: 5}),
Plot.text([{[xlab]: x, [ylab]: y, label: `(${x.toFixed(2)}, ${Math.round(y)})`}], {
x: xlab, y: ylab, text: "label", dx: 10, dy: -10, fill: "black", fontSize: 12
})
];
}
plot_pd = Plot.plot({
grid: true, width: 380, height: 240, marginLeft: 55, marginBottom: 40,
marks: [
Plot.line(data_pd, {x: "PD", y: "RWA", stroke: "#A6192E"}),
...highlight(pd, capital_K(PD, LGD, M, AVCM) * 12.5 * EAD, "PD", "RWA")
],
x: {label: "PD (%)"}, y: {label: "RWA"},
caption: "Sensitivity to PD"
})
plot_lgd = Plot.plot({
grid: true, width: 380, height: 240, marginLeft: 55, marginBottom: 40,
marks: [
Plot.line(data_lgd, {x: "LGD", y: "RWA", stroke: "#80225F"}),
...highlight(lgd, capital_K(PD, LGD, M, AVCM) * 12.5 * EAD, "LGD", "RWA")
],
x: {label: "LGD (%)"}, y: {label: "RWA"},
caption: "Sensitivity to LGD"
})
plot_ead = Plot.plot({
grid: true, width: 380, height: 240, marginLeft: 55, marginBottom: 40,
marks: [
Plot.line(data_ead, {x: "EAD", y: "RWA", stroke: "#00AA4F"}),
...highlight(ead, capital_K(PD, LGD, M, AVCM) * 12.5 * EAD, "EAD", "RWA")
],
x: {label: "EAD"}, y: {label: "RWA"},
caption: "Sensitivity to EAD"
})
plot_m = Plot.plot({
grid: true, width: 380, height: 240, marginLeft: 55, marginBottom: 40,
marks: [
Plot.line(data_m, {x: "M", y: "RWA", stroke: "#D6001C"}),
...highlight(m, capital_K(PD, LGD, M, AVCM) * 12.5 * EAD, "M", "RWA")
],
x: {label: "Maturity (years)"}, y: {label: "RWA"},
caption: "Sensitivity to Maturity"
})
html`
<table class="table" style="font-size:0.85em; margin-bottom:8px;">
<thead><tr><th>Quantity</th><th>Value</th><th>Units</th></tr></thead>
<tbody>
<tr><td>Correlation (R)</td><td>${R.toFixed(6)}</td><td></td></tr>
<tr><td>Maturity adjustment (b)</td><td>${b.toFixed(6)}</td><td></td></tr>
<tr><td>Capital requirement (K)</td><td>${K.toFixed(6)}</td><td>per unit EAD</td></tr>
<tr><td><b>RWA</b></td><td><b>${Math.round(RWA).toLocaleString()}</b></td><td>currency</td></tr>
</tbody>
</table>
`