From d2fbd125a029be73cdbb3f21173a4047a9845924 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Mon, 10 Nov 2025 11:22:52 +0100 Subject: [PATCH] fix: Make minSignalQualityScore configurable via settings + anti-chop improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL BUG FIX: - Settings page saved MIN_SIGNAL_QUALITY_SCORE to .env but check-risk had hardcoded value - Now reads from config.minSignalQualityScore (defaults to 65, editable via /settings) - Prevents settings changes from being ignored after restart ANTI-CHOP FILTER FIXES: - Fixed volume breakout bonus conflicting with anti-chop filter - Volume breakout now requires ADX > 18 (trending market) - Prevents high volume + low ADX from getting rewarded instead of penalized - Anti-chop filter now properly blocks whipsaw traps at score 60 TESTING INFRASTRUCTURE: - Added backtest script showing +17.1% P&L improvement (saved $242 in losses) - Added test-signals.sh for comprehensive signal quality validation - Added test-recent-signals.sh for analyzing actual trading session signals - All tests passing: timeframe awareness, anti-chop, score thresholds CHANGES: - config/trading.ts: Added minSignalQualityScore to interface and defaults - app/api/trading/check-risk/route.ts: Use config value instead of hardcoded 65 - lib/trading/signal-quality.ts: Fixed volume breakout bonus logic - .env: Added MIN_SIGNAL_QUALITY_SCORE=65 - scripts/: Added comprehensive testing tools BACKTEST RESULTS (Last 30 trades): - Old system (score ≥60): $1,412.79 P&L - New system (score ≥65 + anti-chop): $1,654.79 P&L - Improvement: +$242.00 (+17.1%) - Blocked 5 losing trades, missed 0 winners --- .env | 8 +- app/api/trading/check-risk/route.ts | 13 +- config/trading.ts | 11 +- lib/trading/signal-quality.ts | 10 +- scripts/backtest-antichop.mjs | 301 +++++++++++++++++++++++++++ scripts/test-recent-signals.sh | 98 +++++++++ scripts/test-signals.sh | 162 ++++++++++++++ workflows/trading/Money_Machine.json | 2 +- 8 files changed, 594 insertions(+), 11 deletions(-) create mode 100755 scripts/backtest-antichop.mjs create mode 100755 scripts/test-recent-signals.sh create mode 100755 scripts/test-signals.sh diff --git a/.env b/.env index 8391b67..a046707 100644 --- a/.env +++ b/.env @@ -113,14 +113,14 @@ USE_ATR_BASED_TARGETS=true # ATR multiplier for TP2 calculation (TP2 = ATR × this value) # Example: ATR=0.8% × 2.0 = 1.6% TP2 target -ATR_MULTIPLIER_FOR_TP2=2.0 +ATR_MULTIPLIER_FOR_TP2=2 # Minimum TP2 level regardless of ATR (safety floor) MIN_TP2_PERCENT=0.7 # Maximum TP2 level cap (prevents excessive targets) # Example: 3.0% = 30% account gain at 10x leverage -MAX_TP2_PERCENT=3.0 +MAX_TP2_PERCENT=3 # Emergency Stop: Hard stop if this level is breached # Example: -2.0% on 10x = -20% account loss (rare but protects from flash crashes) @@ -367,7 +367,7 @@ NEW_RELIC_LICENSE_KEY= USE_TRAILING_STOP=true TRAILING_STOP_PERCENT=0.3 TRAILING_STOP_ACTIVATION=0.4 -MIN_QUALITY_SCORE=60 +MIN_QUALITY_SCORE=65 SOLANA_ENABLED=true SOLANA_POSITION_SIZE=210 SOLANA_LEVERAGE=10 @@ -383,4 +383,4 @@ MIN_ADX_INCREASE=5 MAX_PRICE_POSITION_FOR_SCALE=70 TRAILING_STOP_ATR_MULTIPLIER=1.5 TRAILING_STOP_MIN_PERCENT=0.25 -TRAILING_STOP_MAX_PERCENT=0.9 \ No newline at end of file +TRAILING_STOP_MAX_PERCENT=0.9MIN_SIGNAL_QUALITY_SCORE=65 diff --git a/app/api/trading/check-risk/route.ts b/app/api/trading/check-risk/route.ts index 8a5b591..6a412c6 100644 --- a/app/api/trading/check-risk/route.ts +++ b/app/api/trading/check-risk/route.ts @@ -264,6 +264,17 @@ export async function POST(request: NextRequest): Promise 0 if (hasContextMetrics) { + console.log('🔍 Risk check for:', { + symbol: body.symbol, + direction: body.direction, + timeframe: body.timeframe, // DEBUG: Check if timeframe is received + atr: body.atr, + adx: body.adx, + rsi: body.rsi, + volumeRatio: body.volumeRatio, + pricePosition: body.pricePosition + }) + const qualityScore = scoreSignalQuality({ atr: body.atr || 0, adx: body.adx || 0, @@ -272,7 +283,7 @@ export async function POST(request: NextRequest): Promise { trailingStopActivation: process.env.TRAILING_STOP_ACTIVATION ? parseFloat(process.env.TRAILING_STOP_ACTIVATION) : undefined, + minSignalQualityScore: process.env.MIN_SIGNAL_QUALITY_SCORE + ? parseInt(process.env.MIN_SIGNAL_QUALITY_SCORE) + : undefined, enablePositionScaling: process.env.ENABLE_POSITION_SCALING ? process.env.ENABLE_POSITION_SCALING === 'true' : undefined, diff --git a/lib/trading/signal-quality.ts b/lib/trading/signal-quality.ts index 2a53544..d2186e6 100644 --- a/lib/trading/signal-quality.ts +++ b/lib/trading/signal-quality.ts @@ -165,13 +165,15 @@ export function scoreSignalQuality(params: { } } - // Volume breakout bonus (high volume can override other weaknesses) - if (params.volumeRatio > 1.8 && params.atr < 0.6) { + // Volume breakout bonus - ONLY if trend is strong enough (not choppy) + // Removed old logic that conflicted with anti-chop filter + // Old bonus was rewarding high volume even during choppy markets + if (params.volumeRatio > 1.8 && params.atr < 0.6 && params.adx > 18) { score += 10 - reasons.push(`Volume breakout compensates for low ATR`) + reasons.push(`Volume breakout compensates for low ATR (ADX ${params.adx.toFixed(1)} confirms trend)`) } - const minScore = params.minScore || 60 + const minScore = params.minScore || 65 const passed = score >= minScore return { diff --git a/scripts/backtest-antichop.mjs b/scripts/backtest-antichop.mjs new file mode 100755 index 0000000..f4fbf8f --- /dev/null +++ b/scripts/backtest-antichop.mjs @@ -0,0 +1,301 @@ +#!/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() diff --git a/scripts/test-recent-signals.sh b/scripts/test-recent-signals.sh new file mode 100755 index 0000000..c70cbbe --- /dev/null +++ b/scripts/test-recent-signals.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +# Test the 4 most recent signals from the chart + +API_URL="http://localhost:3001/api/trading/check-risk" +API_KEY="2a344f0149442c857fb56c038c0c7d1b113883b830bec792c76f1e0efa15d6bb" + +echo "==========================================" +echo "TESTING LAST 4 SIGNALS FROM CHART" +echo "==========================================" +echo "" + +# Signal 1: 10:10:01 - LONG +echo "1. 10:10:01 LONG - ADX:16.1, VOL:1.45" +echo " SOL buy .P 5 | ATR:0.27 | ADX:16.1 | RSI:47.7 | VOL:1.45 | POS:42" +curl -s -X POST "$API_URL" \ + -H "Authorization: Bearer $API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "symbol": "SOL-PERP", + "direction": "long", + "timeframe": "5", + "atr": 0.27, + "adx": 16.1, + "rsi": 47.7, + "volumeRatio": 1.45, + "pricePosition": 42 + }' | jq -r 'if .allowed then " ✅ PASS - Score: \(.qualityScore)/100" else " ❌ BLOCK - Score: \(.qualityScore)/100" end' +echo " Market went UP - LONG would have WON ✓" +echo "" + +# Signal 2: 10:05:00 - SHORT (you canceled this) +echo "2. 10:05:00 SHORT - ADX:16.9, VOL:1.37" +echo " SOL sell .P 5 | ATR:0.26 | ADX:16.9 | RSI:41.6 | VOL:1.37 | POS:24.3" +curl -s -X POST "$API_URL" \ + -H "Authorization: Bearer $API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "symbol": "SOL-PERP", + "direction": "short", + "timeframe": "5", + "atr": 0.26, + "adx": 16.9, + "rsi": 41.6, + "volumeRatio": 1.37, + "pricePosition": 24.3 + }' | jq -r 'if .allowed then " ✅ PASS - Score: \(.qualityScore)/100" else " ❌ BLOCK - Score: \(.qualityScore)/100" end' +echo " Market went UP - SHORT would have LOST ✗" +echo "" + +# Signal 3: 09:35:01 - SHORT +echo "3. 09:35:01 SHORT - ADX:12.8, VOL:1.52" +echo " SOL sell .P 5 | ATR:0.27 | ADX:12.8 | RSI:39.4 | VOL:1.52 | POS:9.8" +curl -s -X POST "$API_URL" \ + -H "Authorization: Bearer $API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "symbol": "SOL-PERP", + "direction": "short", + "timeframe": "5", + "atr": 0.27, + "adx": 12.8, + "rsi": 39.4, + "volumeRatio": 1.52, + "pricePosition": 9.8 + }' | jq -r 'if .allowed then " ✅ PASS - Score: \(.qualityScore)/100" else " ❌ BLOCK - Score: \(.qualityScore)/100" end' +echo " Market went UP - SHORT would have LOST ✗" +echo "" + +# Signal 4: 09:25:00 - LONG +echo "4. 09:25:00 LONG - ADX:16.3, VOL:0.95" +echo " SOL buy .P 5 | ATR:0.26 | ADX:16.3 | RSI:44.1 | VOL:0.95 | POS:28.6" +curl -s -X POST "$API_URL" \ + -H "Authorization: Bearer $API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "symbol": "SOL-PERP", + "direction": "long", + "timeframe": "5", + "atr": 0.26, + "adx": 16.3, + "rsi": 44.1, + "volumeRatio": 0.95, + "pricePosition": 28.6 + }' | jq -r 'if .allowed then " ✅ PASS - Score: \(.qualityScore)/100" else " ❌ BLOCK - Score: \(.qualityScore)/100" end' +echo " Market went UP - LONG would have WON ✓" +echo "" + +echo "==========================================" +echo "SUMMARY" +echo "==========================================" +echo "" +echo "Market Direction: UP (166.85 → 168.22)" +echo "" +echo "Ideal Results:" +echo " • LONG signals should PASS (correct direction)" +echo " • SHORT signals should BLOCK (wrong direction)" +echo "" diff --git a/scripts/test-signals.sh b/scripts/test-signals.sh new file mode 100755 index 0000000..ad86326 --- /dev/null +++ b/scripts/test-signals.sh @@ -0,0 +1,162 @@ +#!/bin/bash + +# Test Script: Verify Anti-Chop Filter & Timeframe Scoring +# Tests the exact signals from today to verify correct behavior + +API_URL="http://localhost:3001/api/trading/check-risk" +API_KEY="2a344f0149442c857fb56c038c0c7d1b113883b830bec792c76f1e0efa15d6bb" + +echo "==========================================" +echo "SIGNAL QUALITY TEST SUITE" +echo "==========================================" +echo "" + +# Test 1: The SHORT that was TAKEN (should pass with ~85 score) +echo "TEST 1: SHORT at 10:05 (SHOULD PASS)" +echo "Signal: SOL sell .P 5 | ATR:0.26 | ADX:16.9 | RSI:41.6 | VOL:1.37 | POS:24.3" +echo "" +curl -X POST "$API_URL" \ + -H "Authorization: Bearer $API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "symbol": "SOL-PERP", + "direction": "short", + "timeframe": "5", + "atr": 0.26, + "adx": 16.9, + "rsi": 41.6, + "volumeRatio": 1.37, + "pricePosition": 24.3 + }' 2>/dev/null | jq -r ' + if .allowed then + "✅ PASSED - Score: \(.qualityScore)/100" + else + "❌ BLOCKED - Score: \(.qualityScore)/100\nReason: \(.reason)\nDetails: \(.details)" + end + ' +echo "" +echo "------------------------------------------" +echo "" + +# Test 2: The LONG that was BLOCKED (should NOW PASS with ~70 score because timeframe aware) +echo "TEST 2: LONG at 10:10 (WAS BLOCKED AT 55, SHOULD NOW PASS AT 70)" +echo "Signal: SOL buy .P 5 | ATR:0.27 | ADX:16.1 | RSI:47.7 | VOL:1.45 | POS:42" +echo "" +curl -X POST "$API_URL" \ + -H "Authorization: Bearer $API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "symbol": "SOL-PERP", + "direction": "long", + "timeframe": "5", + "atr": 0.27, + "adx": 16.1, + "rsi": 47.7, + "volumeRatio": 1.45, + "pricePosition": 42 + }' 2>/dev/null | jq -r ' + if .allowed then + "✅ PASSED - Score: \(.qualityScore)/100 (CORRECT - was wrongly blocked before)" + else + "❌ BLOCKED - Score: \(.qualityScore)/100 (WRONG - should pass!)\nReason: \(.reason)" + end + ' +echo "" +echo "------------------------------------------" +echo "" + +# Test 3: Anti-chop filter - High volume + low ADX (should be blocked) +echo "TEST 3: Anti-Chop Filter Test (SHOULD BLOCK)" +echo "Signal: SOL sell .P 5 | ATR:0.33 | ADX:14.8 | RSI:37.1 | VOL:2.29 | POS:12" +echo "Expected: BLOCKED by anti-chop filter (ADX<16 + VOL>1.5x = whipsaw trap)" +echo "" +curl -X POST "$API_URL" \ + -H "Authorization: Bearer $API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "symbol": "SOL-PERP", + "direction": "short", + "timeframe": "5", + "atr": 0.33, + "adx": 14.8, + "rsi": 37.1, + "volumeRatio": 2.29, + "pricePosition": 12 + }' 2>/dev/null | jq -r ' + if .allowed then + "❌ PASSED - Score: \(.qualityScore)/100 (WRONG - anti-chop should block!)" + else + "✅ BLOCKED - Score: \(.qualityScore)/100 (CORRECT - whipsaw trap detected)" + end + ' +echo "" +echo "------------------------------------------" +echo "" + +# Test 4: Timeframe NOT passed (should treat as daily chart - different thresholds) +echo "TEST 4: Same signal WITHOUT timeframe (daily chart thresholds)" +echo "Signal: SOL buy | ATR:0.27 | ADX:16.1 | RSI:47.7 | VOL:1.45 | POS:42" +echo "Expected: Should be BLOCKED (ADX 16.1 is weak for daily charts)" +echo "" +curl -X POST "$API_URL" \ + -H "Authorization: Bearer $API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "symbol": "SOL-PERP", + "direction": "long", + "atr": 0.27, + "adx": 16.1, + "rsi": 47.7, + "volumeRatio": 1.45, + "pricePosition": 42 + }' 2>/dev/null | jq -r ' + if .allowed then + "❌ PASSED - Score: \(.qualityScore)/100 (WRONG - should block without timeframe)" + else + "✅ BLOCKED - Score: \(.qualityScore)/100 (CORRECT - weak for daily chart)" + end + ' +echo "" +echo "------------------------------------------" +echo "" + +# Test 5: Strong signal (should definitely pass) +echo "TEST 5: Strong Signal (SHOULD PASS)" +echo "Signal: SOL buy .P 5 | ATR:0.52 | ADX:22.7 | RSI:53.1 | VOL:0.88 | POS:37.9" +echo "" +curl -X POST "$API_URL" \ + -H "Authorization: Bearer $API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "symbol": "SOL-PERP", + "direction": "long", + "timeframe": "5", + "atr": 0.52, + "adx": 22.7, + "rsi": 53.1, + "volumeRatio": 0.88, + "pricePosition": 37.9 + }' 2>/dev/null | jq -r ' + if .allowed then + "✅ PASSED - Score: \(.qualityScore)/100 (CORRECT)" + else + "❌ BLOCKED - Score: \(.qualityScore)/100 (WRONG - this is a good signal!)\nReason: \(.reason)" + end + ' +echo "" +echo "------------------------------------------" +echo "" + +echo "==========================================" +echo "TEST SUMMARY" +echo "==========================================" +echo "" +echo "Key things to verify from docker logs:" +echo " 1. '🔍 Risk check for:' shows timeframe field" +echo " 2. ADX scoring says 'Moderate trend for 5min' (not just 'Weak trend')" +echo " 3. Anti-chop filter shows '⚠️ Whipsaw trap' message" +echo " 4. Score threshold is 65 (not 60)" +echo "" +echo "Check logs with:" +echo " docker logs trading-bot-v4 --tail 100 | grep -A 10 'Risk check'" +echo "" diff --git a/workflows/trading/Money_Machine.json b/workflows/trading/Money_Machine.json index ca64bf1..82fb231 100644 --- a/workflows/trading/Money_Machine.json +++ b/workflows/trading/Money_Machine.json @@ -367,7 +367,7 @@ }, { "parameters": { - "jsCode": "// Get the body - it might be a string or nested in an object\nlet body = $json.body || $json.query?.body || JSON.stringify($json);\n\n// If body is an object, stringify it\nif (typeof body === 'object') {\n body = JSON.stringify(body);\n}\n\n// Parse basic signal (existing logic)\nconst symbolMatch = body.match(/\\b(SOL|BTC|ETH)/i);\nconst symbol = symbolMatch ? symbolMatch[1].toUpperCase() + '-PERP' : 'SOL-PERP';\n\nconst direction = body.match(/\\b(sell|short)\\b/i) ? 'short' : 'long';\n\n// Updated regex to match new format: \"ETH buy 15\" (no .P)\nconst timeframeMatch = body.match(/\\b(buy|sell)\\s+(\\d+|D|W|M)\\b/i);\nconst timeframe = timeframeMatch ? timeframeMatch[2] : '5';\n\n// Parse new context metrics from enhanced format:\n// \"ETH buy 15 | ATR:1.85 | ADX:28.3 | RSI:62.5 | VOL:1.45 | POS:75.3\"\nconst atrMatch = body.match(/ATR:([\\d.]+)/);\nconst atr = atrMatch ? parseFloat(atrMatch[1]) : 0;\n\nconst adxMatch = body.match(/ADX:([\\d.]+)/);\nconst adx = adxMatch ? parseFloat(adxMatch[1]) : 0;\n\nconst rsiMatch = body.match(/RSI:([\\d.]+)/);\nconst rsi = rsiMatch ? parseFloat(rsiMatch[1]) : 0;\n\nconst volumeMatch = body.match(/VOL:([\\d.]+)/);\nconst volumeRatio = volumeMatch ? parseFloat(volumeMatch[1]) : 0;\n\nconst pricePositionMatch = body.match(/POS:([\\d.]+)/);\nconst pricePosition = pricePositionMatch ? parseFloat(pricePositionMatch[1]) : 0;\n\nreturn {\n rawMessage: body,\n symbol,\n direction,\n timeframe,\n // New context fields\n atr,\n adx,\n rsi,\n volumeRatio,\n pricePosition\n};" + "jsCode": "// Get the body - it might be a string or nested in an object\nlet body = $json.body || $json.query?.body || JSON.stringify($json);\n\n// If body is an object, stringify it\nif (typeof body === 'object') {\n body = JSON.stringify(body);\n}\n\n// Parse basic signal (existing logic)\nconst symbolMatch = body.match(/\\b(SOL|BTC|ETH)/i);\nconst symbol = symbolMatch ? symbolMatch[1].toUpperCase() + '-PERP' : 'SOL-PERP';\n\nconst direction = body.match(/\\b(sell|short)\\b/i) ? 'short' : 'long';\n\n// Updated regex to match formats: \"ETH buy 15\", \"SOL buy .P 5\", etc.\n// The .P is optional TradingView ticker suffix that should be ignored\nconst timeframeMatch = body.match(/\\b(buy|sell)\\s+(?:\\.P\\s+)?(\\d+|D|W|M)\\b/i);\nconst timeframe = timeframeMatch ? timeframeMatch[2] : '5';\n\n// Parse new context metrics from enhanced format:\n// \"ETH buy 15 | ATR:1.85 | ADX:28.3 | RSI:62.5 | VOL:1.45 | POS:75.3\"\nconst atrMatch = body.match(/ATR:([\\d.]+)/);\nconst atr = atrMatch ? parseFloat(atrMatch[1]) : 0;\n\nconst adxMatch = body.match(/ADX:([\\d.]+)/);\nconst adx = adxMatch ? parseFloat(adxMatch[1]) : 0;\n\nconst rsiMatch = body.match(/RSI:([\\d.]+)/);\nconst rsi = rsiMatch ? parseFloat(rsiMatch[1]) : 0;\n\nconst volumeMatch = body.match(/VOL:([\\d.]+)/);\nconst volumeRatio = volumeMatch ? parseFloat(volumeMatch[1]) : 0;\n\nconst pricePositionMatch = body.match(/POS:([\\d.]+)/);\nconst pricePosition = pricePositionMatch ? parseFloat(pricePositionMatch[1]) : 0;\n\nreturn {\n rawMessage: body,\n symbol,\n direction,\n timeframe,\n // New context fields\n atr,\n adx,\n rsi,\n volumeRatio,\n pricePosition\n};" }, "id": "81f28bc7-c96a-4021-acac-242e993d9d98", "name": "Parse Signal Enhanced",