`;
// Asset Impact Chart
const impactCtx = document.getElementById('assetImpactChart').getContext('2d');
if (assetImpactChart) assetImpactChart.destroy();
assetImpactChart = new Chart(impactCtx, {
type: 'bar',
data: {
labels: result.assetImpacts.map(a => a.class),
datasets: [{
label: 'P&L Contribution (%)',
data: result.assetImpacts.map(a => (a.impact * 100).toFixed(2)),
backgroundColor: result.assetImpacts.map(a => a.impact < 0 ? '#fca5a5' : '#86efac'),
}]
},
options: { indexAxis: 'y', responsive: true, plugins: { legend: { display: false } }, scales: { x: { ticks: { callback: (value) => value + '%' } } } }
});
}
// --- EVENT HANDLING ---
function handleConfigChange(e) {
const { index, field, type } = e.target.dataset;
const value = e.target.type === 'number' ? parseFloat(e.target.value) : e.target.value;
appData[type][index][field] = value;
updateDashboard();
}
const portfolioBody = document.getElementById('portfolio-table-body');
portfolioBody.addEventListener('change', e => { e.target.dataset.type = 'portfolio'; handleConfigChange(e); });
portfolioBody.addEventListener('click', e => { if (e.target.classList.contains('remove-row-btn')) { appData.portfolio.splice(e.target.dataset.index, 1); runAllUpdates(); }});
document.getElementById('add-asset-row').addEventListener('click', () => { appData.portfolio.push({ class: 'US Equity', weight: 0, beta: 1, duration: 0 }); runAllUpdates(); });
document.getElementById('scenarios-form').addEventListener('input', e => {
const { index, field } = e.target.dataset;
const value = e.target.type === 'number' ? parseFloat(e.target.value) : e.target.value;
appData.scenarios[index][field] = value;
updateDashboard();
});
scenarioSelect.addEventListener('change', () => updateDetailedView(runStressTest()));
// --- PDF GENERATION ---
document.getElementById('download-pdf-btn').addEventListener('click', () => {
const results = runStressTest();
const doc = new jsPDF();
let cursorY = 20;
doc.setFontSize(22); doc.setFontStyle('bold'); doc.setTextColor('#0f766e');
doc.text('Portfolio Stress Test Report', 105, cursorY, { align: 'center' });
cursorY += 15;
doc.setFontSize(16); doc.setTextColor('#111827'); doc.text('Overall Scenario Impact', 14, cursorY);
cursorY += 8;
doc.addImage(stressTestChart.toBase64Image('image/png', 1.0), 'PNG', 14, cursorY, 180, 90);
cursorY += 100;
doc.autoTable({
startY: cursorY,
head: [['Scenario', 'P&L ($)', 'P&L (%)']],
body: results.map(r => [r.name, `$${(r.totalImpact * BASE_PORTFOLIO_VALUE / 1000000).toFixed(2)}M`, `${(r.totalImpact * 100).toFixed(2)}%`]),
theme: 'grid', headStyles: { fillColor: '#0d9488' }
});
cursorY = doc.autoTable.previous.finalY + 15;
doc.addPage();
cursorY = 20;
doc.setFontSize(16); doc.text('Portfolio Composition', 14, cursorY);
cursorY += 8;
doc.autoTable({
startY: cursorY,
head: [['Asset Class', 'Weight (%)', 'Beta', 'Duration']],
body: appData.portfolio.map(a => [a.class, `${a.weight}%`, a.beta, a.duration]),
theme: 'striped', headStyles: { fillColor: '#64748b' }
});
doc.save('Portfolio-Stress-Test-Report.pdf');
});
// --- INITIALIZATION ---
function runAllUpdates() {
renderPortfolioTable();
renderScenariosForm();
updateDashboard();
}
runAllUpdates();
setActiveTab('dashboard');
});
