Sovereign Wealth Fund Risk & Return Forecasting Model

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%

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 = ``; data.forEach(yearData => { tableHtml += ``; }); 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'}
`; baseCaseOutputDiv.innerHTML = tableHtml; } /** * Runs the sensitivity analysis for the selected variable. */ function runSensitivity() { hideMessage(); if (!window.swfAnalysisResults) { showMessage('Please calculate the Base Case projections first.', 'info'); return; } const inputs = window.swfAnalysisResults.inputs; let hasError = false; const sensitivityVariable = sensitivityVariableSelect.value; const sensMin = validateNumberInput(sensMinInput, 'Sensitivity Min Value', true); // Allow negative for rates const sensMax = validateNumberInput(sensMaxInput, 'Sensitivity Max Value', true); const sensStep = validateNumberInput(sensStepInput, 'Sensitivity Step', false); if (sensMin === null || sensMax === null || sensStep === null) { sensitivityOutputDiv.innerHTML = '

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}

`; data.forEach(scenario => { tableHtml += ` `; }); tableHtml += `
${displayVarName} Total Ending AUM
${formatFunc(scenario.value / (variableName.includes('Return') || variableName.includes('Rate') ? 100 : 1))} ${formatCurrency(scenario.endingAUM)}
`; sensitivityOutputDiv.innerHTML = tableHtml; } /** * Resets all input fields to their default values and clears output sections. */ function resetForm() { // Reset General Fund Information inputs initialAumInput.value = "1000000000"; projectionYearsInput.value = "10"; annualContributionInput.value = "50000000"; inflationRateInput.value = "2.5"; riskFreeRateInput.value = "3.0"; varConfidenceLevelInput.value = "95"; // Reset Asset Allocation & Expected Performance inputs allocEquitiesInput.value = "50"; retEquitiesInput.value = "8.0"; volEquitiesInput.value = "15.0"; allocFixedIncomeInput.value = "30"; retFixedIncomeInput.value = "4.0"; volFixedIncomeInput.value = "5.0"; allocRealEstateInput.value = "10"; retRealEstateInput.value = "6.0"; volRealEstateInput.value = "10.0"; allocAlternativesInput.value = "10"; retAlternativesInput.value = "12.0"; volAlternativesInput.value = "20.0"; updateTotalAllocation(); // Update display for total allocation // Reset Sensitivity Variables inputs sensitivityVariableSelect.value = "portfolioReturn"; sensMinInput.value = "6.0"; sensMaxInput.value = "10.0"; sensStepInput.value = "1.0"; // Clear stored results window.swfAnalysisResults = null; // Clear output divs baseCaseOutputDiv.innerHTML = '

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(); });
Scroll to Top