Quantitative Macro Hedge Fund Portfolio Modeler

Quantitative Macro Hedge Fund Portfolio Modeler

Macro Factor/Strategy Information

Define your macro factors or strategies and input their historical monthly returns (%).

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 += ``; for(let i=0; iMonth ${i+1}`; } pdfHtmlContent += ``; factor.returns.forEach(r => { pdfHtmlContent += ``; }); pdfHtmlContent += `
${r.toFixed(2)}
`; }); } // 2. Aggregated Portfolio Performance if (portfolioCalculated && !aggregatedPortfolioResultsDiv.classList.contains('hidden')) { 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 += ``; const allocationWeights = getAllocationWeights(); factorData.forEach((factor, index) => { pdfHtmlContent += ``; }); pdfHtmlContent += `
Factor/StrategyWeight (%)
${factor.name}${(allocationWeights[index] * 100).toFixed(2)}
`; } // 3. Optimized Portfolio Performance if (optimizationCalculated && !optimizationResultsDiv.classList.contains('hidden')) { pdfHtmlContent += `

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 += ``; // Retrieve optimal weights from the display div const optimalWeightParagraphs = optimalWeightsDisplayDiv.querySelectorAll('p'); optimalWeightParagraphs.forEach(p => { const text = p.textContent; const [name, weight] = text.split(':').map(s => s.trim()); pdfHtmlContent += ``; }); pdfHtmlContent += `
Factor/StrategyWeight (%)
${name}${weight}
`; } // Set the HTML content to the hidden container pdfRenderContainer.innerHTML = pdfHtmlContent; try { // Create a canvas from the hidden container const canvas = await html2canvas(pdfRenderContainer, { useCORS: true, scale: 2, // Increase scale for better resolution logging: false // Disable logging for cleaner console }); const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF('p', 'mm', 'a4'); // 'p' for portrait, 'mm' for millimeters, 'a4' size const imgWidth = 210; // A4 width in mm const pageHeight = 297; // A4 height in mm const imgHeight = canvas.height * imgWidth / canvas.width; let heightLeft = imgHeight; let position = 0; // Add image with calculated height for multi-page support pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; while (heightLeft >= 0) { position = heightLeft - imgHeight; pdf.addPage(); pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; } pdf.save('macro_fund_performance_report.pdf'); } catch (error) { console.error('Error generating PDF:', error); alertUser('Failed to generate PDF. Please try again or check console for errors.'); } finally { // Clear the content of the hidden container pdfRenderContainer.innerHTML = ''; } }); // --- Custom Alert Function (instead of alert()) --- function alertUser(message) { // Implement a simple modal or message box instead of alert() // For now, using a simple console log for demonstration in browser console.warn("Alert (User-friendly):", message); // A basic in-page message display: let msgBox = document.getElementById('user-message-box'); if (!msgBox) { msgBox = document.createElement('div'); msgBox.id = 'user-message-box'; msgBox.className = 'fixed inset-x-0 top-0 flex items-center justify-center p-4 z-50'; msgBox.innerHTML = ` `; document.body.appendChild(msgBox); document.getElementById('close-user-message').addEventListener('click', () => { msgBox.classList.add('hidden'); }); } document.getElementById('user-message-text').textContent = message; msgBox.classList.remove('hidden'); setTimeout(() => { msgBox.classList.add('hidden'); }, 5000); // Hide after 5 seconds } // Initial rendering renderFactorsInputs(); updateAllocationInputs(); });
Scroll to Top