From 0365560c5b63512157a2d3f2a4fa4257dbd0d24b Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Fri, 7 Nov 2025 08:56:19 +0100 Subject: [PATCH] Add timeframe-aware signal quality scoring for 5min charts - Lower ADX/ATR thresholds for 5min timeframe (ADX 12-22, ATR 0.2-0.7%) - Add anti-chop filter: -20 points for extreme sideways (ADX<10, ATR<0.25, Vol<0.9) - Pass timeframe parameter through check-risk and execute endpoints - Fixes flip-flop losses from overly strict 5min filters - Higher timeframes unchanged (still use ADX 18+, ATR 0.4+) 5min scoring now: - ADX 12-15: moderate trend (+5) - ADX 22+: strong trend (+15) - ATR 0.2-0.35: acceptable (+5) - ATR 0.35+: healthy (+10) - Extreme chop penalty prevents whipsaw trades --- app/api/trading/check-risk/route.ts | 5 +- app/api/trading/execute/route.ts | 2 + lib/trading/signal-quality.ts | 93 ++++++++++++++++++++++------- 3 files changed, 76 insertions(+), 24 deletions(-) diff --git a/app/api/trading/check-risk/route.ts b/app/api/trading/check-risk/route.ts index 036f3af..bb25f02 100644 --- a/app/api/trading/check-risk/route.ts +++ b/app/api/trading/check-risk/route.ts @@ -15,6 +15,7 @@ import { scoreSignalQuality, SignalQualityResult } from '@/lib/trading/signal-qu export interface RiskCheckRequest { symbol: string direction: 'long' | 'short' + timeframe?: string // e.g., '5', '15', '60', '1D' // Optional context metrics from TradingView atr?: number adx?: number @@ -57,6 +58,7 @@ function shouldAllowScaling( pricePosition: newSignal.pricePosition, direction: newSignal.direction, minScore: config.minScaleQualityScore, + timeframe: newSignal.timeframe, }) // 2. Check quality score (higher bar than initial entry) @@ -271,7 +273,8 @@ export async function POST(request: NextRequest): Promise 0) { - if (params.atr < 0.15) { - score -= 15 - reasons.push(`ATR too low (${params.atr.toFixed(2)}% - dead market)`) - } else if (params.atr > 2.5) { - score -= 20 - reasons.push(`ATR too high (${params.atr.toFixed(2)}% - too volatile)`) - } else if (params.atr >= 0.15 && params.atr < 0.4) { - score += 5 - reasons.push(`ATR moderate (${params.atr.toFixed(2)}%)`) + if (is5min) { + // 5min: lower thresholds, more lenient + if (params.atr < 0.2) { + score -= 15 + reasons.push(`ATR too low (${params.atr.toFixed(2)}% - dead market)`) + } else if (params.atr > 1.5) { + score -= 20 + reasons.push(`ATR too high (${params.atr.toFixed(2)}% - too volatile)`) + } else if (params.atr >= 0.2 && params.atr < 0.35) { + score += 5 + reasons.push(`ATR acceptable (${params.atr.toFixed(2)}%)`) + } else { + score += 10 + reasons.push(`ATR healthy (${params.atr.toFixed(2)}%)`) + } } else { - score += 10 - reasons.push(`ATR healthy (${params.atr.toFixed(2)}%)`) + // Higher timeframes: stricter requirements + if (params.atr < 0.15) { + score -= 15 + reasons.push(`ATR too low (${params.atr.toFixed(2)}% - dead market)`) + } else if (params.atr > 2.5) { + score -= 20 + reasons.push(`ATR too high (${params.atr.toFixed(2)}% - too volatile)`) + } else if (params.atr >= 0.15 && params.atr < 0.4) { + score += 5 + reasons.push(`ATR moderate (${params.atr.toFixed(2)}%)`) + } else { + score += 10 + reasons.push(`ATR healthy (${params.atr.toFixed(2)}%)`) + } } } - // ADX check (trend strength: want >18) + // ADX check - TIMEFRAME AWARE if (params.adx > 0) { - if (params.adx > 25) { - score += 15 - reasons.push(`Strong trend (ADX ${params.adx.toFixed(1)})`) - } else if (params.adx < 18) { - score -= 15 - reasons.push(`Weak trend (ADX ${params.adx.toFixed(1)})`) + if (is5min) { + // 5min: ADX 15+ is actually trending, 20+ is strong + if (params.adx > 22) { + score += 15 + reasons.push(`Strong 5min trend (ADX ${params.adx.toFixed(1)})`) + } else if (params.adx < 12) { + score -= 15 + reasons.push(`Weak 5min trend (ADX ${params.adx.toFixed(1)})`) + } else { + score += 5 + reasons.push(`Moderate 5min trend (ADX ${params.adx.toFixed(1)})`) + } } else { - score += 5 - reasons.push(`Moderate trend (ADX ${params.adx.toFixed(1)})`) + // Higher timeframes: stricter ADX requirements + if (params.adx > 25) { + score += 15 + reasons.push(`Strong trend (ADX ${params.adx.toFixed(1)})`) + } else if (params.adx < 18) { + score -= 15 + reasons.push(`Weak trend (ADX ${params.adx.toFixed(1)})`) + } else { + score += 5 + reasons.push(`Moderate trend (ADX ${params.adx.toFixed(1)})`) + } } } @@ -135,6 +176,12 @@ export function scoreSignalQuality(params: { score += 10 reasons.push(`Volume breakout compensates for low ATR`) } + + // ANTI-CHOP FILTER for 5min (extreme penalty for sideways chop) + if (is5min && params.adx < 10 && params.atr < 0.25 && params.volumeRatio < 0.9) { + score -= 20 + reasons.push(`⛔ Extreme chop detected (ADX ${params.adx.toFixed(1)}, ATR ${params.atr.toFixed(2)}%, Vol ${params.volumeRatio.toFixed(2)}x)`) + } const minScore = params.minScore || 60 const passed = score >= minScore