#!/usr/bin/env node /** * Backtest Anti-Chop Filter V2 * * Compares OLD scoring (price position < 40% = OK) * vs NEW scoring (price position < 40% + ADX < 25 = -25 points) */ const trades = [ // Format: [direction, atr, adx, rsi, volumeRatio, pricePosition, oldScore, pnl, holdTime, exitReason] // Most recent first ['short', 0.45, 32, 42, 1.25, 45, 100, null, null, 'OPEN'], // Current position ['short', 0.37, 21.2, 44.1, 0.85, 16.8, 75, 0, 24, 'SOFT_SL'], // Flip-flop #3 ['long', 0.37, 21.4, 47.5, 1.66, 30.6, 80, 0, 8, 'SOFT_SL'], // Flip-flop #2 ['short', 0.36, 21.2, 41.1, 1.64, 23.8, 90, -3.4, 306, 'manual'], // Flip-flop #1 ['long', 0.28, 19.7, 50.3, 0.83, 55.7, 75, 0, 1356, 'TP2'], ['short', 0.26, 16.9, 41.6, 1.37, 24.3, 85, -11.3, 1506, 'SL'], ['short', 0.33, 14.8, 37.1, 2.29, 12, 80, -31.2, 914, 'SL'], ['long', 0.52, 22.7, 53.1, 0.88, 37.9, 80, 15.6, 323, 'TP2'], ['short', 0.52, 24, 48.8, 1.04, 24.6, 80, -6.2, 301, 'manual'], ['long', 0.3, 26.1, 61.5, 1.45, 74.5, 95, 14.5, 9280, 'TP2'], ['short', 0.28, 19.8, 38.1, 1.26, 3.5, 65, 3.4, 1247, 'SL'], ['long', 0.26, 22.1, 57.3, 0.43, 81.4, 65, 14.6, 3560, 'TP2'], ['short', 0.29, 26.7, 57, 0.62, 78.8, 65, 7.6, 774, 'SL'], ['long', 0.32, 28, 62.4, 1.23, 88.9, 95, -3.1, 900, 'manual'], ['short', 0.27, 15.2, 44.5, 1.26, 43.9, 65, 16.3, 226, 'TP2'], ['long', 0.25, 16, 54.7, 1.65, 76.1, 70, -8.6, 597, 'manual'], ['short', 0.25, 18.2, 52.7, 1.25, 69.4, 75, 1.9, 32, 'TP1'], ['long', 0.25, 19.9, 58.2, 2.11, 87.7, 100, -0.9, 1204, 'manual'], ['short', 0.17, 12.9, 42.6, 1.7, 22.2, 70, 7.7, 585, 'SL'], ] function scoreSignalQualityOLD(atr, adx, rsi, volumeRatio, pricePosition, direction) { let score = 50 const reasons = [] // ATR if (atr < 0.15) { score -= 15 reasons.push(`ATR too low`) } else if (atr > 2.5) { score -= 20 reasons.push(`ATR too high`) } else if (atr >= 0.15 && atr < 0.4) { score += 5 reasons.push(`ATR moderate`) } else { score += 10 reasons.push(`ATR healthy`) } // ADX if (adx > 25) { score += 15 reasons.push(`Strong trend`) } else if (adx < 18) { score -= 15 reasons.push(`Weak trend`) } else { score += 5 reasons.push(`Moderate trend`) } // RSI if (direction === 'long') { if (rsi > 50 && rsi < 70) { score += 10 reasons.push(`RSI supports long`) } else if (rsi > 70) { score -= 10 reasons.push(`RSI overbought`) } } else { if (rsi < 50 && rsi > 30) { score += 10 reasons.push(`RSI supports short`) } else if (rsi < 30) { score -= 10 reasons.push(`RSI oversold`) } } // Volume const isChoppy = adx < 16 const hasHighVolume = volumeRatio > 1.5 if (isChoppy && hasHighVolume) { score -= 15 reasons.push(`Whipsaw trap`) } else if (volumeRatio > 1.5) { score += 15 reasons.push(`Very strong volume`) } else if (volumeRatio > 1.2) { score += 10 reasons.push(`Strong volume`) } else if (volumeRatio < 0.8) { score -= 10 reasons.push(`Weak volume`) } // Price position - OLD LOGIC if (direction === 'long' && pricePosition > 95) { if (volumeRatio > 1.4) { score += 5 reasons.push(`Volume breakout at top`) } else { score -= 15 reasons.push(`Chasing highs`) } } else if (direction === 'short' && pricePosition < 5) { if (volumeRatio > 1.4) { score += 5 reasons.push(`Volume breakdown at bottom`) } else { score -= 15 reasons.push(`Chasing lows`) } } else { score += 5 reasons.push(`Price position OK`) } return { score, reasons } } function scoreSignalQualityNEW(atr, adx, rsi, volumeRatio, pricePosition, direction) { let score = 50 const reasons = [] // ATR (same as old) if (atr < 0.15) { score -= 15 reasons.push(`ATR too low`) } else if (atr > 2.5) { score -= 20 reasons.push(`ATR too high`) } else if (atr >= 0.15 && atr < 0.4) { score += 5 reasons.push(`ATR moderate`) } else { score += 10 reasons.push(`ATR healthy`) } // ADX (same as old) if (adx > 25) { score += 15 reasons.push(`Strong trend`) } else if (adx < 18) { score -= 15 reasons.push(`Weak trend`) } else { score += 5 reasons.push(`Moderate trend`) } // RSI (same as old) if (direction === 'long') { if (rsi > 50 && rsi < 70) { score += 10 reasons.push(`RSI supports long`) } else if (rsi > 70) { score -= 10 reasons.push(`RSI overbought`) } } else { if (rsi < 50 && rsi > 30) { score += 10 reasons.push(`RSI supports short`) } else if (rsi < 30) { score -= 10 reasons.push(`RSI oversold`) } } // Volume (same as old) const isChoppy = adx < 16 const hasHighVolume = volumeRatio > 1.5 if (isChoppy && hasHighVolume) { score -= 15 reasons.push(`Whipsaw trap`) } else if (volumeRatio > 1.5) { score += 15 reasons.push(`Very strong volume`) } else if (volumeRatio > 1.2) { score += 10 reasons.push(`Strong volume`) } else if (volumeRatio < 0.8) { score -= 10 reasons.push(`Weak volume`) } // Price position - NEW LOGIC WITH ANTI-CHOP const isWeakTrend = adx < 25 const isLowInRange = pricePosition < 40 if (isLowInRange && isWeakTrend) { score -= 25 reasons.push(`⚠️ RANGE-BOUND CHOP (pos ${pricePosition.toFixed(0)}%, ADX ${adx.toFixed(1)})`) } else if (direction === 'long' && pricePosition > 95) { if (volumeRatio > 1.4) { score += 5 reasons.push(`Volume breakout at top`) } else { score -= 15 reasons.push(`Chasing highs`) } } else if (direction === 'short' && pricePosition < 5) { if (volumeRatio > 1.4) { score += 5 reasons.push(`Volume breakdown at bottom`) } else { score -= 15 reasons.push(`Chasing lows`) } } else { score += 5 reasons.push(`Price position OK`) } return { score, reasons } } console.log('=' .repeat(100)) console.log('BACKTEST: Anti-Chop Filter V2 - Price Position < 40% + ADX < 25 = -25 points') console.log('=' .repeat(100)) console.log() let oldTotalPnL = 0 let oldWins = 0 let oldLosses = 0 let oldTradesExecuted = 0 let newTotalPnL = 0 let newWins = 0 let newLosses = 0 let newTradesExecuted = 0 let blockedBadTrades = [] let blockedGoodTrades = [] let stillExecutedBadTrades = [] const MIN_SCORE = 65 trades.forEach(([direction, atr, adx, rsi, volumeRatio, pricePosition, oldScore, pnl, holdTime, exitReason], idx) => { const oldResult = scoreSignalQualityOLD(atr, adx, rsi, volumeRatio, pricePosition, direction) const newResult = scoreSignalQualityNEW(atr, adx, rsi, volumeRatio, pricePosition, direction) const oldPassed = oldResult.score >= MIN_SCORE const newPassed = newResult.score >= MIN_SCORE const isBadTrade = pnl !== null && (pnl <= 0 || holdTime < 60) // Loss or quick stop const isGoodTrade = pnl !== null && pnl > 0 && holdTime > 300 // Profit + held > 5min // OLD system stats if (oldPassed && pnl !== null) { oldTradesExecuted++ oldTotalPnL += pnl if (pnl > 0) oldWins++ else oldLosses++ } // NEW system stats if (newPassed && pnl !== null) { newTradesExecuted++ newTotalPnL += pnl if (pnl > 0) newWins++ else newLosses++ } // Track what changed if (oldPassed && !newPassed) { if (isBadTrade) { blockedBadTrades.push({ direction, atr, adx, pricePosition, pnl, holdTime, exitReason, oldScore: oldResult.score, newScore: newResult.score }) } else if (isGoodTrade) { blockedGoodTrades.push({ direction, atr, adx, pricePosition, pnl, holdTime, exitReason, oldScore: oldResult.score, newScore: newResult.score }) } } if (newPassed && isBadTrade) { stillExecutedBadTrades.push({ direction, atr, adx, pricePosition, pnl, holdTime, exitReason, oldScore: oldResult.score, newScore: newResult.score }) } // Print details for significant trades if (oldPassed !== newPassed || Math.abs(oldResult.score - newResult.score) > 10) { console.log(`Trade #${idx + 1}: ${direction.toUpperCase()} | ADX ${adx} | PricePos ${pricePosition.toFixed(0)}% | P&L $${pnl?.toFixed(1) || 'OPEN'}`) console.log(` OLD: ${oldResult.score} ${oldPassed ? '✅ PASS' : '❌ BLOCK'} - ${oldResult.reasons.join(', ')}`) console.log(` NEW: ${newResult.score} ${newPassed ? '✅ PASS' : '❌ BLOCK'} - ${newResult.reasons.join(', ')}`) if (oldPassed && !newPassed && isBadTrade) { console.log(` 🎯 BLOCKED BAD TRADE: ${exitReason} after ${holdTime}s`) } if (oldPassed && !newPassed && isGoodTrade) { console.log(` ⚠️ BLOCKED GOOD TRADE: Would have made $${pnl.toFixed(2)}`) } console.log() } }) console.log('=' .repeat(100)) console.log('RESULTS SUMMARY') console.log('=' .repeat(100)) console.log() console.log('OLD SYSTEM (Price position < 40% = OK):') console.log(` Trades executed: ${oldTradesExecuted}`) console.log(` Wins: ${oldWins} | Losses: ${oldLosses} | Win rate: ${oldTradesExecuted > 0 ? ((oldWins / oldTradesExecuted) * 100).toFixed(1) : 0}%`) console.log(` Total P&L: $${oldTotalPnL.toFixed(2)}`) console.log(` Avg P&L per trade: $${oldTradesExecuted > 0 ? (oldTotalPnL / oldTradesExecuted).toFixed(2) : 0}`) console.log() console.log('NEW SYSTEM (Price position < 40% + ADX < 25 = -25 points):') console.log(` Trades executed: ${newTradesExecuted}`) console.log(` Wins: ${newWins} | Losses: ${newLosses} | Win rate: ${newTradesExecuted > 0 ? ((newWins / newTradesExecuted) * 100).toFixed(1) : 0}%`) console.log(` Total P&L: $${newTotalPnL.toFixed(2)}`) console.log(` Avg P&L per trade: $${newTradesExecuted > 0 ? (newTotalPnL / newTradesExecuted).toFixed(2) : 0}`) console.log() console.log('IMPACT:') console.log(` 🎯 Bad trades BLOCKED: ${blockedBadTrades.length}`) if (blockedBadTrades.length > 0) { const savedLoss = blockedBadTrades.reduce((sum, t) => sum + Math.abs(t.pnl), 0) console.log(` Saved loss: $${savedLoss.toFixed(2)}`) blockedBadTrades.forEach(t => { console.log(` - ${t.direction} ADX ${t.adx} Pos ${t.pricePosition.toFixed(0)}%: ${t.exitReason} in ${t.holdTime}s → $${t.pnl.toFixed(2)}`) }) } console.log() console.log(` ⚠️ Good trades BLOCKED: ${blockedGoodTrades.length}`) if (blockedGoodTrades.length > 0) { const missedProfit = blockedGoodTrades.reduce((sum, t) => sum + t.pnl, 0) console.log(` Missed profit: $${missedProfit.toFixed(2)}`) blockedGoodTrades.forEach(t => { console.log(` - ${t.direction} ADX ${t.adx} Pos ${t.pricePosition.toFixed(0)}%: Held ${t.holdTime}s → $${t.pnl.toFixed(2)}`) }) } console.log() console.log(` ⚠️ Bad trades STILL EXECUTED: ${stillExecutedBadTrades.length}`) if (stillExecutedBadTrades.length > 0) { stillExecutedBadTrades.forEach(t => { console.log(` - ${t.direction} ADX ${t.adx} Pos ${t.pricePosition.toFixed(0)}%: ${t.exitReason} in ${t.holdTime}s → $${t.pnl.toFixed(2)} (score ${t.newScore})`) }) } console.log() const improvement = newTotalPnL - oldTotalPnL console.log(`NET IMPROVEMENT: $${improvement.toFixed(2)} (${improvement > 0 ? '+' : ''}${oldTotalPnL !== 0 ? ((improvement / Math.abs(oldTotalPnL)) * 100).toFixed(1) : 0}%)`) console.log()