Global Macro Hedge Fund Backtester
Define Your Global Macro Strategy
Asset Universe (Illustrative)
Trading Signal (Illustrative)
Note: 'SENTIMENT' data is a mock indicator (Positive, Neutral, Negative) for each period in the sample dataset.
Portfolio Allocation
Set Backtest Parameters
Backtest Results & Analysis
Click "Run Backtest" to see results. Ensure strategy and parameters are set.
No trades executed.
`; } resultsOutputDiv.innerHTML = html; } if (runBacktestBtn) { runBacktestBtn.addEventListener('click', runBacktest); } // --- PDF Download --- function loadJsPdfIfNeeded(callback) { if (jsPdfLoaded) { if (callback) callback(); return; } const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js'; script.onload = () => { jsPdfLoaded = true; console.log("jsPDF loaded dynamically."); if (callback) callback(); }; script.onerror = () => { console.error("Failed to load jsPDF. PDF functionality unavailable."); alert("Error: Could not load PDF library."); }; document.head.appendChild(script); } function downloadResultsAsPdf() { if (!jsPdfLoaded) { alert("PDF library not loaded. Please wait or ensure it's included."); return; } if (!backtestResultsData) { alert("No backtest results to download. Please run a backtest first."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF({ unit: 'pt', format: 'a4' }); const r = backtestResultsData; // alias for results const pageMargin = 40; const pageWidth = doc.internal.pageSize.getWidth() - 2 * pageMargin; let y = pageMargin; function addTitle(text) { doc.setFontSize(18); doc.setFont(undefined, 'bold'); doc.setTextColor(26, 35, 126); // Primary color doc.text(text, pageMargin, y); y += 30; } function addSectionTitle(text) { if (y > doc.internal.pageSize.getHeight() - 80) { doc.addPage(); y = pageMargin; } doc.setFontSize(14); doc.setFont(undefined, 'bold'); doc.setTextColor(57, 73, 171); // Secondary color doc.text(text, pageMargin, y); y += 20; } function addLine(key, value) { if (y > doc.internal.pageSize.getHeight() - 40) { doc.addPage(); y = pageMargin; } doc.setFontSize(10); doc.setFont(undefined, 'bold'); doc.setTextColor(33,33,33); doc.text(key, pageMargin, y); doc.setFont(undefined, 'normal'); const valueText = String(value); // Ensure value is a string doc.text(valueText, pageMargin + 150, y); // Adjust x for value y += 15; } function addTable(headers, data, columnWidths) { if (y > doc.internal.pageSize.getHeight() - 100) { doc.addPage(); y = pageMargin; } doc.setFontSize(9); doc.setFillColor(57, 73, 171); doc.setTextColor(255); doc.setFont(undefined, 'bold'); let currentX = pageMargin; headers.forEach((header, i) => { doc.rect(currentX, y, columnWidths[i], 20, 'F'); doc.text(header, currentX + 5, y + 14); currentX += columnWidths[i]; }); y += 20; doc.setTextColor(33,33,33); doc.setFont(undefined, 'normal'); data.forEach(row => { if (y > doc.internal.pageSize.getHeight() - 40) { doc.addPage(); y = pageMargin; } currentX = pageMargin; row.forEach((cell, i) => { doc.rect(currentX, y, columnWidths[i], 18); const cellText = String(cell); // Ensure cell is a string const textLines = doc.splitTextToSize(cellText, columnWidths[i] - 10); doc.text(textLines, currentX + 5, y + 12); currentX += columnWidths[i]; }); y += 18; }); y += 10; } addTitle(`Backtest Report: ${r.strategyName}`); addSectionTitle("Strategy & Parameters"); addLine("Selected Assets:", r.selectedAssets.join(', ')); addLine("Signal Logic:", r.signalType.replace('_', ' ')); addLine("Initial Capital:", `$${r.initialCapital.toLocaleString()}`); addLine("Leverage:", `${r.leverage}x`); addLine("Backtest Period:", `${r.startDateStr} to ${r.endDateStr}`); addLine("Transaction Cost:", `${(r.transactionCostPct * 100).toFixed(2)}% per trade`); addLine("Annual Risk-Free Rate:", `${(r.riskFreeRateAnnualPct * 100).toFixed(1)}%`); addSectionTitle("Key Performance Metrics"); addLine("Final Portfolio Value:", `$${r.finalPortfolioValue.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}`); addLine("Total Return:", `${r.totalReturn.toFixed(2)}%`); addLine("Annualized Return:", `${r.annualizedReturn.toFixed(2)}%`); addLine("Annualized Volatility:", `${r.annualizedVolatility.toFixed(2)}%`); addLine("Sharpe Ratio:", `${r.sharpeRatio.toFixed(2)}`); addLine("Maximum Drawdown:", `${r.maxDrawdown.toFixed(2)}%`); addSectionTitle("Portfolio Value Over Time"); const portfolioHeaders = ["Date", "Portfolio Value ($)"]; const portfolioTableData = r.portfolioHistory.map(h => [h.date, h.value.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})]); addTable(portfolioHeaders, portfolioTableData, [100, 150]); if (r.tradeLog.length > 0) { addSectionTitle("Trade Log (Simplified)"); const tradeHeaders = ["Date", "Asset", "Action", "Units", "Price ($)", "P&L ($)"]; const tradeTableData = r.tradeLog.map(t => [t.date, t.asset, t.action, t.units.toFixed(4), t.price.toFixed(2), t.pnl.toFixed(2)]); addTable(tradeHeaders, tradeTableData, [70, 70, 70, 70, 70, 70]); } else { addLine("Trade Log:", "No trades executed."); } // Footer const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFontSize(8); doc.setTextColor(150); doc.text(`Page ${i} of ${pageCount} - Global Macro Backtest Report`, pageMargin, doc.internal.pageSize.getHeight() - 20); doc.text(new Date().toLocaleString(), doc.internal.pageSize.getWidth() - pageMargin - doc.getTextWidth(new Date().toLocaleString()), doc.internal.pageSize.getHeight() - 20); } doc.save(`Backtest_Report_${r.strategyName.replace(/\s+/g, '_')}.pdf`); } if (downloadPdfBtn) { downloadPdfBtn.addEventListener('click', () => loadJsPdfIfNeeded(downloadResultsAsPdf)); } // --- Initialization --- populateDateSelects(); showTab(0); loadJsPdfIfNeeded(); // Attempt to load jsPDF early });