diff --git a/lib/trading/market-data-cache.ts b/lib/trading/market-data-cache.ts index 134f0fc..6c7554b 100644 --- a/lib/trading/market-data-cache.ts +++ b/lib/trading/market-data-cache.ts @@ -14,6 +14,7 @@ export interface MarketMetrics { rsi: number // Relative Strength Index (momentum) volumeRatio: number // Current volume / average volume pricePosition: number // Position in recent range (0-100%) + maGap?: number // MA50-MA200 gap percentage (v9+) currentPrice: number // Latest close price timestamp: number // Unix timestamp (ms) timeframe: string // "5" for 5min, "60" for 1h, etc. diff --git a/lib/trading/smart-entry-timer.ts b/lib/trading/smart-entry-timer.ts index 3c25e14..558f444 100644 --- a/lib/trading/smart-entry-timer.ts +++ b/lib/trading/smart-entry-timer.ts @@ -249,23 +249,119 @@ export class SmartEntryTimer { return } - // Validate ADX hasn't degraded + // ============================================================ + // PHASE 7.2: REAL-TIME QUALITY VALIDATION (Nov 27, 2025) + // ============================================================ + // Re-validate signal quality before entry using fresh market data + // Prevents execution if conditions degraded during wait period + const marketCache = getMarketDataCache() const latestMetrics = marketCache.get(signal.symbol) - if (latestMetrics && latestMetrics.adx) { - const adxDrop = signal.signalADX - latestMetrics.adx + if (latestMetrics) { + const now = Date.now() + const dataAge = (now - latestMetrics.timestamp) / 1000 - if (adxDrop > signal.adxTolerance) { - console.log(` ❌ ADX degraded: ${signal.signalADX.toFixed(1)} → ${latestMetrics.adx.toFixed(1)} (dropped ${adxDrop.toFixed(1)} points, max ${signal.adxTolerance})`) - signal.status = 'cancelled' - signal.executionReason = 'manual_override' - this.queuedSignals.delete(signal.id) - console.log(` 🚫 Signal cancelled: ADX degradation exceeded tolerance`) - return + console.log(` 📊 Real-time validation (data age: ${dataAge.toFixed(0)}s):`) + + // 1. ADX degradation check (original logic) + if (latestMetrics.adx) { + const adxDrop = signal.signalADX - latestMetrics.adx + + if (adxDrop > signal.adxTolerance) { + console.log(` ❌ ADX degraded: ${signal.signalADX.toFixed(1)} → ${latestMetrics.adx.toFixed(1)} (dropped ${adxDrop.toFixed(1)} points, max ${signal.adxTolerance})`) + signal.status = 'cancelled' + signal.executionReason = 'manual_override' + this.queuedSignals.delete(signal.id) + console.log(` 🚫 Signal cancelled: ADX degradation exceeded tolerance`) + return + } + + console.log(` ✅ ADX: ${signal.signalADX.toFixed(1)} → ${latestMetrics.adx.toFixed(1)} (within tolerance)`) } - console.log(` ✅ ADX validation: ${signal.signalADX.toFixed(1)} → ${latestMetrics.adx.toFixed(1)} (within tolerance)`) + // 2. Volume degradation check (NEW) + // If volume drops significantly, momentum may be fading + if (signal.originalSignalData.volumeRatio && latestMetrics.volumeRatio) { + const originalVolume = signal.originalSignalData.volumeRatio + const currentVolume = latestMetrics.volumeRatio + const volumeDrop = ((originalVolume - currentVolume) / originalVolume) * 100 + + // Cancel if volume dropped >40% + if (volumeDrop > 40) { + console.log(` ❌ Volume collapsed: ${originalVolume.toFixed(2)}x → ${currentVolume.toFixed(2)}x (${volumeDrop.toFixed(0)}% drop)`) + signal.status = 'cancelled' + signal.executionReason = 'manual_override' + this.queuedSignals.delete(signal.id) + console.log(` 🚫 Signal cancelled: Volume degradation - momentum fading`) + return + } + + console.log(` ✅ Volume: ${originalVolume.toFixed(2)}x → ${currentVolume.toFixed(2)}x`) + } + + // 3. RSI reversal check (NEW) + // If RSI crossed into opposite territory, trend may be reversing + if (signal.originalSignalData.rsi && latestMetrics.rsi) { + const originalRSI = signal.originalSignalData.rsi + const currentRSI = latestMetrics.rsi + + if (signal.direction === 'long') { + // LONG: Cancel if RSI dropped into oversold (<30) + if (originalRSI >= 40 && currentRSI < 30) { + console.log(` ❌ RSI collapsed: ${originalRSI.toFixed(1)} → ${currentRSI.toFixed(1)} (now oversold)`) + signal.status = 'cancelled' + signal.executionReason = 'manual_override' + this.queuedSignals.delete(signal.id) + console.log(` 🚫 Signal cancelled: RSI reversal - trend weakening`) + return + } + } else { + // SHORT: Cancel if RSI rose into overbought (>70) + if (originalRSI <= 60 && currentRSI > 70) { + console.log(` ❌ RSI spiked: ${originalRSI.toFixed(1)} → ${currentRSI.toFixed(1)} (now overbought)`) + signal.status = 'cancelled' + signal.executionReason = 'manual_override' + this.queuedSignals.delete(signal.id) + console.log(` 🚫 Signal cancelled: RSI reversal - trend weakening`) + return + } + } + + console.log(` ✅ RSI: ${originalRSI.toFixed(1)} → ${currentRSI.toFixed(1)}`) + } + + // 4. MAGAP divergence check (NEW) + // If MA gap widened in opposite direction, structure changing + if (latestMetrics.maGap !== undefined) { + const currentMAGap = latestMetrics.maGap + + if (signal.direction === 'long' && currentMAGap < -1.0) { + // LONG but MAs now bearish diverging + console.log(` ❌ MA structure bearish: MAGAP ${currentMAGap.toFixed(2)}% (death cross accelerating)`) + signal.status = 'cancelled' + signal.executionReason = 'manual_override' + this.queuedSignals.delete(signal.id) + console.log(` 🚫 Signal cancelled: MA structure turned bearish`) + return + } + + if (signal.direction === 'short' && currentMAGap > 1.0) { + // SHORT but MAs now bullish diverging + console.log(` ❌ MA structure bullish: MAGAP ${currentMAGap.toFixed(2)}% (golden cross accelerating)`) + signal.status = 'cancelled' + signal.executionReason = 'manual_override' + this.queuedSignals.delete(signal.id) + console.log(` 🚫 Signal cancelled: MA structure turned bullish`) + return + } + + console.log(` ✅ MAGAP: ${currentMAGap.toFixed(2)}%`) + } + + console.log(` ✅ All real-time validations passed - signal quality maintained`) + } else { + console.log(` ⚠️ No fresh market data available - proceeding with original signal`) } // All conditions met - execute!