Sovereign Wealth Fund Risk & Return Forecasting Model
Project fund performance and analyze sensitivity to key drivers over time. This is a deterministic model based on expected values and simplified risk metrics.
1. Enter Fund Details & Investment Assumptions
General Fund Information
Enter a positive value for contribution, negative for withdrawal.
Commonly 95% or 99%. Higher means more extreme potential loss.
Asset Allocation & Expected Performance (Base Case)
Allocation percentages must sum to 100%.
Total Allocation: 100.0%
2. Base Case Fund Projections
Calculate the model to see Base Case Projections.
3. Sensitivity Analysis
Sensitivity Variables
Select a key variable and define its range for sensitivity analysis. The model will calculate the Total Ending AUM for each step.
Run sensitivity analysis to see results.
Please run sensitivity analysis to see results.
'; window.swfAnalysisResults = null; return; } // --- Calculations for Base Case --- const baseCaseData = []; let currentAum = initialAum; const portfolioPerformance = calculatePortfolioPerformance(allocations, returns, volatilities); const portfolioExpectedReturn = portfolioPerformance.totalReturn; const portfolioVolatility = portfolioPerformance.totalVolatility; // Add Year 0 (Current) baseCaseData.push({ year: 0, startingAUM: initialAum, netContribution: 0, investmentReturn: 0, endingAUM: initialAum, annualReturnNominal: null, annualReturnReal: null, cumulativeReturnNominal: null, cumulativeReturnReal: null, portfolioVolatility: portfolioVolatility, varValue: portfolioExpectedReturn - (zScore * portfolioVolatility) // Annual VaR }); for (let i = 1; i <= projectionYears; i++) { const startingAUM = currentAum; const netContribution = annualContribution; // Constant annual contribution/withdrawal const investmentReturnAmount = startingAUM * portfolioExpectedReturn; let calculatedEndingAUM = startingAUM + netContribution + investmentReturnAmount; // Changed const to let, and name const annualReturnNominal = portfolioExpectedReturn; const annualReturnReal = (1 + annualReturnNominal) / (1 + inflationRate) - 1; // Handle division by zero for cumulative returns if initialAum is 0 const cumulativeReturnNominal = (initialAum === 0) ? null : (calculatedEndingAUM / initialAum) - 1; const cumulativeReturnReal = (initialAum === 0) ? null : (calculatedEndingAUM / initialAum) / Math.pow(1 + inflationRate, i) - 1; baseCaseData.push({ year: i, startingAUM: startingAUM, netContribution: netContribution, investmentReturn: investmentReturnAmount, endingAUM: calculatedEndingAUM, // Use the new variable annualReturnNominal: annualReturnNominal, annualReturnReal: annualReturnReal, cumulativeReturnNominal: cumulativeReturnNominal, cumulativeReturnReal: cumulativeReturnReal, portfolioVolatility: portfolioVolatility, // Assumed constant for simplicity varValue: portfolioExpectedReturn - (zScore * portfolioVolatility) // Assumed constant for simplicity }); currentAum = calculatedEndingAUM; // Update for next iteration } // Store results for PDF window.swfAnalysisResults = { inputs: { initialAum, projectionYears, annualContribution, inflationRate, riskFreeRate, varConfidenceLevel, allocations, returns, volatilities, portfolioExpectedReturn, portfolioVolatility, zScore }, baseCaseProjections: baseCaseData, sensitivityAnalysis: {} // Will be populated by runSensitivity }; // Display results displayBaseCaseProjections(baseCaseData); openTab('base-case'); showMessage('Base case projections calculated successfully!', 'success'); } /** * Displays the base case projections table. * @param {Array} data The base case projection data. */ function displayBaseCaseProjections(data) { let tableHtml = `| Year | Starting AUM | Net Contribution | Investment Return | Ending AUM | Annual Return (Nominal) | Annual Return (Real) | Cumulative Return (Nominal) | Cumulative Return (Real) | Portfolio Volatility | VaR (${window.swfAnalysisResults.inputs.varConfidenceLevel}%) |
|---|---|---|---|---|---|---|---|---|---|---|
| ${yearData.year} | ${formatCurrency(yearData.startingAUM)} | ${formatCurrency(yearData.netContribution)} | ${formatCurrency(yearData.investmentReturn)} | ${formatCurrency(yearData.endingAUM)} | ${yearData.annualReturnNominal !== null ? formatPercentage(yearData.annualReturnNominal) : 'N/A'} | ${yearData.annualReturnReal !== null ? formatPercentage(yearData.annualReturnReal) : 'N/A'} | ${yearData.cumulativeReturnNominal !== null ? formatPercentage(yearData.cumulativeReturnNominal) : 'N/A'} | ${yearData.cumulativeReturnReal !== null ? formatPercentage(yearData.cumulativeReturnReal) : 'N/A'} | ${yearData.portfolioVolatility !== null ? formatPercentage(yearData.portfolioVolatility) : 'N/A'} | ${yearData.varValue !== null ? formatPercentage(yearData.varValue) : 'N/A'} |
Please fix sensitivity input errors.
'; return; } if (sensStep <= 0) { showMessage('Sensitivity Step must be greater than 0.', 'error'); return; } if (sensMin >= sensMax) { showMessage('Sensitivity Min Value must be less than Max Value.', 'error'); return; } const sensitivityResults = []; const originalAllocations = inputs.allocations; const originalReturns = inputs.returns; const originalVolatilities = inputs.volatilities; for (let val = sensMin; val <= sensMax; val += sensStep) { let currentAnnualContribution = inputs.annualContribution; let currentInflationRate = inputs.inflationRate; let currentPortfolioExpectedReturn = inputs.portfolioExpectedReturn; // This will be affected by asset class returns let tempAllocations = { ...originalAllocations }; let tempReturns = { ...originalReturns }; let tempVolatilities = { ...originalVolatilities }; // For simplicity, volatilities not directly varied in sensitivity. // Adjust the specific variable based on selection if (sensitivityVariable === 'portfolioReturn') { currentPortfolioExpectedReturn = val / 100; // Val is in % } else if (sensitivityVariable === 'annualContribution') { // Map 'revenueGrowth' to annualContribution currentAnnualContribution = val; } else if (sensitivityVariable === 'inflationRate') { currentInflationRate = val / 100; } else if (sensitivityVariable === 'publicEquitiesReturn') { tempReturns.equities = val / 100; const recalculatedPortfolioPerformance = calculatePortfolioPerformance(tempAllocations, tempReturns, tempVolatilities); currentPortfolioExpectedReturn = recalculatedPortfolioPerformance.totalReturn; } else if (sensitivityVariable === 'fixedIncomeReturn') { tempReturns.fixedIncome = val / 100; const recalculatedPortfolioPerformance = calculatePortfolioPerformance(tempAllocations, tempReturns, tempVolatilities); currentPortfolioExpectedReturn = recalculatedPortfolioPerformance.totalReturn; } else if (sensitivityVariable === 'realEstateReturn') { tempReturns.realEstate = val / 100; const recalculatedPortfolioPerformance = calculatePortfolioPerformance(tempAllocations, tempReturns, tempVolatilities); currentPortfolioExpectedReturn = recalculatedPortfolioPerformance.totalReturn; } else if (sensitivityVariable === 'alternativesReturn') { tempReturns.alternatives = val / 100; const recalculatedPortfolioPerformance = calculatePortfolioPerformance(tempAllocations, tempReturns, tempVolatilities); currentPortfolioExpectedReturn = recalculatedPortfolioPerformance.totalReturn; } let currentAum = inputs.initialAum; for (let i = 1; i <= inputs.projectionYears; i++) { const investmentReturnAmount = currentAum * currentPortfolioExpectedReturn; currentAum = currentAum + currentAnnualContribution + investmentReturnAmount; } sensitivityResults.push({ value: val, endingAUM: currentAum }); } window.swfAnalysisResults.sensitivityAnalysis[sensitivityVariable] = sensitivityResults; displaySensitivityResults(sensitivityResults, sensitivityVariable); openTab('sensitivity'); showMessage('Sensitivity analysis completed successfully!', 'success'); } /** * Displays the sensitivity analysis results table. * @param {Array} data The sensitivity analysis data. * @param {string} variableName The name of the variable being varied. */ function displaySensitivityResults(data, variableName) { let displayVarName = ''; let formatFunc = formatPercentage; if (variableName === 'portfolioReturn') { displayVarName = 'Overall Portfolio Expected Return (%)'; } else if (variableName === 'annualContribution') { displayVarName = 'Annual Net Contribution ($)'; formatFunc = formatCurrency; // Override for currency } else if (variableName === 'inflationRate') { displayVarName = 'Annual Inflation Rate (%)'; } else if (variableName === 'publicEquitiesReturn') { displayVarName = 'Public Equities Expected Return (%)'; } else if (variableName === 'fixedIncomeReturn') { displayVarName = 'Fixed Income Expected Return (%)'; } else if (variableName === 'realEstateReturn') { displayVarName = 'Real Estate Expected Return (%)'; } else if (variableName === 'alternativesReturn') { displayVarName = 'Private Equity/Alts Expected Return (%)'; } let tableHtml = `Sensitivity of Ending AUM to ${displayVarName}
| ${displayVarName} | Total Ending AUM |
|---|---|
| ${formatFunc(scenario.value / (variableName.includes('Return') || variableName.includes('Rate') ? 100 : 1))} | ${formatCurrency(scenario.endingAUM)} |
Calculate the model to see Base Case Projections.
'; sensitivityOutputDiv.innerHTML = 'Run sensitivity analysis to see results.
'; // Reset to the first tab openTab('inputs'); hideMessage(); } /** * Downloads the Sovereign Wealth Fund analysis as a PDF. */ function downloadPDF() { if (!window.swfAnalysisResults) { showMessage('Please perform the analysis first.', 'info'); return; } const { jsPDF } = window.jspdf; const pdf = new jsPDF('l', 'mm', 'a4'); // 'l' for landscape, 'mm', 'a4' size (better for wide tables) const BLACK_COLOR = [31, 41, 55]; // Tailwind gray-900 equivalent const BLUE_HEADER_COLOR = [29, 78, 216]; // Tailwind blue-700 equivalent let y = 15; // Y-coordinate for placing content // Title pdf.setFontSize(22); pdf.setFont('helvetica', 'bold'); pdf.setTextColor(BLACK_COLOR[0], BLACK_COLOR[1], BLACK_COLOR[2]); pdf.text('Sovereign Wealth Fund Risk & Return Report', 148, y, { align: 'center' }); // Centered for landscape y += 15; // --- 1. Input Parameters & Base Case Assumptions Section --- pdf.setFontSize(16); pdf.setFont('helvetica', 'bold'); pdf.text('1. Input Parameters & Base Case Assumptions', 15, y); y += 8; const inputs = window.swfAnalysisResults.inputs; const inputData = [ ['Initial AUM', formatCurrency(inputs.initialAum)], ['Projection Years', inputs.projectionYears], ['Annual Net Contribution / (Withdrawal)', formatCurrency(inputs.annualContribution)], ['Annual Inflation Rate', formatPercentage(inputs.inflationRate)], ['Risk-Free Rate', formatPercentage(inputs.riskFreeRate)], ['VaR Confidence Level', `${inputs.varConfidenceLevel}%`], ['Portfolio Expected Return (Derived)', formatPercentage(inputs.portfolioExpectedReturn)], ['Portfolio Volatility (Derived)', formatPercentage(inputs.portfolioVolatility)], ]; pdf.autoTable({ startY: y, head: [['Parameter', 'Value']], body: inputData, theme: 'grid', styles: { fontSize: 8, cellPadding: 2, overflow: 'linebreak', lineColor: 200, lineWidth: 0.1 }, headStyles: { fillColor: BLUE_HEADER_COLOR, textColor: 255, fontStyle: 'bold' }, margin: { left: 15, right: 15 }, columnStyles: { 0: { cellWidth: 100 }, 1: { cellWidth: 'auto' } } }); y = pdf.autoTable.previous.finalY + 10; // Asset Class Details Table pdf.setFontSize(14); pdf.setFont('helvetica', 'bold'); pdf.text('Asset Class Allocation & Performance:', 15, y); y += 7; const assetData = [ ['Asset Class', 'Allocation (%)', 'Expected Return (%)', 'Volatility (%)'], ['Public Equities', (inputs.allocations.equities * 100).toFixed(1), (inputs.returns.equities * 100).toFixed(1), (inputs.volatilities.equities * 100).toFixed(1)], ['Fixed Income', (inputs.allocations.fixedIncome * 100).toFixed(1), (inputs.returns.fixedIncome * 100).toFixed(1), (inputs.volatilities.fixedIncome * 100).toFixed(1)], ['Real Estate', (inputs.allocations.realEstate * 100).toFixed(1), (inputs.returns.realEstate * 100).toFixed(1), (inputs.volatilities.realEstate * 100).toFixed(1)], ['Private Equity/Alts', (inputs.allocations.alternatives * 100).toFixed(1), (inputs.returns.alternatives * 100).toFixed(1), (inputs.volatilities.alternatives * 100).toFixed(1)], ]; pdf.autoTable({ startY: y, head: [assetData[0]], body: assetData.slice(1), theme: 'grid', styles: { fontSize: 8, cellPadding: 2, overflow: 'linebreak', lineColor: 200, lineWidth: 0.1 }, headStyles: { fillColor: BLUE_HEADER_COLOR, textColor: 255, fontStyle: 'bold' }, margin: { left: 15, right: 15 }, columnStyles: { 0: { cellWidth: 50 }, 1: { cellWidth: 40, halign: 'right' }, 2: { cellWidth: 40, halign: 'right' }, 3: { cellWidth: 40, halign: 'right' } } }); y = pdf.autoTable.previous.finalY + 15; // --- 2. Base Case Projections Section --- const baseCaseData = window.swfAnalysisResults.baseCaseProjections; const addBaseCaseTable = (data, startY) => { if (startY + 30 + (data.length * 7) > pdf.internal.pageSize.height - 15) { pdf.addPage('l', 'a4'); startY = 15; } pdf.setFontSize(16); pdf.setFont('helvetica', 'bold'); pdf.text('2. Base Case Fund Projections', 15, startY); startY += 8; const headers = [ 'Year', 'Starting AUM', 'Net Contribution', 'Investment Return', 'Ending AUM', 'Annual Return (Nominal)', 'Annual Return (Real)', 'Cumulative Return (Nominal)', 'Cumulative Return (Real)', 'Portfolio Volatility', `VaR (${inputs.varConfidenceLevel}%)` ]; const tableBody = data.map(yearData => [ yearData.year, { content: formatCurrency(yearData.startingAUM), styles: { halign: 'right' } }, { content: formatCurrency(yearData.netContribution), styles: { halign: 'right' } }, { content: formatCurrency(yearData.investmentReturn), styles: { halign: 'right' } }, { content: formatCurrency(yearData.endingAUM), styles: { halign: 'right', fontStyle: 'bold' } }, { content: yearData.annualReturnNominal !== null ? formatPercentage(yearData.annualReturnNominal) : 'N/A', styles: { halign: 'right' } }, { content: yearData.annualReturnReal !== null ? formatPercentage(yearData.annualReturnReal) : 'N/A', styles: { halign: 'right' } }, { content: yearData.cumulativeReturnNominal !== null ? formatPercentage(yearData.cumulativeReturnNominal) : 'N/A', styles: { halign: 'right' } }, { content: yearData.cumulativeReturnReal !== null ? formatPercentage(yearData.cumulativeReturnReal) : 'N/A', styles: { halign: 'right' } }, { content: yearData.portfolioVolatility !== null ? formatPercentage(yearData.portfolioVolatility) : 'N/A', styles: { halign: 'right' } }, { content: yearData.varValue !== null ? formatPercentage(yearData.varValue) : 'N/A', styles: { halign: 'right' } } ]); pdf.autoTable({ startY: startY, head: [headers], body: tableBody, theme: 'grid', styles: { fontSize: 6, cellPadding: 1.5, overflow: 'linebreak', lineColor: 200, lineWidth: 0.1 }, headStyles: { fillColor: BLUE_HEADER_COLOR, textColor: 255, fontStyle: 'bold' }, margin: { left: 15, right: 15 }, columnStyles: { 0: { cellWidth: 10 }, 1: { cellWidth: 25 }, 2: { cellWidth: 25 }, 3: { cellWidth: 25 }, 4: { cellWidth: 25 }, 5: { cellWidth: 20 }, 6: { cellWidth: 20 }, 7: { cellWidth: 20 }, 8: { cellWidth: 20 }, 9: { cellWidth: 20 }, 10: { cellWidth: 20 } } }); return pdf.autoTable.previous.finalY + 15; }; y = addBaseCaseTable(baseCaseData, y); // --- 3. Sensitivity Analysis Section --- const sensitivityAnalysis = window.swfAnalysisResults.sensitivityAnalysis; for (const variable in sensitivityAnalysis) { // Determine display name and format function let displayVarName = ''; let formatFunc = formatPercentage; // Default for rates/percentages if (variable === 'portfolioReturn') { displayVarName = 'Overall Portfolio Expected Return (%)'; } else if (variable === 'annualContribution') { displayVarName = 'Annual Net Contribution ($)'; formatFunc = formatCurrency; // Override for currency } else if (variable === 'inflationRate') { displayVarName = 'Annual Inflation Rate (%)'; } else if (variable === 'publicEquitiesReturn') { displayVarName = 'Public Equities Expected Return (%)'; } else if (variable === 'fixedIncomeReturn') { displayVarName = 'Fixed Income Expected Return (%)'; } else if (variable === 'realEstateReturn') { displayVarName = 'Real Estate Expected Return (%)'; } else if (variable === 'alternativesReturn') { displayVarName = 'Private Equity/Alts Expected Return (%)'; } if (y + 30 + (sensitivityAnalysis[variable].length * 7) > pdf.internal.pageSize.height - 15) { pdf.addPage('l', 'a4'); y = 15; } pdf.setFontSize(16); pdf.setFont('helvetica', 'bold'); pdf.text(`3. Sensitivity to ${displayVarName}`, 15, y); y += 8; const headers = [displayVarName, 'Total Ending AUM']; const tableBody = sensitivityAnalysis[variable].map(scenario => { const formattedValue = formatFunc(scenario.value / (variable.includes('Return') || variable.includes('Rate') ? 100 : 1)); return [formattedValue, { content: formatCurrency(scenario.endingAUM), styles: { halign: 'right' } }]; }); pdf.autoTable({ startY: y, head: [headers], body: tableBody, theme: 'grid', styles: { fontSize: 8, cellPadding: 2, overflow: 'linebreak', lineColor: 200, lineWidth: 0.1 }, headStyles: { fillColor: BLUE_HEADER_COLOR, textColor: 255, fontStyle: 'bold' }, margin: { left: 15, right: 15 }, columnStyles: { 0: { cellWidth: 100 }, 1: { cellWidth: 'auto' } } }); y = pdf.autoTable.previous.finalY + 10; } pdf.save('SWF_Risk_Return_Forecast_Report.pdf'); } /** * Initializes DOM element references once the document is fully loaded. */ document.addEventListener('DOMContentLoaded', () => { // General Fund Information Inputs initialAumInput = document.getElementById('initialAum'); projectionYearsInput = document.getElementById('projectionYears'); annualContributionInput = document.getElementById('annualContribution'); inflationRateInput = document.getElementById('inflationRate'); riskFreeRateInput = document.getElementById('riskFreeRate'); varConfidenceLevelInput = document.getElementById('varConfidenceLevel'); // Asset Allocation & Expected Performance Inputs allocEquitiesInput = document.getElementById('allocEquities'); retEquitiesInput = document.getElementById('retEquities'); volEquitiesInput = document.getElementById('volEquities'); allocFixedIncomeInput = document.getElementById('allocFixedIncome'); retFixedIncomeInput = document.getElementById('retFixedIncome'); volFixedIncomeInput = document.getElementById('volFixedIncome'); allocRealEstateInput = document.getElementById('allocRealEstate'); retRealEstateInput = document.getElementById('retRealEstate'); volRealEstateInput = document.getElementById('volRealEstate'); allocAlternativesInput = document.getElementById('allocAlternatives'); retAlternativesInput = document.getElementById('retAlternatives'); volAlternativesInput = document.getElementById('volAlternatives'); totalAllocationValueSpan = document.getElementById('totalAllocationValue'); // Add event listeners for allocation inputs to update total sum document.querySelectorAll('#assetAllocationInputs input[id^="alloc"]').forEach(input => { input.addEventListener('input', updateTotalAllocation); }); updateTotalAllocation(); // Initial call to set the sum on load // Sensitivity Variables Inputs sensitivityVariableSelect = document.getElementById('sensitivityVariable'); sensMinInput = document.getElementById('sensMin'); sensMaxInput = document.getElementById('sensMax'); sensStepInput = document.getElementById('sensStep'); // Output Elements baseCaseOutputDiv = document.getElementById('baseCaseOutput'); sensitivityOutputDiv = document.getElementById('sensitivityOutput'); messageBox = document.getElementById('messageBox'); // Initialize navigation button states updateNavigationButtons(); });