diff --git a/index.html b/index.html index 14cdd9b..216c7d2 100644 --- a/index.html +++ b/index.html @@ -117,13 +117,19 @@
- + + + + Limit data size for faster processing. Recommended: 5,000 bars
@@ -290,6 +296,7 @@ + diff --git a/js/backtester.js b/js/backtester.js index 03b1fac..55ff2d4 100644 --- a/js/backtester.js +++ b/js/backtester.js @@ -2,9 +2,15 @@ * Backtester - Fast backtesting engine using typed arrays */ class Backtester { - constructor(data, strategy) { + constructor(data, strategy, symbol = '') { this.strategy = strategy; this.dataLength = data.length; + this.symbol = symbol; + + // Get profit multiplier based on symbol type + this.profitMultiplier = typeof getProfitMultiplier === 'function' + ? getProfitMultiplier(symbol) + : 10000; // Default to standard forex // Use typed arrays for performance this.open = new Float64Array(data.length); @@ -43,7 +49,7 @@ class Backtester { if (position.type === 'BUY' && this.checkSellSignal(i)) { // Close BUY and open SELL const closePrice = this.close[i]; - const profit = closePrice - position.entry; + const profit = (closePrice - position.entry) * this.profitMultiplier; balance += profit; trades.push({ type: 'BUY', @@ -60,7 +66,7 @@ class Backtester { } else if (position.type === 'SELL' && this.checkBuySignal(i)) { // Close SELL and open BUY const closePrice = this.close[i]; - const profit = position.entry - closePrice; + const profit = (position.entry - closePrice) * this.profitMultiplier; balance += profit; trades.push({ type: 'SELL', @@ -101,7 +107,7 @@ class Backtester { // Close any open position at the end if (position) { const exitPrice = this.close[this.dataLength - 1]; - const profit = exitPrice - position.entry; + const profit = (exitPrice - position.entry) * this.profitMultiplier; balance += profit; trades.push({ type: 'BUY', @@ -266,7 +272,7 @@ class Backtester { type: position.type, entry: position.entry, exit: position.tp, - profit: position.tp - position.entry, + profit: (position.tp - position.entry) * this.profitMultiplier, reason: 'take_profit', openTime: position.openTime, closeTime: this.time[index] @@ -278,7 +284,7 @@ class Backtester { type: position.type, entry: position.entry, exit: position.sl, - profit: position.sl - position.entry, + profit: (position.sl - position.entry) * this.profitMultiplier, reason: 'stop_loss', openTime: position.openTime, closeTime: this.time[index] @@ -291,7 +297,7 @@ class Backtester { type: position.type, entry: position.entry, exit: position.tp, - profit: position.entry - position.tp, + profit: (position.entry - position.tp) * this.profitMultiplier, reason: 'take_profit', openTime: position.openTime, closeTime: this.time[index] @@ -303,7 +309,7 @@ class Backtester { type: position.type, entry: position.entry, exit: position.sl, - profit: position.entry - position.sl, + profit: (position.entry - position.sl) * this.profitMultiplier, reason: 'stop_loss', openTime: position.openTime, closeTime: this.time[index] diff --git a/js/ga-engine.js b/js/ga-engine.js index aef4428..6aa767d 100644 --- a/js/ga-engine.js +++ b/js/ga-engine.js @@ -2,8 +2,9 @@ * Genetic Algorithm Engine - Evolves trading strategies */ class GeneticOptimizer { - constructor(data, config) { + constructor(data, config, symbol = '') { this.data = data; + this.symbol = symbol; this.config = { populationSize: config.populationSize || 100, generations: config.generations || 50, @@ -40,7 +41,7 @@ class GeneticOptimizer { */ evaluatePopulation() { for (const strategy of this.population) { - const backtester = new Backtester(this.data, strategy); + const backtester = new Backtester(this.data, strategy, this.symbol); strategy.metrics = backtester.run(); strategy.fitness = this.calculateFitness(strategy.metrics); } diff --git a/js/html-report-generator.js b/js/html-report-generator.js index 3ac57b2..fd6601b 100644 --- a/js/html-report-generator.js +++ b/js/html-report-generator.js @@ -3,10 +3,12 @@ * Generates comprehensive HTML reports with full trading statement */ -function generateHTMLReport(strategy, index) { +function generateHTMLReport(strategy, index, symbol = '') { const m = strategy.metrics; const name = `FxMath_${String(index + 1).padStart(3, '0')}_PF${(m.profitFactor || 0).toFixed(2).replace('.', '_')}_WR${Math.round(m.winRate || 0)}`; + // Note: Profits are already multiplied in the backtester, so we can display them directly + // Generate SELL rules (inverted operators) const sellRulesHTML = strategy.rules.map((rule, i) => { const invertedOp = rule.operator === '>' ? '<=' : @@ -44,8 +46,8 @@ function generateHTMLReport(strategy, index) { ${trade.type} ${new Date(trade.openTime).toLocaleString()} ${new Date(trade.closeTime).toLocaleString()} - $${trade.entry.toFixed(2)} - $${trade.exit.toFixed(2)} + $${trade.entry.toFixed(5)} + $${trade.exit.toFixed(5)} $${trade.profit.toFixed(2)} ${trade.reason.toUpperCase()} diff --git a/js/main.js b/js/main.js index e6b858b..dde4fb1 100644 --- a/js/main.js +++ b/js/main.js @@ -6,6 +6,7 @@ let appData = { csvData: null, dataName: '', + symbol: '', optimizer: null, foundStrategies: [], selectedStrategies: new Set(), @@ -139,7 +140,14 @@ function handleFileUpload(file) { })); appData.dataName = file.name; + + // Extract symbol from filename + appData.symbol = typeof extractSymbolFromFilename === 'function' + ? extractSymbolFromFilename(file.name) + : ''; + console.log('✅ Data loaded successfully:', appData.csvData.length, 'bars'); + console.log('📊 Detected symbol:', appData.symbol || 'Unknown'); // Apply data size limit applyDataSizeLimit(); @@ -189,13 +197,37 @@ function displayDataPreview() { * Apply data size limit */ function applyDataSizeLimit() { - const limit = parseInt(document.getElementById('data-size-limit').value); + const limitSelect = document.getElementById('data-size-limit'); + const customInput = document.getElementById('custom-bars-input'); + + let limit; + if (limitSelect.value === 'custom') { + limit = parseInt(customInput.value) || 0; + } else { + limit = parseInt(limitSelect.value); + } + if (limit > 0 && appData.csvData.length > limit) { console.log(`✂️ Limiting data from ${appData.csvData.length} to ${limit} bars`); appData.csvData = appData.csvData.slice(-limit); // Take last N bars } } +/** + * Toggle custom bars input visibility + */ +function toggleCustomBarsInput() { + const limitSelect = document.getElementById('data-size-limit'); + const customInput = document.getElementById('custom-bars-input'); + + if (limitSelect.value === 'custom') { + customInput.style.display = 'block'; + customInput.focus(); + } else { + customInput.style.display = 'none'; + } +} + /** * Detect timeframe from data */ @@ -269,7 +301,8 @@ async function startGeneration() { // Create optimizer console.log('🧬 Creating GA optimizer with config:', config); - appData.optimizer = new GeneticOptimizer(appData.csvData, config); + console.log('📊 Using symbol:', appData.symbol || 'Unknown'); + appData.optimizer = new GeneticOptimizer(appData.csvData, config, appData.symbol); // Start time const startTime = Date.now(); @@ -520,8 +553,8 @@ function downloadStrategy(index, type) { content = converter.generate(); filename = name + '.pine'; } else if (type === 'report') { - // Use new HTML report generator - content = generateHTMLReport(strategy, index); + // Use new HTML report generator with symbol + content = generateHTMLReport(strategy, index, appData.symbol); filename = name + '_report.html'; } else if (type === 'json') { content = JSON.stringify(strategy.toJSON(), null, 2); @@ -723,3 +756,4 @@ window.downloadAllStrategies = downloadAllStrategies; window.toggleStrategySelection = toggleStrategySelection; window.showComparison = showComparison; window.hideComparison = hideComparison; +window.toggleCustomBarsInput = toggleCustomBarsInput; diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 0000000..787527d --- /dev/null +++ b/js/utils.js @@ -0,0 +1,65 @@ +/** + * Utility Functions for FxMath Quant + * Helper functions for profit calculations and formatting + */ + +/** + * Get profit multiplier based on symbol type + * @param {string} symbol - Trading symbol (e.g., "EURUSD", "USDJPY", "XAUUSD") + * @returns {number} - Multiplier to convert price difference to pips/points + */ +function getProfitMultiplier(symbol) { + if (!symbol) return 10000; // Default to standard forex + + const upperSymbol = symbol.toUpperCase(); + + // Check for JPY pairs (2 decimal places) + if (upperSymbol.includes('JPY')) { + return 100; + } + + // Check for XAU (Gold) - already in dollars + if (upperSymbol.includes('XAU')) { + return 1; + } + + // Default: standard forex pairs (EURUSD, GBPUSD, etc.) - 4 decimal places + return 10000; +} + +/** + * Format profit value with appropriate multiplier + * @param {number} profit - Raw profit value (price difference) + * @param {string} symbol - Trading symbol + * @param {number} decimals - Number of decimal places (default: 2) + * @returns {string} - Formatted profit string + */ +function formatProfit(profit, symbol, decimals = 2) { + const multiplier = getProfitMultiplier(symbol); + return (profit * multiplier).toFixed(decimals); +} + +/** + * Extract symbol from filename + * @param {string} filename - CSV filename (e.g., "EURUSD_H1.csv") + * @returns {string} - Symbol name + */ +function extractSymbolFromFilename(filename) { + if (!filename) return ''; + + // Remove path and extension + const basename = filename.split('/').pop().split('\\').pop(); + const nameWithoutExt = basename.replace(/\.(csv|txt)$/i, ''); + + // Extract symbol (everything before first underscore or period) + const symbol = nameWithoutExt.split(/[_\.]/)[0]; + + return symbol || ''; +} + +// Export functions +if (typeof window !== 'undefined') { + window.getProfitMultiplier = getProfitMultiplier; + window.formatProfit = formatProfit; + window.extractSymbolFromFilename = extractSymbolFromFilename; +}