diff --git a/lib/trading/signal-quality.ts b/lib/trading/signal-quality.ts index 296fcf9..5d80fdc 100644 --- a/lib/trading/signal-quality.ts +++ b/lib/trading/signal-quality.ts @@ -124,24 +124,22 @@ export async function scoreSignalQuality(params: { reasons.push(`RSI overbought (${params.rsi.toFixed(1)})`) } } else { // short + // v10 (Nov 26, 2025): RSI is NOT a good SHORT filter! + // Data analysis of 95 SHORTs showed: + // - Winning SHORTs: avg RSI 45.8 (HIGHER than losers!) + // - Losing SHORTs: avg RSI 42.9 + // - RSI 50+: 68.2% WR, +$29.88 profit (BEST performance) + // RSI alone is counterintuitive for shorts - focus on ADX + price position instead + + // Give small bonus for typical short RSI range, but don't penalize high RSI if (params.rsi < 50 && params.rsi > 30) { - score += 10 + score += 5 reasons.push(`RSI supports short (${params.rsi.toFixed(1)})`) } else if (params.rsi < 30) { score -= 10 - reasons.push(`RSI oversold (${params.rsi.toFixed(1)})`) - } - - // CRITICAL (Nov 25, 2025): Data-driven SHORT filter - // Analysis of 14 SHORT signals showed: - // - All 4 disasters had RSI < 33 (avg RSI 28.3, lost -$665.70) - // - All 2 winners had RSI >= 33 (RSI 33.9, 66.3, won +$59.37) - // - RSI < 33 = shorting into oversold = catching falling knives - // This penalty drops quality below 80 threshold, blocking the signal - if (params.rsi < 33) { - score -= 25 - reasons.push(`🚨 SHORT oversold trap: RSI ${params.rsi.toFixed(1)} < 33 → BLOCKING (-25 pts)`) + reasons.push(`RSI oversold - bounce risk (${params.rsi.toFixed(1)})`) } + // No penalty for RSI 50+ - data shows these are actually the best shorts! } } @@ -199,6 +197,33 @@ export async function scoreSignalQuality(params: { reasons.push(`Volume breakout compensates for low ATR`) } + // v10 (Nov 26, 2025): SHORT-SPECIFIC MOMENTUM FILTER + // Data analysis of 95 SHORTs revealed the REAL pattern: + // - Winners: ADX 26.9, Price Position 37.8-64.2% (shorting at top!) + // - Losers: ADX 20-25, Price Position 13.6-38.4% (shorting in chop at bottom!) + // - Today's disaster: ADX 20.7, Price Pos 13.6% = both failed filters + // + // The winning formula: Catch shorts at the TOP with strong trend confirmation + if (params.direction === 'short') { + const hasStrongTrend = params.adx >= 23 + const atTopOfRange = params.pricePosition >= 60 + const atBottomWithVolume = params.pricePosition <= 40 && params.volumeRatio >= 2.0 + + if (!hasStrongTrend) { + score -= 30 + reasons.push(`🚨 v10: SHORT in weak trend (ADX ${params.adx.toFixed(1)} < 23) → HIGH CHOP RISK (-30 pts)`) + } else if (!atTopOfRange && !atBottomWithVolume) { + score -= 25 + reasons.push(`🚨 v10: SHORT in mid-range (${params.pricePosition.toFixed(0)}%) without setup → TRAP ZONE (-25 pts)`) + } else if (atTopOfRange) { + score += 15 + reasons.push(`✅ v10: SHORT at top of range (${params.pricePosition.toFixed(0)}%) with ADX ${params.adx.toFixed(1)} → IDEAL SETUP (+15 pts)`) + } else if (atBottomWithVolume) { + score += 10 + reasons.push(`✅ v10: SHORT capitulation bounce (${params.pricePosition.toFixed(0)}%, vol ${params.volumeRatio.toFixed(2)}x) → VALID (+10 pts)`) + } + } + // Signal frequency penalties (check database for recent signals) const frequencyPenalties = { overtrading: 0,