diff --git a/lib/trading/signal-quality.ts b/lib/trading/signal-quality.ts index c32cc02..f790beb 100644 --- a/lib/trading/signal-quality.ts +++ b/lib/trading/signal-quality.ts @@ -85,24 +85,41 @@ export function scoreSignalQuality(params: { if (params.adx > 0) { if (is5min) { // 5min: ADX 15+ is actually trending, 20+ is strong + // High volume can compensate for lower ADX in breakouts/breakdowns + const hasStrongVolume = params.volumeRatio > 1.2 + 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)})`) + // Reduce penalty if strong volume present (breakdown/breakout in progress) + if (hasStrongVolume) { + score -= 5 + reasons.push(`Lower 5min ADX (${params.adx.toFixed(1)}) but strong volume compensates`) + } else { + 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 { // Higher timeframes: stricter ADX requirements + const hasStrongVolume = params.volumeRatio > 1.2 + 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)})`) + // Reduce penalty if strong volume present + if (hasStrongVolume) { + score -= 5 + reasons.push(`Lower ADX (${params.adx.toFixed(1)}) but strong volume compensates`) + } else { + score -= 15 + reasons.push(`Weak trend (ADX ${params.adx.toFixed(1)})`) + } } else { score += 5 reasons.push(`Moderate trend (ADX ${params.adx.toFixed(1)})`) @@ -145,7 +162,7 @@ export function scoreSignalQuality(params: { } } - // Price position check (avoid chasing vs breakout detection) + // Price position check (avoid chasing vs breakout/breakdown detection) if (params.pricePosition > 0) { if (params.direction === 'long' && params.pricePosition > 95) { // High volume breakout at range top can be good @@ -158,12 +175,22 @@ export function scoreSignalQuality(params: { } } else if (params.direction === 'short' && params.pricePosition < 5) { // High volume breakdown at range bottom can be good - if (params.volumeRatio > 1.4) { + // Also allow if RSI is bearish (< 40) - breakdowns continue lower + if (params.volumeRatio > 1.2 || (params.rsi > 0 && params.rsi < 40)) { score += 5 - reasons.push(`Volume breakdown at range bottom (${params.pricePosition.toFixed(0)}%, vol ${params.volumeRatio.toFixed(2)}x)`) + reasons.push(`Valid breakdown at range bottom (${params.pricePosition.toFixed(0)}%, vol ${params.volumeRatio.toFixed(2)}x, RSI ${params.rsi.toFixed(1)})`) } else { - score -= 15 - reasons.push(`Price near bottom of range (${params.pricePosition.toFixed(0)}%) - risky short`) + score -= 10 + reasons.push(`Price near bottom of range (${params.pricePosition.toFixed(0)}%) - reduced penalty for short`) + } + } else if (params.direction === 'long' && params.pricePosition < 5) { + // Longs at bottom with good volume = potential reversal + if (params.volumeRatio > 1.2 || (params.rsi > 0 && params.rsi > 60)) { + score += 5 + reasons.push(`Potential reversal at bottom (${params.pricePosition.toFixed(0)}%, vol ${params.volumeRatio.toFixed(2)}x, RSI ${params.rsi.toFixed(1)})`) + } else { + score -= 10 + reasons.push(`Price near bottom (${params.pricePosition.toFixed(0)}%) - reduced penalty for reversal long`) } } else { score += 5