Liability-Driven Investment (LDI) Strategy Model (Conceptual Simulator)
Define Future Liabilities
%
Asset Portfolio Construction
$
Asset Classes & Characteristics
Total Allocation: 0%
LDI Analysis & Funding Status
Analysis results will appear here.
Funding Status Chart will appear here.
Interest Rate Scenario Analysis
pp
E.g., 1.0 for a +1% (100bps) rate increase, -0.5 for a -0.5% (50bps) decrease. Assumes a parallel shift.
Scenario Impact
Chart appears here.
'; return; } const res = ldism_model.analysisResults; const fundingStatusClass = res.fundingRatio >= 100 ? 'ldism_funded' : 'ldism_underfunded'; resultsSection.innerHTML = `LDI Funding Status
Present Value of Liabilities (PVL): ${ldism_formatCurrency(res.pvl)}
Present Value of Assets (PVA): ${ldism_formatCurrency(res.pva)}
Funding Ratio (PVA / PVL): ${ldism_formatPercent(res.fundingRatio)}
Surplus / (Deficit): ${ldism_formatCurrency(res.surplusDeficit)}
Duration Matching (Conceptual)
Liability Duration: ${ldism_formatYears(res.liabilityDuration)}
Asset Portfolio Duration: ${ldism_formatYears(res.portfolioDuration)}
Duration Gap (Assets - Liabilities): ${ldism_formatYears(res.portfolioDuration - res.liabilityDuration)}
`;
ldism_renderFundingChart(res.pva, res.pvl, chartContainer);
}
function ldism_displayScenarioResults() {
const resultsSection = ldism_getEl('ldism_scenarioResultsSection');
if (!resultsSection) { console.error("Scenario results display element missing."); return; }
if (!ldism_model.scenarioResults) {
resultsSection.innerHTML = "Run a scenario to see impact.
"; resultsSection.style.display = 'none'; return; } const res = ldism_model.scenarioResults; const baseRes = ldism_model.analysisResults; // For comparison const fundingStatusClass = res.fundingRatio >= 100 ? 'ldism_funded' : 'ldism_underfunded'; resultsSection.innerHTML = `Scenario Impact (Rate Change: ${(res.rateChangeApplied || 0).toFixed(1)} pp)
New Present Value of Liabilities (PVL): ${ldism_formatCurrency(res.pvl)}
New Present Value of Assets (PVA): ${ldism_formatCurrency(res.pva)}
New Funding Ratio: ${ldism_formatPercent(res.fundingRatio)}
New Surplus / (Deficit): ${ldism_formatCurrency(res.surplusDeficit)}
Change in Funding Ratio: ${(res.fundingRatio - baseRes.fundingRatio).toFixed(2)} pp
Change in Surplus/Deficit: ${ldism_formatCurrency(res.surplusDeficit - baseRes.surplusDeficit)}
`;
resultsSection.style.display = 'block';
}
function ldism_renderFundingChart(pva, pvl, container) {
container.innerHTML = ''; // Clear previous
const svgWidth = Math.min(400, (ldism_getEl('ldismContainer_main').offsetWidth || 400) - 80);
const svgHeight = 200;
const m = {top: 20, right: 20, bottom: 30, left: 60};
const w = svgWidth - m.left - m.right;
const h = svgHeight - m.top - m.bottom;
const maxValue = Math.max(pva, pvl, 0);
const barWidth = w / 3;
const yScale = val => h - ((val / (maxValue === 0 ? 1 : maxValue)) * h);
container.innerHTML = `
`;
}
// --- PDF Generation ---
function ldism_downloadPDF() {
if (!ldism_model.analysisResults) { alert("Please run analysis first."); return; }
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') {
alert('Core PDF library (jsPDF) is not loaded.'); console.error('jsPDF library not found.'); return;
}
const { jsPDF: JSPDF } = window.jspdf;
const doc = new JSPDF();
if (typeof doc.autoTable !== 'function') {
alert('PDF Table plugin (jsPDF-AutoTable) not loaded. Tables may be missing.');
console.error('doc.autoTable is not a function.');
}
let y = 15; const m = 15; const cw = doc.internal.pageSize.getWidth() - (2 * m);
const baseRes = ldism_model.analysisResults;
const scenarioRes = ldism_model.scenarioResults; // Might be null
const inputs = ldism_model; // Use main model for input section
function addLine(text, size, style = 'normal', indent = 0, spacing = 2.5) {
if (y > 275) { doc.addPage(); y = m; }
doc.setFontSize(size); doc.setFont(undefined, style);
const lines = doc.splitTextToSize(text, cw - indent); doc.text(lines, m + indent, y);
y += (lines.length * (size * 0.35)) + spacing;
}
addLine(`LDI Strategy Model Report`, 16, 'bold', 0, 5);
addLine(`Report Date: ${new Date().toLocaleDateString()}`, 9, 'italic', 0, 5);
addLine("Liability Profile:", 12, 'bold', 0, 3);
addLine(`- Discount Rate: ${ldism_formatPercent(inputs.discountRate)}`, 9);
if(typeof doc.autoTable === 'function' && inputs.liabilities.length > 0) {
const liaHead = [['Liability', 'Amount ($)', 'Years to Payout']];
const liaBody = inputs.liabilities.map(l => [l.name, l.amount.toFixed(0), l.yearsToPayout.toFixed(1)]);
doc.autoTable({startY: y, head: liaHead, body: liaBody, theme: 'striped', headStyles:{fillColor:[55,65,81]}, styles:{fontSize:8.5}});
y = doc.lastAutoTable.finalY + 5;
} else { inputs.liabilities.forEach(l=> addLine(` - ${l.name}: ${ldism_formatCurrency(l.amount)} in ${l.yearsToPayout} yrs`, 9)); y+=2;}
addLine("Asset Portfolio:", 12, 'bold', 0, 3);
addLine(`- Initial Market Value (PVA): ${ldism_formatCurrency(inputs.portfolioInitialValue)}`, 9);
if(typeof doc.autoTable === 'function' && inputs.assetClasses.length > 0) {
const assetHead = [['Asset Class', 'Allocation (%)', 'Exp. Return (%)', 'Duration (Yrs)']];
const assetBody = inputs.assetClasses.map(ac => [ac.name, ac.allocationPercent.toFixed(1), ac.expectedReturn.toFixed(1), ac.durationYears.toFixed(1)]);
doc.autoTable({startY: y, head: assetHead, body: assetBody, theme: 'striped', headStyles:{fillColor:[55,65,81]}, styles:{fontSize:8.5}});
y = doc.lastAutoTable.finalY + 5;
} else { addLine(" Asset table could not be generated (plugin/data issue).", 8, 'italic'); y+=5;}
addLine("Base LDI Analysis:", 14, 'bold', 0, 4);
let baseData = [
["Present Value of Liabilities (PVL):", ldism_formatCurrency(baseRes.pvl)],
["Present Value of Assets (PVA):", ldism_formatCurrency(baseRes.pva)],
["Funding Ratio (PVA / PVL):", ldism_formatPercent(baseRes.fundingRatio)],
["Surplus / (Deficit):", ldism_formatCurrency(baseRes.surplusDeficit)],
["Liability Duration:", ldism_formatYears(baseRes.liabilityDuration)],
["Asset Portfolio Duration:", ldism_formatYears(baseRes.portfolioDuration)],
["Duration Gap (Assets - Liabilities):", ldism_formatYears(baseRes.portfolioDuration - baseRes.liabilityDuration)],
];
if(typeof doc.autoTable === 'function') {
doc.autoTable({startY: y, body: baseData, theme:'plain', styles:{fontSize:9, cellPadding:1.2}, columnStyles:{0:{fontStyle:'bold'}}});
y = doc.lastAutoTable.finalY + 6;
} else { baseData.forEach(row => addLine(`${row[0]} ${row[1]}`, 9)); y+=5; }
if (scenarioRes) {
addLine(`Interest Rate Scenario Analysis (Rate Change: ${(scenarioRes.rateChangeApplied || 0).toFixed(1)} pp):`, 14, 'bold', 0, 4);
let scenarioData = [
["New PVL:", ldism_formatCurrency(scenarioRes.pvl)],
["New PVA:", ldism_formatCurrency(scenarioRes.pva)],
["New Funding Ratio:", ldism_formatPercent(scenarioRes.fundingRatio)],
["New Surplus / (Deficit):", ldism_formatCurrency(scenarioRes.surplusDeficit)],
["Change in Funding Ratio:", `${(scenarioRes.fundingRatio - baseRes.fundingRatio).toFixed(2)} pp`],
["Change in Surplus/Deficit:", ldism_formatCurrency(scenarioRes.surplusDeficit - baseRes.surplusDeficit)],
];
if(typeof doc.autoTable === 'function') {
doc.autoTable({startY: y, body: scenarioData, theme:'plain', styles:{fontSize:9, cellPadding:1.2}, columnStyles:{0:{fontStyle:'bold'}}});
y = doc.lastAutoTable.finalY + 7;
} else { scenarioData.forEach(row => addLine(`${row[0]} ${row[1]}`, 9)); y+=5; }
}
addLine("Note: This is a conceptual LDI simulator based on user-defined inputs and simplified duration/PV calculations. It does not constitute financial advice.", 7, 'italic');
doc.save(`LDI_Strategy_Analysis.pdf`);
}