`;
}
});
}
function renderStressTestTable(results) {
const table = document.getElementById('stress-test-table');
let html = `
Scenario
Original P/L
Mitigated P/L
Risk Reduced
`;
results.forEach(r => {
const reduction = r.originalImpact - r.mitigatedImpact;
html += `
${r.name}
${formatCurrency(r.originalImpact)}
${formatCurrency(r.mitigatedImpact)}
${formatCurrency(reduction)}
`;
});
html += ``;
table.innerHTML = html;
}
// --- CHARTING ---
function createCharts() {
const allocationCtx = document.getElementById('allocationChart')?.getContext('2d');
if (allocationCtx) {
allocationChart = new Chart(allocationCtx, { type: 'doughnut', data: {}, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right' } } } });
}
const stressTestCtx = document.getElementById('stressTestChart')?.getContext('2d');
if (stressTestCtx) {
stressTestChart = new Chart(stressTestCtx, {
type: 'bar', data: {},
options: { responsive: true, maintainAspectRatio: false, scales: { x: { stacked: false }, y: { stacked: false, ticks: { callback: (v) => formatCurrency(v,0) } } }, plugins: { legend: { position: 'top' } } }
});
}
}
function updateAllocationChart(totalValue) {
if (!allocationChart) return;
allocationChart.data = {
labels: portfolio.map(a => a.class),
datasets: [{
data: portfolio.map(a => a.value),
backgroundColor: ['#1e3a8a', '#374151', '#60a5fa', '#93c5fd', '#d1d5db'],
}]
};
allocationChart.update();
}
function updateStressTestChart(results) {
if (!stressTestChart) return;
stressTestChart.data = {
labels: results.map(r => r.name),
datasets: [
{ label: 'Original Portfolio P/L', data: results.map(r => r.originalImpact), backgroundColor: '#ef4444' },
{ label: 'Mitigated Portfolio P/L', data: results.map(r => r.mitigatedImpact), backgroundColor: '#10b981' }
]
};
stressTestChart.update();
}
// --- UTILS & NAVIGATION ---
const formatCurrency = (val, digits = 2) => val.toLocaleString('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: digits, maximumFractionDigits: digits });
function updateActiveTab() {
tabPanels.forEach((panel, index) => panel.classList.toggle('hidden', index !== currentTabIndex));
tabs.forEach((tab, index) => {
tab.classList.toggle('tab-btn-active', index === currentTabIndex);
tab.classList.toggle('tab-btn-inactive', index !== currentTabIndex);
tab.classList.toggle('border-blue-900', index === currentTabIndex);
tab.classList.toggle('border-transparent', index !== currentTabIndex);
});
updateNavButtons();
}
function updateNavButtons() {
prevBtn.disabled = currentTabIndex === 0;
nextBtn.disabled = currentTabIndex === tabs.length - 1;
prevBtn.classList.toggle('opacity-50', prevBtn.disabled);
nextBtn.classList.toggle('opacity-50', nextBtn.disabled);
}
function navigateNext() { if (currentTabIndex < tabs.length - 1) { currentTabIndex++; updateActiveTab(); } }
function navigatePrev() { if (currentTabIndex > 0) { currentTabIndex--; updateActiveTab(); } }
// --- PDF DOWNLOAD ---
function downloadPDF() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' });
const primaryColor = '#1e3a8a';
const today = new Date().toLocaleDateString('en-US');
// Header
doc.setFillColor('#e0e7ff'); // indigo-100
doc.rect(0, 0, 210, 30, 'F');
doc.setFontSize(20);
doc.setTextColor(primaryColor);
doc.setFont('helvetica', 'bold');
doc.text('Institutional Risk Mitigation Report', 105, 18, { align: 'center' });
doc.setFontSize(10);
doc.setTextColor('#374151');
doc.text(`Generated on ${today}`, 200, 25, { align: 'right' });
// Section 1: Portfolio Summary
doc.setFontSize(16);
doc.setTextColor(primaryColor);
doc.text('Portfolio Summary', 14, 45);
if (allocationChart) {
doc.addImage(allocationChart.toBase64Image('image/jpeg', 0.8), 'JPEG', 110, 50, 85, 85);
}
const totalValue = portfolio.reduce((sum, asset) => sum + asset.value, 0);
const { currentVolatility, mitigatedVolatility } = calculateRiskMetrics(totalValue);
doc.autoTable({
startY: 50,
body: [
['Total Value', formatCurrency(totalValue)],
['Current Weighted Volatility', `${currentVolatility.toFixed(2)}%`],
['Mitigated Weighted Volatility', `${mitigatedVolatility.toFixed(2)}%`],
],
theme: 'plain',
tableWidth: 90,
styles: { fontSize: 11 },
});
// Section 2: Asset Allocation Table
const head = [['Asset Class', 'Value (USD)', 'Allocation', 'Volatility (%)']];
const body = portfolio.map(asset => [
asset.class,
formatCurrency(asset.value),
`${((asset.value / totalValue) * 100).toFixed(2)}%`,
`${asset.volatility}%`
]);
doc.autoTable({
startY: doc.autoTable.previous.finalY + 15,
head: head,
body: body,
theme: 'grid',
headStyles: { fillColor: primaryColor }
});
// Section 3: Stress Test Results
doc.addPage();
doc.setFontSize(16);
doc.setTextColor(primaryColor);
doc.text('Stress Test Analysis', 14, 20);
if (stressTestChart) {
doc.addImage(stressTestChart.toBase64Image('image/jpeg', 0.8), 'JPEG', 14, 30, 182, 90);
}
const stressHead = [['Scenario', 'Original P/L', 'Mitigated P/L', 'Risk Reduced']];
const stressBody = runStressTests(totalValue).map(r => [
r.name,
formatCurrency(r.originalImpact),
formatCurrency(r.mitigatedImpact),
formatConverter(r.originalImpact - r.mitigatedImpact)
]);
doc.autoTable({
startY: 130,
head: stressHead,
body: stressBody,
theme: 'grid',
headStyles: { fillColor: primaryColor }
});
doc.save(`Risk_Mitigation_Report_${today.replace(/\//g, '-')}.pdf`);
}
// --- RUN INITIALIZATION ---
initialize();
});
