#!/usr/bin/env node /** * Backtest: Anti-Chop Filter + Score 65 * * Simulates how the last 30 trades would have performed with: * 1. New anti-chop filter (high volume + low ADX = -15 pts) * 2. Minimum score raised from 60 to 65 */ // Simplified scoring function matching signal-quality.ts function scoreSignalQuality(params) { let score = 50 const reasons = [] const { atr, adx, rsi, volumeRatio, pricePosition, direction, timeframe } = params const is5minChart = timeframe === '5' const is15minChart = timeframe === '15' const isShortTimeframe = is5minChart || is15minChart // ATR check if (atr > 0) { if (atr < 0.15) { score -= 15 reasons.push(`ATR too low (${atr.toFixed(2)}%)`) } else if (atr > 2.5) { score -= 20 reasons.push(`ATR too high (${atr.toFixed(2)}%)`) } else if (atr >= 0.15 && atr < 0.4) { score += 5 reasons.push(`ATR moderate (${atr.toFixed(2)}%)`) } else { score += 10 reasons.push(`ATR healthy (${atr.toFixed(2)}%)`) } } // ADX check - timeframe aware if (adx > 0) { if (isShortTimeframe) { if (adx > 22) { score += 15 reasons.push(`Strong trend for ${timeframe}min (ADX ${adx.toFixed(1)})`) } else if (adx < 12) { score -= 15 reasons.push(`Weak trend for ${timeframe}min (ADX ${adx.toFixed(1)})`) } else { score += 5 reasons.push(`Moderate trend for ${timeframe}min (ADX ${adx.toFixed(1)})`) } } else { if (adx > 25) { score += 15 reasons.push(`Strong trend (ADX ${adx.toFixed(1)})`) } else if (adx < 18) { score -= 15 reasons.push(`Weak trend (ADX ${adx.toFixed(1)})`) } else { score += 5 reasons.push(`Moderate trend (ADX ${adx.toFixed(1)})`) } } } // RSI check if (rsi > 0) { if (direction === 'long') { if (rsi > 50 && rsi < 70) { score += 10 reasons.push(`RSI supports long (${rsi.toFixed(1)})`) } else if (rsi > 70) { score -= 10 reasons.push(`RSI overbought (${rsi.toFixed(1)})`) } } else { if (rsi < 50 && rsi > 30) { score += 10 reasons.push(`RSI supports short (${rsi.toFixed(1)})`) } else if (rsi < 30) { score -= 10 reasons.push(`RSI oversold (${rsi.toFixed(1)})`) } } } // Volume check + ANTI-CHOP FILTER if (volumeRatio > 0) { const isChoppy = adx > 0 && adx < 16 const hasHighVolume = volumeRatio > 1.5 if (isChoppy && hasHighVolume) { // NEW: Anti-chop filter score -= 15 reasons.push(`⚠️ Whipsaw trap: High vol (${volumeRatio.toFixed(2)}x) + choppy (ADX ${adx.toFixed(1)})`) } else if (volumeRatio > 1.5) { score += 15 reasons.push(`Very strong volume (${volumeRatio.toFixed(2)}x)`) } else if (volumeRatio > 1.2) { score += 10 reasons.push(`Strong volume (${volumeRatio.toFixed(2)}x)`) } else if (volumeRatio < 0.8) { score -= 10 reasons.push(`Weak volume (${volumeRatio.toFixed(2)}x)`) } } // Price position check if (pricePosition > 0) { if (direction === 'long' && pricePosition > 95) { if (volumeRatio > 1.4) { score += 5 reasons.push(`Volume breakout at top (${pricePosition.toFixed(0)}%)`) } else { score -= 15 reasons.push(`Chasing highs (${pricePosition.toFixed(0)}%)`) } } else if (direction === 'short' && pricePosition < 5) { if (volumeRatio > 1.4) { score += 5 reasons.push(`Volume breakdown at bottom (${pricePosition.toFixed(0)}%)`) } else { score -= 15 reasons.push(`Chasing lows (${pricePosition.toFixed(0)}%)`) } } else if (pricePosition > 90 && direction === 'long') { score -= 10 reasons.push(`High in range (${pricePosition.toFixed(0)}%)`) } else if (pricePosition < 10 && direction === 'short') { score -= 10 reasons.push(`Low in range (${pricePosition.toFixed(0)}%)`) } } return { score, reasons, passed: score >= 65 } } // Trade data from database query const trades = [ { id: 'cmhsds9390000t107mc7zyizw', symbol: 'SOL-PERP', direction: 'short', realizedPnL: 0, exitReason: 'SL', atr: 0.33, adx: 14.8, rsi: 37.1, volume: 2.29, pricePos: 12, timeframe: '5', oldScore: 80, date: '2025-11-10 00:05' }, { id: 'cmhrui8wh0001lq075axmexal', symbol: 'SOL-PERP', direction: 'long', realizedPnL: 117.63, exitReason: 'TP2', atr: 0.52, adx: 22.7, rsi: 53.1, volume: 0.88, pricePos: 37.9, timeframe: '5', oldScore: 80, date: '2025-11-09 15:05' }, { id: 'cmhrubi3l0000lq07ucg8u4r7', symbol: 'SOL-PERP', direction: 'short', realizedPnL: -5.78, exitReason: 'manual', atr: 0.52, adx: 24, rsi: 48.8, volume: 1.04, pricePos: 24.6, timeframe: '5', oldScore: 80, date: '2025-11-09 15:00' }, { id: 'cmhrlxaai000bp907aax5y2b1', symbol: 'SOL-PERP', direction: 'long', realizedPnL: 152.19, exitReason: 'TP2', atr: 0.3, adx: 26.1, rsi: 61.5, volume: 1.45, pricePos: 74.5, timeframe: '5', oldScore: 95, date: '2025-11-09 11:05' }, { id: 'cmhrjf9ny000ap907wu3n38w5', symbol: 'SOL-PERP', direction: 'short', realizedPnL: 22.50, exitReason: 'SL', atr: 0.28, adx: 19.8, rsi: 38.1, volume: 1.26, pricePos: 3.5, timeframe: '5', oldScore: 65, date: '2025-11-09 09:55' }, { id: 'cmhr8papg0009p907jczfgdxn', symbol: 'SOL-PERP', direction: 'long', realizedPnL: 953.12, exitReason: 'TP2', atr: 0.26, adx: 22.1, rsi: 57.3, volume: 0.43, pricePos: 81.4, timeframe: '5', oldScore: 65, date: '2025-11-09 04:55' }, { id: 'cmhr7zr460008p9078mhtx0v0', symbol: 'SOL-PERP', direction: 'short', realizedPnL: 0, exitReason: 'SL', atr: 0.29, adx: 26.7, rsi: 57, volume: 0.62, pricePos: 78.8, timeframe: '5', oldScore: 65, date: '2025-11-09 04:35' }, { id: 'cmhr7g9hu0007p907ao3unxti', symbol: 'SOL-PERP', direction: 'long', realizedPnL: -3.63, exitReason: 'manual', atr: 0.32, adx: 28, rsi: 62.4, volume: 1.23, pricePos: 88.9, timeframe: '5', oldScore: 95, date: '2025-11-09 04:20' }, { id: 'cmhr3j1fs0006p907lwyoh4ao', symbol: 'SOL-PERP', direction: 'short', realizedPnL: 328.18, exitReason: 'TP2', atr: 0.27, adx: 15.2, rsi: 44.5, volume: 1.26, pricePos: 43.9, timeframe: '5', oldScore: 65, date: '2025-11-09 02:30' }, { id: 'cmhr361xl0005p9078ty96n42', symbol: 'SOL-PERP', direction: 'long', realizedPnL: -7.44, exitReason: 'manual', atr: 0.25, adx: 16, rsi: 54.7, volume: 1.65, pricePos: 76.1, timeframe: '5', oldScore: 70, date: '2025-11-09 02:20' }, { id: 'cmhr2u1vl0004p907jxgss988', symbol: 'SOL-PERP', direction: 'short', realizedPnL: 44.13, exitReason: 'TP1', atr: 0.25, adx: 18.2, rsi: 52.7, volume: 1.25, pricePos: 69.4, timeframe: '5', oldScore: 75, date: '2025-11-09 02:10' }, { id: 'cmhr23s400003p9072o984c6w', symbol: 'SOL-PERP', direction: 'long', realizedPnL: -1.27, exitReason: 'manual', atr: 0.25, adx: 19.9, rsi: 58.2, volume: 2.11, pricePos: 87.7, timeframe: '5', oldScore: 100, date: '2025-11-09 01:50' }, { id: 'cmhqycwhx0002p907rhz4zt4g', symbol: 'SOL-PERP', direction: 'short', realizedPnL: 0, exitReason: 'SL', atr: 0.17, adx: 12.9, rsi: 42.6, volume: 1.7, pricePos: 22.2, timeframe: '5', oldScore: 70, date: '2025-11-09 00:05' }, { id: 'cmhqvnw7t0001p907s5pkyb6w', symbol: 'SOL-PERP', direction: 'long', realizedPnL: -3.13, exitReason: 'manual', atr: 0.18, adx: 9.2, rsi: 56.1, volume: 1.23, pricePos: 72.7, timeframe: '5', oldScore: 65, date: '2025-11-08 22:50' }, { id: 'cmhqhcdwk0000p907zo9hwjwd', symbol: 'SOL-PERP', direction: 'short', realizedPnL: 0, exitReason: 'SL', atr: 0.45, adx: 32, rsi: 42, volume: 1.25, pricePos: 45, timeframe: 'manual', oldScore: 100, date: '2025-11-08 16:09' }, { id: 'cmhqex45o0001qz07xkeahoo8', symbol: 'SOL-PERP', direction: 'short', realizedPnL: 102.71, exitReason: 'TP2', atr: 0.45, adx: 32, rsi: 42, volume: 1.25, pricePos: 45, timeframe: 'manual', oldScore: 100, date: '2025-11-08 15:01' }, { id: 'cmhqd0qgr0000qz075qs97y5s', symbol: 'SOL-PERP', direction: 'short', realizedPnL: 35.83, exitReason: 'TP2', atr: 0.45, adx: 32, rsi: 42, volume: 1.25, pricePos: 45, timeframe: 'manual', oldScore: 100, date: '2025-11-08 14:08' }, { id: 'cmhq3sz5u000crn07akzysxyi', symbol: 'SOL-PERP', direction: 'short', realizedPnL: 0, exitReason: 'SL', atr: 0.26, adx: 15.4, rsi: 47.3, volume: 1.49, pricePos: 52.9, timeframe: '5', oldScore: 85, date: '2025-11-08 09:50' }, { id: 'cmhq2qi2m000brn077sbgop44', symbol: 'SOL-PERP', direction: 'long', realizedPnL: -4.32, exitReason: 'manual', atr: 0.27, adx: 14.3, rsi: 58.9, volume: 1.37, pricePos: 87.6, timeframe: '5', oldScore: 85, date: '2025-11-08 09:20' }, { id: 'cmhpm4bgp000arn0704n40t5q', symbol: 'SOL-PERP', direction: 'short', realizedPnL: -238.87, exitReason: 'SL', atr: 0.38, adx: 19.6, rsi: 47.5, volume: 0.6, pricePos: 60.4, timeframe: '5', oldScore: 70, date: '2025-11-08 01:35' }, { id: 'cmhpllfvq0009rn07mtysadz7', symbol: 'SOL-PERP', direction: 'long', realizedPnL: 0, exitReason: 'SL', atr: 0.41, adx: 23.3, rsi: 51.6, volume: 0.92, pricePos: 79.7, timeframe: '5', oldScore: 90, date: '2025-11-08 01:20' }, { id: 'cmhplexpc0008rn07wkv88y3m', symbol: 'SOL-PERP', direction: 'short', realizedPnL: -6.53, exitReason: 'manual', atr: 0.42, adx: 25, rsi: 47.9, volume: 1.12, pricePos: 59.4, timeframe: '5', oldScore: 90, date: '2025-11-08 01:15' }, { id: 'cmhpl81le0007rn07qj91dm0k', symbol: 'SOL-PERP', direction: 'long', realizedPnL: -2.57, exitReason: 'manual', atr: 0.43, adx: 26.3, rsi: 49, volume: 2.52, pricePos: 65.8, timeframe: '5', oldScore: 105, date: '2025-11-08 01:10' }, { id: 'cmhpkp4o50006rn07wduu7ujp', symbol: 'SOL-PERP', direction: 'short', realizedPnL: 0, exitReason: 'SL', atr: 0.4, adx: 29.9, rsi: 45.2, volume: 1.31, pricePos: 45.4, timeframe: '5', oldScore: 100, date: '2025-11-08 00:55' }, { id: 'cmhpkicqo0005rn073ie5n8h1', symbol: 'SOL-PERP', direction: 'long', realizedPnL: -6.63, exitReason: 'manual', atr: 0.38, adx: 31.9, rsi: 53.3, volume: 1.17, pricePos: 73.1, timeframe: '5', oldScore: 90, date: '2025-11-08 00:50' }, { id: 'cmhpj9jqi0004rn07utncy8d0', symbol: 'SOL-PERP', direction: 'long', realizedPnL: 81.06, exitReason: 'TP1', atr: 0.31, adx: 34.9, rsi: 42, volume: 1.25, pricePos: 31, timeframe: '5', oldScore: 85, date: '2025-11-08 00:15' }, { id: 'cmhpijmv80003rn07ko6ixr6z', symbol: 'SOL-PERP', direction: 'short', realizedPnL: -5.85, exitReason: 'manual', atr: 0.31, adx: 31, rsi: 36.4, volume: 1.08, pricePos: 11.3, timeframe: '5', oldScore: 65, date: '2025-11-07 23:55' }, { id: 'cmhp6y0yi0002rn07qqikioz7', symbol: 'SOL-PERP', direction: 'long', realizedPnL: 0, exitReason: 'SOFT_SL', atr: 0, adx: 0, rsi: 0, volume: 0, pricePos: 0, timeframe: '5', oldScore: 30, date: '2025-11-07 18:30' }, { id: 'cmhp6t54m0001rn07lw4lajdl', symbol: 'SOL-PERP', direction: 'short', realizedPnL: -10.24, exitReason: 'manual', atr: 0.45, adx: 32, rsi: 42, volume: 1.25, pricePos: 45, timeframe: 'manual', oldScore: 100, date: '2025-11-07 18:26' }, { id: 'cmhp6nf4g0000rn07ayhyec3y', symbol: 'SOL-PERP', direction: 'short', realizedPnL: 0, exitReason: 'SL', atr: 0.45, adx: 32, rsi: 42, volume: 1.25, pricePos: 45, timeframe: 'manual', oldScore: 100, date: '2025-11-07 18:22' }, ] console.log('='.repeat(80)) console.log('BACKTEST: Anti-Chop Filter + Score 65 Threshold') console.log('='.repeat(80)) console.log() let totalPnLOld = 0 let totalPnLNew = 0 let tradesBlockedOld = 0 let tradesBlockedNew = 0 let winnersBlockedNew = 0 let losersBlockedNew = 0 let blockedWinPnL = 0 let blockedLossPnL = 0 const results = [] trades.forEach(trade => { // Skip manual trades and trades with missing data if (trade.timeframe === 'manual' || trade.atr === 0) { return } const newScore = scoreSignalQuality({ atr: trade.atr, adx: trade.adx, rsi: trade.rsi, volumeRatio: trade.volume, pricePosition: trade.pricePos, direction: trade.direction, timeframe: trade.timeframe }) const wouldPassOld = trade.oldScore >= 60 const wouldPassNew = newScore.score >= 65 // Old system (score 60) if (wouldPassOld) { totalPnLOld += trade.realizedPnL } else { tradesBlockedOld++ } // New system (score 65 + anti-chop) if (wouldPassNew) { totalPnLNew += trade.realizedPnL } else { tradesBlockedNew++ if (trade.realizedPnL > 0) { winnersBlockedNew++ blockedWinPnL += trade.realizedPnL } else { losersBlockedNew++ blockedLossPnL += trade.realizedPnL } } const statusChange = wouldPassOld !== wouldPassNew ? (wouldPassNew ? '✅ NOW PASSES' : '❌ NOW BLOCKED') : '' results.push({ date: trade.date, direction: trade.direction.toUpperCase(), oldScore: trade.oldScore, newScore: newScore.score, pnl: trade.realizedPnL, exitReason: trade.exitReason, passedOld: wouldPassOld, passedNew: wouldPassNew, statusChange, adx: trade.adx, volume: trade.volume, atr: trade.atr }) }) // Sort by date (newest first) results.reverse() // Print results console.log('Trade Analysis:') console.log('-'.repeat(80)) results.forEach(r => { const pnlStr = r.pnl >= 0 ? `+$${r.pnl.toFixed(2)}` : `-$${Math.abs(r.pnl).toFixed(2)}` const oldPass = r.passedOld ? '✓' : '✗' const newPass = r.passedNew ? '✓' : '✗' console.log(`${r.date} ${r.direction.padEnd(5)} | Old:${oldPass} ${r.oldScore.toString().padStart(3)} | New:${newPass} ${r.newScore.toString().padStart(3)} | ${pnlStr.padStart(10)} ${r.exitReason.padEnd(8)} ${r.statusChange}`) if (r.statusChange.includes('BLOCKED')) { console.log(` └─ ADX ${r.adx.toFixed(1)}, VOL ${r.volume.toFixed(2)}x, ATR ${r.atr.toFixed(2)}%`) } }) console.log() console.log('='.repeat(80)) console.log('SUMMARY') console.log('='.repeat(80)) console.log() console.log('OLD SYSTEM (Score ≥60):') console.log(` Trades taken: ${results.length - tradesBlockedOld}`) console.log(` Trades blocked: ${tradesBlockedOld}`) console.log(` Total P&L: $${totalPnLOld.toFixed(2)}`) console.log() console.log('NEW SYSTEM (Score ≥65 + Anti-Chop):') console.log(` Trades taken: ${results.length - tradesBlockedNew}`) console.log(` Trades blocked: ${tradesBlockedNew}`) console.log(` ├─ Winners blocked: ${winnersBlockedNew} (missed $${blockedWinPnL.toFixed(2)})`) console.log(` └─ Losers blocked: ${losersBlockedNew} (saved $${Math.abs(blockedLossPnL).toFixed(2)})`) console.log(` Total P&L: $${totalPnLNew.toFixed(2)}`) console.log() const improvement = totalPnLNew - totalPnLOld const improvementPct = totalPnLOld !== 0 ? ((improvement / Math.abs(totalPnLOld)) * 100) : 0 console.log('IMPACT:') console.log(` P&L Change: ${improvement >= 0 ? '+' : ''}$${improvement.toFixed(2)} (${improvementPct >= 0 ? '+' : ''}${improvementPct.toFixed(1)}%)`) console.log(` Net effect: ${tradesBlockedNew - tradesBlockedOld} more trades blocked`) console.log(` Blocked impact: -$${blockedWinPnL.toFixed(2)} missed + $${Math.abs(blockedLossPnL).toFixed(2)} saved = ${(blockedLossPnL + blockedWinPnL) >= 0 ? '+' : ''}$${(Math.abs(blockedLossPnL) - blockedWinPnL).toFixed(2)} net`) console.log() if (improvement > 0) { console.log('✅ NEW SYSTEM PERFORMS BETTER') } else if (improvement < 0) { console.log('⚠️ OLD SYSTEM HAD BETTER P&L') } else { console.log('⚖️ SYSTEMS TIED') } console.log()