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 (%).

${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