Quantitative Macro Hedge Fund Portfolio Modeler
Macro Factor/Strategy Information
Define your macro factors or strategies and input their historical monthly returns (%).
Portfolio Allocation
Allocate weights (in percentage) to each factor/strategy to form your overall portfolio. Weights must sum to 100%.
Performance Results & Optimization
Review the performance metrics for individual factors/strategies and the aggregated portfolio. Run optimization to find the minimum variance portfolio.
Individual Factor/Strategy Performance
Aggregated Portfolio Performance
Cumulative Return: 0.00%
Annualized Return: 0.00%
Annualized Volatility (Std Dev): 0.00%
Sharpe Ratio: 0.00
Maximum Drawdown: 0.00%
Optimized Portfolio (Minimum Variance)
This allocation aims to minimize portfolio risk given your input data.
Optimal Weights:
Expected Annualized Return: 0.00%
Annualized Volatility (Risk): 0.00%
Sharpe Ratio: 0.00
Annualized Volatility: ${(annualizedStdDev * 100).toFixed(2)}%
Sharpe Ratio: ${sharpeRatio.toFixed(2)}
Maximum Drawdown: ${(maxDrawdown * 100).toFixed(2)}%
`; factorPerformanceCardsDiv.appendChild(card); }); individualPerformanceResultsDiv.classList.remove('hidden'); } function calculateAggregatedPortfolioPerformance(weights) { if (factorData.length === 0 || weights.length === 0) { aggregatedPortfolioResultsDiv.classList.add('hidden'); return { cumulativeReturn: 0, annualizedReturn: 0, annualizedStdDev: 0, sharpeRatio: 0, maxDrawdown: 0 }; } // Calculate portfolio monthly returns const portfolioMonthlyReturnsDecimal = []; for (let monthIdx = 0; monthIdx < numHistoricalMonths; monthIdx++) { let weightedMonthlyReturn = 0; for (let i = 0; i < factorData.length; i++) { const factor = factorData[i]; if (factor.returns[monthIdx] !== undefined) { weightedMonthlyReturn += (factor.returns[monthIdx] / 100) * weights[i]; } } portfolioMonthlyReturnsDecimal.push(weightedMonthlyReturn); } // Cumulative Return const portfolioCumulativeReturn = portfolioMonthlyReturnsDecimal.reduce((acc, r) => acc * (1 + r), 1) - 1; // Annualized Return const portfolioAnnualizedReturn = Math.pow(1 + portfolioCumulativeReturn, 12 / numHistoricalMonths) - 1; // Volatility (Standard Deviation) const portfolioMonthlyStdDev = calculateStandardDeviation(portfolioMonthlyReturnsDecimal); const portfolioAnnualizedStdDev = portfolioMonthlyStdDev * Math.sqrt(12); // Sharpe Ratio const annualizedRiskFreeRate = Math.pow(1 + riskFreeRate, 12 / numHistoricalMonths) - 1; const portfolioSharpeRatio = portfolioAnnualizedStdDev !== 0 ? (portfolioAnnualizedReturn - annualizedRiskFreeRate) / portfolioAnnualizedStdDev : 0; // Maximum Drawdown let portfolioMaxDrawdown = 0; let peakValue = 1; let currentValue = 1; portfolioMonthlyReturnsDecimal.forEach(r => { currentValue *= (1 + r); if (currentValue > peakValue) { peakValue = currentValue; } const drawdown = (peakValue - currentValue) / peakValue; if (drawdown > portfolioMaxDrawdown) { portfolioMaxDrawdown = drawdown; } }); portfolioCumulativeReturnSpan.textContent = `${(portfolioCumulativeReturn * 100).toFixed(2)}%`; portfolioAnnualizedReturnSpan.textContent = `${(portfolioAnnualizedReturn * 100).toFixed(2)}%`; portfolioAnnualizedStdDevSpan.textContent = `${(portfolioAnnualizedStdDev * 100).toFixed(2)}%`; portfolioSharpeRatioSpan.textContent = `${portfolioSharpeRatio.toFixed(2)}`; portfolioMaxDrawdownSpan.textContent = `${(portfolioMaxDrawdown * 100).toFixed(2)}%`; aggregatedPortfolioResultsDiv.classList.remove('hidden'); return { cumulativeReturn: portfolioCumulativeReturn, annualizedReturn: portfolioAnnualizedReturn, annualizedStdDev: portfolioAnnualizedStdDev, sharpeRatio: portfolioSharpeRatio, maxDrawdown: portfolioMaxDrawdown }; } // --- Optimization (Minimum Variance Portfolio) Function --- runOptimizationButton.addEventListener('click', () => { if (!validateFactorInputs()) return; updateFactorDataFromInputs(); // Capture latest data const numFactors = factorData.length; if (numFactors === 0) { alertUser('Please add at least one factor/strategy to run optimization.'); return; } if (numHistoricalMonths < 2) { alertUser('At least 2 months of historical returns are required for optimization calculations.'); return; } const numSimulations = 50000; // Number of random portfolios to generate let minRisk = Infinity; let optimalWeights = Array(numFactors).fill(0); let optimalReturn = 0; let optimalSharpe = 0; const factorReturnsDecimal = factorData.map(f => f.returns.map(r => r / 100)); // Helper to calculate portfolio standard deviation from monthly returns and weights function calculatePortfolioStdDevFromReturns(weights, factorReturns) { const portfolioMonthlyReturns = []; for (let monthIdx = 0; monthIdx < numHistoricalMonths; monthIdx++) { let weightedMonthlyReturn = 0; for (let i = 0; i < numFactors; i++) { weightedMonthlyReturn += factorReturns[i][monthIdx] * weights[i]; } portfolioMonthlyReturns.push(weightedMonthlyReturn); } const monthlyStdDev = calculateStandardDeviation(portfolioMonthlyReturns); return monthlyStdDev * Math.sqrt(12); // Annualized } // Monte Carlo simulation for (let i = 0; i < numSimulations; i++) { let randomWeights = []; let sum = 0; for (let j = 0; j < numFactors; j++) { const w = Math.random(); randomWeights.push(w); sum += w; } // Normalize weights to sum to 1 (long-only portfolio) const normalizedWeights = randomWeights.map(w => w / sum); const currentAnnualizedRisk = calculatePortfolioStdDevFromReturns(normalizedWeights, factorReturnsDecimal); // Calculate annualized return for this specific portfolio let currentCumulativeReturn = 1; for (let monthIdx = 0; monthIdx < numHistoricalMonths; monthIdx++) { let weightedMonthlyReturn = 0; for (let j = 0; j < numFactors; j++) { weightedMonthlyReturn += factorReturnsDecimal[j][monthIdx] * normalizedWeights[j]; } currentCumulativeReturn *= (1 + weightedMonthlyReturn); } currentCumulativeReturn -= 1; const currentAnnualizedReturn = Math.pow(1 + currentCumulativeReturn, 12 / numHistoricalMonths) - 1; const annualizedRiskFreeRate = Math.pow(1 + riskFreeRate, 12 / numHistoricalMonths) - 1; const currentSharpeRatio = currentAnnualizedRisk !== 0 ? (currentAnnualizedReturn - annualizedRiskFreeRate) / currentAnnualizedRisk : 0; if (currentAnnualizedRisk < minRisk) { minRisk = currentAnnualizedRisk; optimalWeights = normalizedWeights; optimalReturn = currentAnnualizedReturn; optimalSharpe = currentSharpeRatio; } } // Display Optimization results optimalWeightsDisplayDiv.innerHTML = ''; optimalWeights.forEach((weight, index) => { const factorName = factorData[index] ? factorData[index].name : `Factor/Strategy ${index + 1}`; optimalWeightsDisplayDiv.innerHTML += `${factorName}: ${(weight * 100).toFixed(2)}%
`; }); optimizedPortfolioReturnSpan.textContent = `${(optimalReturn * 100).toFixed(2)}%`; optimizedPortfolioRiskSpan.textContent = `${(minRisk * 100).toFixed(2)}%`; optimizedPortfolioSharpeSpan.textContent = `${optimalSharpe.toFixed(2)}`; optimizationResultsDiv.classList.remove('hidden'); optimizationCalculated = true; showTab(currentTab); // Re-evaluate PDF button visibility }); // --- Input Validation --- function validateFactorInputs() { let isValid = true; const months = parseInt(numMonthsInput.value); if (isNaN(months) || months < 2) { alertUser('Number of historical months must be an integer greater than or equal to 2.'); numMonthsInput.focus(); return false; } const rfRate = parseFloat(riskFreeRateInput.value); if (isNaN(rfRate)) { alertUser('Risk-free rate must be a number.'); riskFreeRateInput.focus(); return false; } if (factorData.length === 0) { alertUser('Please add at least one factor/strategy.'); return false; } for (const factor of factorData) { const nameInput = document.getElementById(`factor-name-${factor.id}`); if (!nameInput || !nameInput.value.trim()) { alertUser('Factor/Strategy Name cannot be empty.'); if (nameInput) nameInput.focus(); isValid = false; break; } for (let i = 0; i < numHistoricalMonths; i++) { const returnInput = document.getElementById(`factor-${factor.id}-return-${i}`); if (!returnInput || isNaN(parseFloat(returnInput.value)) || returnInput.value.trim() === '') { alertUser(`Monthly Return for ${factor.name}, Month ${i + 1} must be a number.`); if (returnInput) returnInput.focus(); isValid = false; break; } } if (!isValid) break; // Break outer loop if inner loop failed } return isValid; } // --- PDF Generation Function --- downloadPdfButton.addEventListener('click', async function() { const pdfTitle = 'Quantitative Macro Hedge Fund Performance Report'; let pdfHtmlContent = `${pdfTitle}
`; // General Parameters pdfHtmlContent += `Report Parameters
`; pdfHtmlContent += `Number of Historical Months: ${numHistoricalMonths}
`; pdfHtmlContent += `Annual Risk-Free Rate: ${(riskFreeRate * 100).toFixed(2)}%
`; // 1. Individual Factor/Strategy Performance if (factorData.length > 0) { pdfHtmlContent += `Individual Factor/Strategy Performance
`; factorData.forEach(factor => { const returnsDecimal = factor.returns.map(r => r / 100); const cumulativeReturn = returnsDecimal.reduce((acc, r) => acc * (1 + r), 1) - 1; const annualizedReturn = Math.pow(1 + cumulativeReturn, 12 / numHistoricalMonths) - 1; const monthlyStdDev = calculateStandardDeviation(returnsDecimal); const annualizedStdDev = monthlyStdDev * Math.sqrt(12); const annualizedRiskFreeRate = Math.pow(1 + riskFreeRate, 12 / numHistoricalMonths) - 1; const sharpeRatio = annualizedStdDev !== 0 ? (annualizedReturn - annualizedRiskFreeRate) / annualizedStdDev : 0; let maxDrawdown = 0; let peakValue = 1; let currentValue = 1; returnsDecimal.forEach(r => { currentValue *= (1 + r); if (currentValue > peakValue) { peakValue = currentValue; } const drawdown = (peakValue - currentValue) / peakValue; if (drawdown > maxDrawdown) { maxDrawdown = drawdown; } }); pdfHtmlContent += `${factor.name}
`; pdfHtmlContent += `Cumulative Return: ${(cumulativeReturn * 100).toFixed(2)}%
`; pdfHtmlContent += `Annualized Return: ${(annualizedReturn * 100).toFixed(2)}%
`; pdfHtmlContent += `Annualized Volatility: ${(annualizedStdDev * 100).toFixed(2)}%
`; pdfHtmlContent += `Sharpe Ratio: ${sharpeRatio.toFixed(2)}
`; pdfHtmlContent += `Maximum Drawdown: ${(maxDrawdown * 100).toFixed(2)}%
`; pdfHtmlContent += `Monthly Returns (%):
`; pdfHtmlContent += `| ${r.toFixed(2)} | `; }); pdfHtmlContent += `
Aggregated Portfolio Performance (Your Allocation)
`; pdfHtmlContent += `Cumulative Return: ${portfolioCumulativeReturnSpan.textContent}
`; pdfHtmlContent += `Annualized Return: ${portfolioAnnualizedReturnSpan.textContent}
`; pdfHtmlContent += `Annualized Volatility (Std Dev): ${portfolioAnnualizedStdDevSpan.textContent}
`; pdfHtmlContent += `Sharpe Ratio: ${portfolioSharpeRatioSpan.textContent}
`; pdfHtmlContent += `Maximum Drawdown: ${portfolioMaxDrawdownSpan.textContent}
`; pdfHtmlContent += `Portfolio Allocation Weights:
`; pdfHtmlContent += `| Factor/Strategy | Weight (%) |
|---|---|
| ${factor.name} | ${(allocationWeights[index] * 100).toFixed(2)} |
Optimized Portfolio (Minimum Variance)
`; pdfHtmlContent += `Expected Annualized Return: ${optimizedPortfolioReturnSpan.textContent}
`; pdfHtmlContent += `Annualized Volatility (Risk): ${optimizedPortfolioRiskSpan.textContent}
`; pdfHtmlContent += `Sharpe Ratio: ${optimizedPortfolioSharpeSpan.textContent}
`; pdfHtmlContent += `Optimal Weights:
`; pdfHtmlContent += `| Factor/Strategy | Weight (%) |
|---|---|
| ${name} | ${weight} |