Sovereign Wealth Fund Portfolio Simulator (Conceptual Analyzer)
Fund Setup & Economic Assumptions
$
Economic Assumptions
%
E.g., 0.2-0.4 for diversified portfolios. Used for conceptual overall portfolio risk.
Asset Allocation & Capital Market Assumptions
Define asset classes, target allocations (must sum to 100%), and their expected long-term nominal performance. Illustrative defaults are provided.
Total Allocation: 0%
Contribution/Withdrawal Policy & Projection Settings
$%
Enter a positive value for net contributions, negative for net withdrawals.
Long-Term Projections & Analysis
Portfolio summary metrics will appear here.
Target Asset Allocation
Portfolio Value Projection (Nominal & Real)
Projection chart will appear here.
Year-by-Year Projection Details:
Projection table will appear here.
No allocations.
'; return; } const boxSize = Math.min(svg.parentElement.offsetWidth, svg.parentElement.offsetHeight, 260) || 260; svg.setAttribute('viewBox', `0 0 ${boxSize} ${boxSize}`); const radius = boxSize / 2 * 0.85; const cx = boxSize / 2; const cy = boxSize / 2; let cumulativePercent = 0; filteredAC.forEach(ac => { const percent = (ac.targetAllocation || 0) / 100; const startAngle = cumulativePercent * 2 * Math.PI - Math.PI / 2; const endAngle = (cumulativePercent + percent) * 2 * Math.PI - Math.PI / 2; const x1 = cx + radius * Math.cos(startAngle); const y1 = cy + radius * Math.sin(startAngle); const x2 = cx + radius * Math.cos(endAngle); const y2 = cy + radius * Math.sin(endAngle); const largeArcFlag = percent > 0.5 ? 1 : 0; const pathData = `M ${cx},${cy} L ${x1},${y1} A ${radius},${radius} 0 ${largeArcFlag},1 ${x2},${y2} Z`; const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", pathData); path.setAttribute("fill", ac.color || '#ccc'); path.setAttribute("stroke", "#1F2937"); path.setAttribute("stroke-width", "1"); const title = document.createElementNS("http://www.w3.org/2000/svg", "title"); title.textContent = `${ac.name}: ${ac.targetAllocation.toFixed(1)}%`; path.appendChild(title); svg.appendChild(path); cumulativePercent += percent; }); } function swfsim_renderProjectionLineChart(projections, container, initialValue) { /* Similar to empa_renderProjectionChart */ container.innerHTML = ''; const svgWidth = Math.min(800, (swfsim_getEl('swfsimContainer_main').offsetWidth || 400) / 1.5 - 40); // Adjust width for layout const svgHeight = 280; const m = {top: 20, right: 20, bottom: 40, left: 75}; // Increased left margin for Y-axis labels const w = svgWidth - m.left - m.right; const h = svgHeight - m.top - m.bottom; const nominalValues = [initialValue].concat(projections.map(p => p.endNominalValue)); const realValues = [initialValue].concat(projections.map(p => p.endRealValue)); const allYValues = nominalValues.concat(realValues); const yMin = 0; const yMax = Math.max(...allYValues.filter(v=>!isNaN(v) && v !== null)); const xMax = projections.length; const xScale = x => (x / xMax) * w; const yScale = y => h - ((y - yMin) / (yMax - yMin === 0 ? 1 : yMax - yMin)) * h; let nominalPathData = "M" + nominalValues.map((val, i) => `${xScale(i).toFixed(2)},${yScale(val).toFixed(2)}`).join(" L"); let realPathData = "M" + realValues.map((val, i) => `${xScale(i).toFixed(2)},${yScale(val).toFixed(2)}`).join(" L"); container.innerHTML = ` `; } // --- PDF Generation --- function swfsim_downloadPDF() { if (!swfsim_model.analysisResults) { alert("Please analyze portfolio 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('landscape'); 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 = 10; const res = swfsim_model.analysisResults; const inputs = res.inputsSnapshot || swfsim_model; function addLine(text, size, style = 'normal', indent = 0, spacing = 2.5) { const cw = doc.internal.pageSize.getWidth() - (2 * m); if (y > 185 && size > 8) { doc.addPage(); y = m; } else if (y > 190) { 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(`Sovereign Wealth Fund Portfolio Simulation: ${inputs.fundName || 'N/A'}`, 16, 'bold', 0, 5); addLine(`Fund Objective: ${inputs.fundObjective.replace(/_/g,' ')} | Report Date: ${new Date().toLocaleDateString()}`, 9, 'italic', 0, 5); addLine("Key Assumptions:", 11, 'bold', 0, 3); let assumptionsData = [ ["Initial Fund Value:", swfsim_formatCurrency(inputs.initialValue), "Avg. Annual Inflation (CPI):", swfsim_formatPercent(inputs.inflationRate)], ["Projection Period:", `${inputs.projectionPeriod} years`, "Avg. Pairwise Correlation:", inputs.avgCorrelation.toFixed(2)], ["Flow Policy Type:", inputs.flowPolicy.type.replace(/_/g,' '), "Flow Value:", `${inputs.flowPolicy.type === 'fixed_amount' ? swfsim_formatCurrency(inputs.flowPolicy.value) : swfsim_formatPercent(inputs.flowPolicy.value,1)}`], inputs.flowPolicy.type === 'fixed_amount' ? ["Fixed Flow Grows w/ Inflation:", inputs.flowPolicy.growsWithInflation ? 'Yes' : 'No', "", ""] : ["","","",""], ]; if (typeof doc.autoTable === 'function') { doc.autoTable({ startY: y, body: assumptionsData, theme: 'plain', styles: {fontSize: 8, cellPadding: 1}, columnStyles: {0:{fontStyle:'bold'}, 2:{fontStyle:'bold'}}}); y = doc.lastAutoTable.finalY + 5; } else { assumptionsData.forEach(row => addLine(`${row[0]} ${row[1]} | ${row[2]} ${row[3]}`, 8)); y+=5; } addLine("Portfolio Summary Metrics:", 11, 'bold', 0, 3); let summaryMetricsData = [ ["Nominal Expected Return (Portfolio):", swfsim_formatPercent(res.portfolioNominalExpReturn)], ["Real Expected Return (Portfolio):", swfsim_formatPercent(res.portfolioRealExpReturn)], ["Conceptual Nominal Volatility:", swfsim_formatPercent(res.portfolioNominalExpVolatility_conceptual)], ["Projected Final Nominal Value:", swfsim_formatCurrency(res.projections[res.projections.length-1].endNominalValue)], ["Projected Final Real Value:", swfsim_formatCurrency(res.projections[res.projections.length-1].endRealValue)] ]; if (typeof doc.autoTable === 'function') { doc.autoTable({ startY: y, body: summaryMetricsData, theme: 'plain', styles: {fontSize: 8, cellPadding: 1}, columnStyles: {0:{fontStyle:'bold'}}}); y = doc.lastAutoTable.finalY + 5; } else { summaryMetricsData.forEach(row => addLine(`${row[0]} ${row[1]}`, 8)); y+=5; } addLine("Target Asset Allocation & Assumptions:", 11, 'bold', 0, 3); const allocHead = [['Asset Class', 'Allocation (%)', 'Nom. E(R) (%)', 'Volatility (%)']]; const allocBody = res.assetClassesFull.map(ac => [ac.name, (ac.targetAllocation||0).toFixed(1), (ac.nominalExpReturn||0).toFixed(1), (ac.nominalExpVolatility||0).toFixed(1)]); if (typeof doc.autoTable === 'function') { doc.autoTable({startY: y, head: allocHead, body: allocBody, theme: 'striped', headStyles:{fillColor:[75,85,99], fontSize:8}, styles:{fontSize:7.5, cellPadding:1}}); y = doc.lastAutoTable.finalY + 5; } else { addLine("Asset allocation table cannot be generated (plugin issue).", 8, 'italic'); y+=5; } if (y > 180 && res.projections.length > 5) {doc.addPage(); y=m;} addLine(`Year-by-Year Projections:`, 11, 'bold', 0, 3); const projHead = [['Year', 'Start Val (Nom)', 'Net Flow (Nom)', 'Growth (Nom)', 'End Val (Nom)', 'End Val (Real)']]; const projBody = res.projections.map(p => [ p.year, p.startNominalValue.toFixed(0), p.nominalFlow.toFixed(0), p.nominalGrowth.toFixed(0), p.endNominalValue.toFixed(0), p.endRealValue.toFixed(0) ]); if (typeof doc.autoTable === 'function') { doc.autoTable({ startY: y, head: projHead, body: projBody, theme: 'grid', headStyles: { fillColor: [75,85,99], fontSize: 8 }, styles: { fontSize: 7, cellPadding: 1, halign: 'right' }, columnStyles: { 0: { halign: 'center', fontStyle: 'bold'} } }); y = doc.lastAutoTable.finalY + 7; } else { addLine("Projection table cannot be generated.", 8, 'italic'); y+=5;} if (y > 190) {doc.addPage(); y=m;} addLine("Note: This is a conceptual simulator. Expected returns, volatilities, and correlations are long-term assumptions. Actual results will vary significantly. Portfolio volatility is a simplified estimate.", 7, 'italic'); doc.save(`${(inputs.fundName || 'SWF_Portfolio').replace(/\s+/g, '_')}_Simulation.pdf`); }