Python-Based Quantitative Trading Toolkit (Conceptual JS Simulator)
Strategy Setup & Price Data Input
Ensure no headers. Date format YYYY-MM-DD (or any consistent string for date). Only O,H,L,C are used for basic indicators.
Technical Indicators
Select and configure indicators. They will be calculated based on the 'Close' price from your input data.
Strategy Rules (IF-THEN Logic)
Define entry and exit rules based on price and indicator values. Conditions within a rule are ANDed.
Entry Rules (Long Only for this simulator)
Exit Rules (for Long Positions)
Backtest Results
Backtest performance summary will appear here.
Trade Log:
| # | Entry Date | Entry Price ($) | Exit Date | Exit Price ($) | P&L ($) | P&L (%) |
|---|
Total Net P&L: $${totalNetPnl.toFixed(2)}
Total Return: ${totalReturnPercent.toFixed(2)}%
Number of Trades: ${numTrades}
Winning Trades: ${winningTrades}
Losing Trades: ${losingTrades}
Win Rate: ${winRate.toFixed(2)}%
`; trades.forEach((trade, index) => { if(trade.exitPrice === null) return; // Skip trades not closed const row = logBody.insertRow(); row.insertCell().textContent = index + 1; row.insertCell().textContent = trade.entryDate; row.insertCell().textContent = trade.entryPrice.toFixed(2); row.insertCell().textContent = trade.exitDate; row.insertCell().textContent = trade.exitPrice.toFixed(2); const pnlDollarCell = row.insertCell(); pnlDollarCell.textContent = (trade.pnlDollar || 0).toFixed(2); pnlDollarCell.className = (trade.pnlDollar || 0) >= 0 ? 'pbqtt_profit' : 'pbqtt_loss'; const pnlPercentCell = row.insertCell(); pnlPercentCell.textContent = (trade.pnlPercent || 0).toFixed(2) + '%'; pnlPercentCell.className = (trade.pnlPercent || 0) >= 0 ? 'pbqtt_profit' : 'pbqtt_loss'; }); if (pdfButton) pdfButton.style.display = 'block'; } // --- PDF Generation --- function pbqtt_downloadPDF() { if (!pbqtt_strategy.backtestResults) { alert("Run backtest first."); return; } if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('PDF library (jsPDF) is not loaded.'); return; } const { jsPDF: JSPDF } = window.jspdf; const doc = new JSPDF(); let y = 15; const m = 15; const cw = doc.internal.pageSize.getWidth() - (2 * m); function addLine(text, size, style = 'normal', indent = 0, spacing = 2.5) { if (y > 275) { doc.addPage(); y = m; } doc.setFontSize(size); doc.setFont(undefined, style); const lines = doc.splitTextToSize(text, cw - indent); doc.text(lines, m + indent, y); y += (lines.length * (size * 0.35)) + spacing; } // Collect strategy settings const stratName = pbqtt_getInputValue('pbqtt_strategyName', 'Unnamed Strategy'); const assetName = pbqtt_getInputValue('pbqtt_assetName', 'ASSET_XYZ'); const initialCap = pbqtt_getInputNumValue('pbqtt_initialCapital', 10000); addLine(`Quant Trading Strategy Report: ${stratName}`, 16, 'bold', 0, 5); addLine(`Asset: ${assetName}, Initial Capital: $${initialCap.toFixed(2)}`, 10); y += 3; addLine("Indicators Configured:", 12, 'bold', 0, 3); pbqtt_strategy.indicators.forEach(ind => addLine(`- ${ind.displayName || ind.type + '('+ind.period+')'}`, 9, 'normal', 5, 1.5)); y += 3; const renderRulesToPdf = (rules, title) => { addLine(title, 12, 'bold', 0, 3); rules.forEach((rule, i) => { addLine(`Rule ${i+1}:`, 10, 'bold', 5, 2); rule.conditions.forEach(c => { let c1Str = c.source1_type === 'price' ? `Price(${c.source1_priceComponent})` : pbqtt_strategy.indicators.find(i=>i.id===c.source1_indicatorId)?.displayName || 'N/A'; let c2Str = ''; if(c.source2_type === 'value') c2Str = c.source2_value; else if(c.source2_type === 'price') c2Str = `Price(${c.source2_priceComponent})`; else c2Str = pbqtt_strategy.indicators.find(i=>i.id===c.source2_indicatorId)?.displayName || 'N/A'; addLine(` IF ${c1Str} ${c.operator.replace(/_/g,' ')} ${c2Str}`, 9, 'normal', 10, 1); }); }); y += 2; }; renderRulesToPdf(pbqtt_strategy.entryRules, "Entry Rules:"); renderRulesToPdf(pbqtt_strategy.exitRules, "Exit Rules:"); y += 5; addLine("Backtest Performance Summary:", 14, 'bold', 0, 4); const res = pbqtt_strategy.backtestResults; addLine(`Initial Capital: $${res.initialCapital.toFixed(2)}`, 10); addLine(`Final Capital: $${res.finalCapital.toFixed(2)}`, 10); addLine(`Total Net P&L: $${res.totalNetPnl.toFixed(2)} (${res.totalReturnPercent.toFixed(2)}%)`, 10); addLine(`Number of Trades: ${res.numTrades}`, 10); addLine(`Win Rate: ${res.winRate.toFixed(2)}% (Winners: ${res.winningTrades}, Losers: ${res.losingTrades})`, 10); y += 5; addLine("Trade Log:", 12, 'bold', 0, 3); if(pbqtt_strategy.tradeLog.filter(t => t.exitPrice !== null).length > 0){ doc.setFontSize(8); let head = [['#', 'Entry Date', 'Entry Px', 'Exit Date', 'Exit Px', 'P&L $', 'P&L %']]; let body = pbqtt_strategy.tradeLog.filter(t => t.exitPrice !== null).map((t, i) => [ i + 1, t.entryDate, t.entryPrice.toFixed(2), t.exitDate, t.exitPrice.toFixed(2), (t.pnlDollar||0).toFixed(2), (t.pnlPercent||0).toFixed(2) ]); if (y > 200 && body.length > 5) { doc.addPage(); y = m;} // Crude check for page break before table doc.autoTable({startY: y, head: head, body: body, theme: 'grid', styles: {fontSize: 7, cellPadding: 1.5}, headStyles: {fillColor: [46, 53, 64], textColor: 230, fontSize: 7.5}, columnStyles: { 0: {cellWidth: 8}, 5:{cellWidth:18},6:{cellWidth:18} }}); y = doc.lastAutoTable.finalY + 10; } else { addLine("No trades executed.", 10); } doc.save(`${stratName.replace(/\s+/g, '_') || 'quant_strategy_report'}.pdf`); }