feat: Add production logging gating (Phase 1, Task 1.1)

- Created logger utility with environment-based gating (lib/utils/logger.ts)
- Replaced 517 console.log statements with logger.log (71% reduction)
- Fixed import paths in 15 files (resolved comment-trapped imports)
- Added DEBUG_LOGS=false to .env
- Achieves 71% immediate log reduction (517/731 statements)
- Expected 90% reduction in production when deployed

Impact: Reduced I/O blocking, lower log volume in production
Risk: LOW (easy rollback, non-invasive)
Phase: Phase 1, Task 1.1 (Quick Wins - Console.log Production Gating)

Files changed:
- NEW: lib/utils/logger.ts (production-safe logging)
- NEW: scripts/replace-console-logs.js (automation tool)
- Modified: 15 lib/*.ts files (console.log → logger.log)
- Modified: .env (DEBUG_LOGS=false)

Next: Task 1.2 (Image Size Optimization)
This commit is contained in:
mindesbunister
2025-12-05 00:32:41 +01:00
parent cc3a0a85a0
commit 302511293c
20 changed files with 2223 additions and 518 deletions

View File

@@ -1,3 +1,5 @@
import { logger } from '../utils/logger'
/**
* Market Data Cache Service
*
@@ -29,7 +31,7 @@ class MarketDataCache {
*/
set(symbol: string, metrics: MarketMetrics): void {
this.cache.set(symbol, metrics)
console.log(
logger.log(
`📊 Cached market data for ${symbol}: ` +
`ADX=${metrics.adx.toFixed(1)} ` +
`ATR=${metrics.atr.toFixed(2)}% ` +
@@ -46,18 +48,18 @@ class MarketDataCache {
const data = this.cache.get(symbol)
if (!data) {
console.log(`⚠️ No cached data for ${symbol}`)
logger.log(`⚠️ No cached data for ${symbol}`)
return null
}
const ageSeconds = Math.round((Date.now() - data.timestamp) / 1000)
if (Date.now() - data.timestamp > this.MAX_AGE_MS) {
console.log(`⏰ Cached data for ${symbol} is stale (${ageSeconds}s old, max 300s)`)
logger.log(`⏰ Cached data for ${symbol} is stale (${ageSeconds}s old, max 300s)`)
return null
}
console.log(`✅ Using fresh TradingView data for ${symbol} (${ageSeconds}s old)`)
logger.log(`✅ Using fresh TradingView data for ${symbol} (${ageSeconds}s old)`)
return data
}
@@ -102,7 +104,7 @@ class MarketDataCache {
*/
clear(): void {
this.cache.clear()
console.log('🗑️ Market data cache cleared')
logger.log('🗑️ Market data cache cleared')
}
}
@@ -112,7 +114,7 @@ let marketDataCache: MarketDataCache | null = null
export function getMarketDataCache(): MarketDataCache {
if (!marketDataCache) {
marketDataCache = new MarketDataCache()
console.log('🔧 Initialized Market Data Cache (5min expiry)')
logger.log('🔧 Initialized Market Data Cache (5min expiry)')
}
return marketDataCache
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@
*/
import { getRecentSignals } from '../database/trades'
import { logger } from '../utils/logger'
export interface SignalQualityResult {
score: number
@@ -235,7 +236,7 @@ export async function scoreSignalQuality(params: {
(params.currentPrice - recentSignals.oppositeDirectionPrice) / recentSignals.oppositeDirectionPrice * 100
)
console.log(`🔍 Flip-flop price check: $${recentSignals.oppositeDirectionPrice.toFixed(2)}$${params.currentPrice.toFixed(2)} = ${priceChangePercent.toFixed(2)}%`)
logger.log(`🔍 Flip-flop price check: $${recentSignals.oppositeDirectionPrice.toFixed(2)}$${params.currentPrice.toFixed(2)} = ${priceChangePercent.toFixed(2)}%`)
if (priceChangePercent < 2.0) {
// Small price move = consolidation/chop = BAD

View File

@@ -14,6 +14,7 @@
*/
import { getMarketDataCache } from './market-data-cache'
import { logger } from '../utils/logger'
import { getPythPriceMonitor } from '../pyth/price-monitor'
export interface QueuedSignal {
@@ -85,7 +86,7 @@ export class SmartEntryTimer {
monitorIntervalMs: 15000 // 15 seconds
}
console.log('💡 Smart Entry Timer initialized:', {
logger.log('💡 Smart Entry Timer initialized:', {
enabled: this.config.enabled,
maxWait: `${this.config.maxWaitMs / 1000}s`,
pullback: `${this.config.pullbackMin}-${this.config.pullbackMax}%`,
@@ -137,10 +138,10 @@ export class SmartEntryTimer {
this.queuedSignals.set(signal.id, signal)
console.log(`📥 Smart Entry: Queued signal ${signal.id}`)
console.log(` ${signal.direction.toUpperCase()} ${signal.symbol} @ $${signal.signalPrice.toFixed(2)}`)
console.log(` Target pullback: ${this.config.pullbackMin}-${this.config.pullbackMax}%`)
console.log(` Max wait: ${this.config.maxWaitMs / 1000}s`)
logger.log(`📥 Smart Entry: Queued signal ${signal.id}`)
logger.log(` ${signal.direction.toUpperCase()} ${signal.symbol} @ $${signal.signalPrice.toFixed(2)}`)
logger.log(` Target pullback: ${this.config.pullbackMin}-${this.config.pullbackMax}%`)
logger.log(` Max wait: ${this.config.maxWaitMs / 1000}s`)
// Start monitoring if not already running
if (!this.monitoringInterval) {
@@ -156,7 +157,7 @@ export class SmartEntryTimer {
private startMonitoring(): void {
if (this.monitoringInterval) return
console.log(`👁️ Smart Entry: Starting monitoring loop (${this.config.monitorIntervalMs / 1000}s interval)`)
logger.log(`👁️ Smart Entry: Starting monitoring loop (${this.config.monitorIntervalMs / 1000}s interval)`)
this.monitoringInterval = setInterval(() => {
this.checkAllSignals()
@@ -170,7 +171,7 @@ export class SmartEntryTimer {
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval)
this.monitoringInterval = null
console.log(`⏸️ Smart Entry: Monitoring stopped (no active signals)`)
logger.log(`⏸️ Smart Entry: Monitoring stopped (no active signals)`)
}
}
@@ -187,7 +188,7 @@ export class SmartEntryTimer {
// Check for timeout
if (now >= signal.expiresAt) {
console.log(`⏰ Smart Entry: Timeout for ${signal.symbol} (waited ${this.config.maxWaitMs / 1000}s)`)
logger.log(`⏰ Smart Entry: Timeout for ${signal.symbol} (waited ${this.config.maxWaitMs / 1000}s)`)
const priceMonitor = getPythPriceMonitor()
const latestPrice = priceMonitor.getCachedPrice(signal.symbol)
const currentPrice = latestPrice?.price || signal.signalPrice
@@ -210,7 +211,7 @@ export class SmartEntryTimer {
const latestPrice = priceMonitor.getCachedPrice(signal.symbol)
if (!latestPrice || !latestPrice.price) {
console.log(`⚠️ Smart Entry: No price available for ${signal.symbol}, skipping check`)
logger.log(`⚠️ Smart Entry: No price available for ${signal.symbol}, skipping check`)
return
}
@@ -234,18 +235,18 @@ export class SmartEntryTimer {
}
// Log check
console.log(`🔍 Smart Entry: Checking ${signal.symbol} (check #${signal.checksPerformed})`)
console.log(` Signal: $${signal.signalPrice.toFixed(2)} → Current: $${currentPrice.toFixed(2)}`)
console.log(` Pullback: ${pullbackMagnitude.toFixed(2)}% (target ${signal.targetPullbackMin}-${signal.targetPullbackMax}%)`)
logger.log(`🔍 Smart Entry: Checking ${signal.symbol} (check #${signal.checksPerformed})`)
logger.log(` Signal: $${signal.signalPrice.toFixed(2)} → Current: $${currentPrice.toFixed(2)}`)
logger.log(` Pullback: ${pullbackMagnitude.toFixed(2)}% (target ${signal.targetPullbackMin}-${signal.targetPullbackMax}%)`)
// Check if pullback is in target range
if (pullbackMagnitude < signal.targetPullbackMin) {
console.log(` ⏳ Waiting for pullback (${pullbackMagnitude.toFixed(2)}% < ${signal.targetPullbackMin}%)`)
logger.log(` ⏳ Waiting for pullback (${pullbackMagnitude.toFixed(2)}% < ${signal.targetPullbackMin}%)`)
return
}
if (pullbackMagnitude > signal.targetPullbackMax) {
console.log(` ⚠️ Pullback too large (${pullbackMagnitude.toFixed(2)}% > ${signal.targetPullbackMax}%), might be reversal - waiting`)
logger.log(` ⚠️ Pullback too large (${pullbackMagnitude.toFixed(2)}% > ${signal.targetPullbackMax}%), might be reversal - waiting`)
return
}
@@ -262,22 +263,22 @@ export class SmartEntryTimer {
const now = Date.now()
const dataAge = (now - latestMetrics.timestamp) / 1000
console.log(` 📊 Real-time validation (data age: ${dataAge.toFixed(0)}s):`)
logger.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})`)
logger.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`)
logger.log(` 🚫 Signal cancelled: ADX degradation exceeded tolerance`)
return
}
console.log(` ✅ ADX: ${signal.signalADX.toFixed(1)}${latestMetrics.adx.toFixed(1)} (within tolerance)`)
logger.log(` ✅ ADX: ${signal.signalADX.toFixed(1)}${latestMetrics.adx.toFixed(1)} (within tolerance)`)
}
// 2. Volume degradation check (NEW)
@@ -289,15 +290,15 @@ export class SmartEntryTimer {
// Cancel if volume dropped >40%
if (volumeDrop > 40) {
console.log(` ❌ Volume collapsed: ${originalVolume.toFixed(2)}x → ${currentVolume.toFixed(2)}x (${volumeDrop.toFixed(0)}% drop)`)
logger.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`)
logger.log(` 🚫 Signal cancelled: Volume degradation - momentum fading`)
return
}
console.log(` ✅ Volume: ${originalVolume.toFixed(2)}x → ${currentVolume.toFixed(2)}x`)
logger.log(` ✅ Volume: ${originalVolume.toFixed(2)}x → ${currentVolume.toFixed(2)}x`)
}
// 3. RSI reversal check (NEW)
@@ -309,26 +310,26 @@ export class SmartEntryTimer {
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)`)
logger.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`)
logger.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)`)
logger.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`)
logger.log(` 🚫 Signal cancelled: RSI reversal - trend weakening`)
return
}
}
console.log(` ✅ RSI: ${originalRSI.toFixed(1)}${currentRSI.toFixed(1)}`)
logger.log(` ✅ RSI: ${originalRSI.toFixed(1)}${currentRSI.toFixed(1)}`)
}
// 4. MAGAP divergence check (NEW)
@@ -338,36 +339,36 @@ export class SmartEntryTimer {
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)`)
logger.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`)
logger.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)`)
logger.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`)
logger.log(` 🚫 Signal cancelled: MA structure turned bullish`)
return
}
console.log(` ✅ MAGAP: ${currentMAGap.toFixed(2)}%`)
logger.log(` ✅ MAGAP: ${currentMAGap.toFixed(2)}%`)
}
console.log(` ✅ All real-time validations passed - signal quality maintained`)
logger.log(` ✅ All real-time validations passed - signal quality maintained`)
} else {
console.log(` ⚠️ No fresh market data available - proceeding with original signal`)
logger.log(` ⚠️ No fresh market data available - proceeding with original signal`)
}
// All conditions met - execute!
console.log(`✅ Smart Entry: OPTIMAL ENTRY CONFIRMED`)
console.log(` Pullback: ${pullbackMagnitude.toFixed(2)}% (target ${signal.targetPullbackMin}-${signal.targetPullbackMax}%)`)
console.log(` Price improvement: $${signal.signalPrice.toFixed(2)}$${currentPrice.toFixed(2)}`)
logger.log(`✅ Smart Entry: OPTIMAL ENTRY CONFIRMED`)
logger.log(` Pullback: ${pullbackMagnitude.toFixed(2)}% (target ${signal.targetPullbackMin}-${signal.targetPullbackMax}%)`)
logger.log(` Price improvement: $${signal.signalPrice.toFixed(2)}$${currentPrice.toFixed(2)}`)
await this.executeSignal(signal, currentPrice, 'pullback_confirmed')
}
@@ -388,10 +389,10 @@ export class SmartEntryTimer {
const improvement = ((signal.signalPrice - entryPrice) / signal.signalPrice) * 100
const improvementDirection = signal.direction === 'long' ? improvement : -improvement
console.log(`🎯 Smart Entry: EXECUTING ${signal.direction.toUpperCase()} ${signal.symbol}`)
console.log(` Signal Price: $${signal.signalPrice.toFixed(2)}`)
console.log(` Entry Price: $${entryPrice.toFixed(2)}`)
console.log(` Improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%`)
logger.log(`🎯 Smart Entry: EXECUTING ${signal.direction.toUpperCase()} ${signal.symbol}`)
logger.log(` Signal Price: $${signal.signalPrice.toFixed(2)}`)
logger.log(` Entry Price: $${entryPrice.toFixed(2)}`)
logger.log(` Improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%`)
// Execute the actual trade through Drift
try {
@@ -423,7 +424,7 @@ export class SmartEntryTimer {
signal.qualityScore
)
console.log(` Opening position: $${positionSizeUSD.toFixed(2)} at ${leverage}x leverage`)
logger.log(` Opening position: $${positionSizeUSD.toFixed(2)} at ${leverage}x leverage`)
// Open position
const openResult = await openPosition({
@@ -439,7 +440,7 @@ export class SmartEntryTimer {
}
const fillPrice = openResult.fillPrice!
console.log(`✅ Smart Entry: Position opened at $${fillPrice.toFixed(2)}`)
logger.log(`✅ Smart Entry: Position opened at $${fillPrice.toFixed(2)}`)
// Calculate TP/SL prices
let tp1Percent = config.takeProfit1Percent
@@ -505,7 +506,7 @@ export class SmartEntryTimer {
if (exitRes.success) {
exitOrderSignatures = exitRes.signatures || []
console.log(`✅ Smart Entry: Exit orders placed - ${exitOrderSignatures.length} orders`)
logger.log(`✅ Smart Entry: Exit orders placed - ${exitOrderSignatures.length} orders`)
}
} catch (err) {
console.error(`❌ Smart Entry: Error placing exit orders:`, err)
@@ -551,7 +552,7 @@ export class SmartEntryTimer {
}
})
console.log(`💾 Smart Entry: Trade saved to database`)
logger.log(`💾 Smart Entry: Trade saved to database`)
} catch (dbError) {
console.error(`❌ Smart Entry: Failed to save trade:`, dbError)
const { logCriticalError } = await import('../utils/persistent-logger')
@@ -616,14 +617,14 @@ export class SmartEntryTimer {
}
await positionManager.addTrade(activeTrade)
console.log(`📊 Smart Entry: Added to Position Manager`)
logger.log(`📊 Smart Entry: Added to Position Manager`)
} catch (pmError) {
console.error(`❌ Smart Entry: Failed to add to Position Manager:`, pmError)
}
console.log(`✅ Smart Entry: Execution complete for ${signal.symbol}`)
console.log(` Entry improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%`)
console.log(` Estimated value: $${(Math.abs(improvementDirection) / 100 * positionSizeUSD).toFixed(2)}`)
logger.log(`✅ Smart Entry: Execution complete for ${signal.symbol}`)
logger.log(` Entry improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%`)
logger.log(` Estimated value: $${(Math.abs(improvementDirection) / 100 * positionSizeUSD).toFixed(2)}`)
} catch (error) {
console.error(`❌ Smart Entry: Execution error:`, error)
@@ -632,7 +633,7 @@ export class SmartEntryTimer {
// Remove from queue after brief delay (for logging)
setTimeout(() => {
this.queuedSignals.delete(signal.id)
console.log(`🗑️ Smart Entry: Cleaned up signal ${signal.id}`)
logger.log(`🗑️ Smart Entry: Cleaned up signal ${signal.id}`)
if (this.queuedSignals.size === 0) {
this.stopMonitoring()
@@ -688,7 +689,7 @@ export class SmartEntryTimer {
signal.status = 'cancelled'
this.queuedSignals.delete(signalId)
console.log(`🚫 Smart Entry: Cancelled signal ${signalId}`)
logger.log(`🚫 Smart Entry: Cancelled signal ${signalId}`)
return true
}
@@ -713,5 +714,5 @@ export function getSmartEntryTimer(): SmartEntryTimer {
export function startSmartEntryTracking(): void {
getSmartEntryTimer()
console.log('✅ Smart Entry Timer service initialized')
logger.log('✅ Smart Entry Timer service initialized')
}

View File

@@ -12,6 +12,7 @@
*/
import { getMarketDataCache } from './market-data-cache'
import { logger } from '../utils/logger'
import { getMergedConfig } from '../../config/trading'
import { sendValidationNotification } from '../notifications/telegram'
@@ -49,7 +50,7 @@ class SmartValidationQueue {
private isMonitoring = false
constructor() {
console.log('🧠 Smart Validation Queue initialized')
logger.log('🧠 Smart Validation Queue initialized')
}
/**
@@ -110,8 +111,8 @@ class SmartValidationQueue {
}
this.queue.set(signalId, queuedSignal)
console.log(`⏰ Smart validation queued: ${params.symbol} ${params.direction.toUpperCase()} @ $${params.originalPrice.toFixed(2)} (quality: ${params.qualityScore})`)
console.log(` Watching for ${queuedSignal.entryWindowMinutes}min: +${queuedSignal.confirmationThreshold}% confirms, ${queuedSignal.maxDrawdown}% abandons`)
logger.log(`⏰ Smart validation queued: ${params.symbol} ${params.direction.toUpperCase()} @ $${params.originalPrice.toFixed(2)} (quality: ${params.qualityScore})`)
logger.log(` Watching for ${queuedSignal.entryWindowMinutes}min: +${queuedSignal.confirmationThreshold}% confirms, ${queuedSignal.maxDrawdown}% abandons`)
// Send Telegram notification
await sendValidationNotification({
@@ -139,7 +140,7 @@ class SmartValidationQueue {
}
this.isMonitoring = true
console.log('👁️ Smart validation monitoring started (checks every 30s)')
logger.log('👁️ Smart validation monitoring started (checks every 30s)')
// Check every 30 seconds
this.monitoringInterval = setInterval(async () => {
@@ -156,7 +157,7 @@ class SmartValidationQueue {
this.monitoringInterval = undefined
}
this.isMonitoring = false
console.log('⏸️ Smart validation monitoring stopped')
logger.log('⏸️ Smart validation monitoring stopped')
}
/**
@@ -171,7 +172,7 @@ class SmartValidationQueue {
return
}
console.log(`👁️ Smart validation check: ${pending.length} pending signals`)
logger.log(`👁️ Smart validation check: ${pending.length} pending signals`)
for (const signal of pending) {
try {
@@ -195,7 +196,7 @@ class SmartValidationQueue {
// Check if expired (beyond entry window)
if (ageMinutes > signal.entryWindowMinutes) {
signal.status = 'expired'
console.log(`⏰ Signal expired: ${signal.symbol} ${signal.direction} (${ageMinutes.toFixed(1)}min old)`)
logger.log(`⏰ Signal expired: ${signal.symbol} ${signal.direction} (${ageMinutes.toFixed(1)}min old)`)
// Send Telegram notification
await sendValidationNotification({
@@ -215,7 +216,7 @@ class SmartValidationQueue {
const cachedData = marketDataCache.get(signal.symbol)
if (!cachedData || !cachedData.currentPrice) {
console.log(`⚠️ No price data for ${signal.symbol}, skipping validation`)
logger.log(`⚠️ No price data for ${signal.symbol}, skipping validation`)
return
}
@@ -237,8 +238,8 @@ class SmartValidationQueue {
// Price moved up enough - CONFIRMED!
signal.status = 'confirmed'
signal.validatedAt = now
console.log(`✅ LONG CONFIRMED: ${signal.symbol} moved +${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)}$${currentPrice.toFixed(2)})`)
console.log(` Validation time: ${ageMinutes.toFixed(1)} minutes, executing trade...`)
logger.log(`✅ LONG CONFIRMED: ${signal.symbol} moved +${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)}$${currentPrice.toFixed(2)})`)
logger.log(` Validation time: ${ageMinutes.toFixed(1)} minutes, executing trade...`)
// Send Telegram notification
await sendValidationNotification({
@@ -257,8 +258,8 @@ class SmartValidationQueue {
} else if (priceChange <= signal.maxDrawdown) {
// Price moved down too much - ABANDON
signal.status = 'abandoned'
console.log(`❌ LONG ABANDONED: ${signal.symbol} dropped ${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)}$${currentPrice.toFixed(2)})`)
console.log(` Saved from potential loser after ${ageMinutes.toFixed(1)} minutes`)
logger.log(`❌ LONG ABANDONED: ${signal.symbol} dropped ${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)}$${currentPrice.toFixed(2)})`)
logger.log(` Saved from potential loser after ${ageMinutes.toFixed(1)} minutes`)
// Send Telegram notification
await sendValidationNotification({
@@ -273,7 +274,7 @@ class SmartValidationQueue {
})
} else {
// Still pending, log progress
console.log(`⏳ LONG watching: ${signal.symbol} at ${priceChange.toFixed(2)}% (need +${signal.confirmationThreshold}%, abandon at ${signal.maxDrawdown}%) - ${ageMinutes.toFixed(1)}min`)
logger.log(`⏳ LONG watching: ${signal.symbol} at ${priceChange.toFixed(2)}% (need +${signal.confirmationThreshold}%, abandon at ${signal.maxDrawdown}%) - ${ageMinutes.toFixed(1)}min`)
}
} else {
// SHORT: Need price to move DOWN to confirm
@@ -281,8 +282,8 @@ class SmartValidationQueue {
// Price moved down enough - CONFIRMED!
signal.status = 'confirmed'
signal.validatedAt = now
console.log(`✅ SHORT CONFIRMED: ${signal.symbol} moved ${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)}$${currentPrice.toFixed(2)})`)
console.log(` Validation time: ${ageMinutes.toFixed(1)} minutes, executing trade...`)
logger.log(`✅ SHORT CONFIRMED: ${signal.symbol} moved ${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)}$${currentPrice.toFixed(2)})`)
logger.log(` Validation time: ${ageMinutes.toFixed(1)} minutes, executing trade...`)
// Send Telegram notification
await sendValidationNotification({
@@ -301,8 +302,8 @@ class SmartValidationQueue {
} else if (priceChange >= -signal.maxDrawdown) {
// Price moved up too much - ABANDON
signal.status = 'abandoned'
console.log(`❌ SHORT ABANDONED: ${signal.symbol} rose +${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)}$${currentPrice.toFixed(2)})`)
console.log(` Saved from potential loser after ${ageMinutes.toFixed(1)} minutes`)
logger.log(`❌ SHORT ABANDONED: ${signal.symbol} rose +${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)}$${currentPrice.toFixed(2)})`)
logger.log(` Saved from potential loser after ${ageMinutes.toFixed(1)} minutes`)
// Send Telegram notification
await sendValidationNotification({
@@ -317,7 +318,7 @@ class SmartValidationQueue {
})
} else {
// Still pending, log progress
console.log(`⏳ SHORT watching: ${signal.symbol} at ${priceChange.toFixed(2)}% (need ${-signal.confirmationThreshold}%, abandon at +${-signal.maxDrawdown}%) - ${ageMinutes.toFixed(1)}min`)
logger.log(`⏳ SHORT watching: ${signal.symbol} at ${priceChange.toFixed(2)}% (need ${-signal.confirmationThreshold}%, abandon at +${-signal.maxDrawdown}%) - ${ageMinutes.toFixed(1)}min`)
}
}
}
@@ -347,9 +348,9 @@ class SmartValidationQueue {
validationDelayMinutes: (Date.now() - signal.blockedAt) / (1000 * 60),
}
console.log(`🚀 Executing validated trade: ${signal.symbol} ${signal.direction.toUpperCase()} @ $${currentPrice.toFixed(2)}`)
console.log(` Original signal: $${signal.originalPrice.toFixed(2)}, Quality: ${signal.qualityScore}`)
console.log(` Entry delay: ${payload.validationDelayMinutes.toFixed(1)} minutes`)
logger.log(`🚀 Executing validated trade: ${signal.symbol} ${signal.direction.toUpperCase()} @ $${currentPrice.toFixed(2)}`)
logger.log(` Original signal: $${signal.originalPrice.toFixed(2)}, Quality: ${signal.qualityScore}`)
logger.log(` Entry delay: ${payload.validationDelayMinutes.toFixed(1)} minutes`)
const response = await fetch(executeUrl, {
method: 'POST',
@@ -367,9 +368,9 @@ class SmartValidationQueue {
signal.executedAt = Date.now()
signal.executionPrice = currentPrice
signal.tradeId = result.trade?.id
console.log(`✅ Trade executed successfully: ${signal.symbol} ${signal.direction}`)
console.log(` Trade ID: ${signal.tradeId}`)
console.log(` Entry: $${currentPrice.toFixed(2)}, Size: $${result.trade?.positionSizeUSD || 'unknown'}`)
logger.log(`✅ Trade executed successfully: ${signal.symbol} ${signal.direction}`)
logger.log(` Trade ID: ${signal.tradeId}`)
logger.log(` Entry: $${currentPrice.toFixed(2)}, Size: $${result.trade?.positionSizeUSD || 'unknown'}`)
// Send execution notification
const slippage = ((currentPrice - signal.originalPrice) / signal.originalPrice) * 100
@@ -408,7 +409,7 @@ class SmartValidationQueue {
}
if (cleaned > 0) {
console.log(`🧹 Cleaned up ${cleaned} old validated signals`)
logger.log(`🧹 Cleaned up ${cleaned} old validated signals`)
}
}
@@ -454,5 +455,5 @@ export function getSmartValidationQueue(): SmartValidationQueue {
export function startSmartValidation(): void {
const queue = getSmartValidationQueue()
console.log('🧠 Smart validation system ready')
logger.log('🧠 Smart validation system ready')
}

View File

@@ -14,6 +14,7 @@
*/
import { getPrismaClient } from '../database/trades'
import { logger } from '../utils/logger'
import { initializeDriftService } from '../drift/client'
import { getPythPriceMonitor } from '../pyth/price-monitor'
@@ -70,7 +71,7 @@ export class StopHuntTracker {
}): Promise<void> {
// Only track quality 85+ stop-outs (high-confidence trades)
if (params.originalQualityScore < 85) {
console.log(`⚠️ Stop hunt not tracked: Quality ${params.originalQualityScore} < 85 threshold`)
logger.log(`⚠️ Stop hunt not tracked: Quality ${params.originalQualityScore} < 85 threshold`)
return
}
@@ -93,9 +94,9 @@ export class StopHuntTracker {
}
})
console.log(`🎯 STOP HUNT RECORDED: ${params.symbol} ${params.direction.toUpperCase()}`)
console.log(` Quality: ${params.originalQualityScore}, Loss: $${params.stopLossAmount.toFixed(2)}`)
console.log(` Revenge window: 4 hours (expires ${revengeExpiresAt.toLocaleTimeString()})`)
logger.log(`🎯 STOP HUNT RECORDED: ${params.symbol} ${params.direction.toUpperCase()}`)
logger.log(` Quality: ${params.originalQualityScore}, Loss: $${params.stopLossAmount.toFixed(2)}`)
logger.log(` Revenge window: 4 hours (expires ${revengeExpiresAt.toLocaleTimeString()})`)
// Start monitoring if not already running
if (!this.isMonitoring) {
@@ -113,7 +114,7 @@ export class StopHuntTracker {
if (this.isMonitoring) return
this.isMonitoring = true
console.log('🔍 Stop Hunt Revenge Tracker: Monitoring started')
logger.log('🔍 Stop Hunt Revenge Tracker: Monitoring started')
// Check every 30 seconds
monitoringInterval = setInterval(async () => {
@@ -130,7 +131,7 @@ export class StopHuntTracker {
monitoringInterval = null
}
this.isMonitoring = false
console.log('🛑 Stop Hunt Revenge Tracker: Monitoring stopped')
logger.log('🛑 Stop Hunt Revenge Tracker: Monitoring stopped')
}
/**
@@ -152,13 +153,13 @@ export class StopHuntTracker {
if (activeStopHunts.length === 0) {
// No active stop hunts, stop monitoring to save resources
if (this.isMonitoring) {
console.log('📊 No active stop hunts - pausing monitoring')
logger.log('📊 No active stop hunts - pausing monitoring')
this.stopMonitoring()
}
return
}
console.log(`🔍 Checking ${activeStopHunts.length} active stop hunt(s)...`)
logger.log(`🔍 Checking ${activeStopHunts.length} active stop hunt(s)...`)
for (const stopHunt of activeStopHunts) {
await this.checkStopHunt(stopHunt as StopHuntRecord)
@@ -211,7 +212,7 @@ export class StopHuntTracker {
const shouldRevenge = await this.shouldExecuteRevenge(stopHunt, currentPrice)
if (shouldRevenge) {
console.log(`🔥 REVENGE CONDITIONS MET: ${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}`)
logger.log(`🔥 REVENGE CONDITIONS MET: ${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}`)
await this.executeRevengeTrade(stopHunt, currentPrice)
}
@@ -260,7 +261,7 @@ export class StopHuntTracker {
lowestInZone: currentPrice,
}
})
console.log(` ⏱️ LONG revenge zone entered at ${currentPrice.toFixed(2)}, waiting for 90s confirmation...`)
logger.log(` ⏱️ LONG revenge zone entered at ${currentPrice.toFixed(2)}, waiting for 90s confirmation...`)
return false
}
@@ -274,17 +275,17 @@ export class StopHuntTracker {
// Check if we've been in zone for 90+ seconds (1.5 minutes)
const timeInZone = now - stopHunt.firstCrossTime.getTime()
if (timeInZone >= 90000) { // 90 seconds = 1.5 minutes
console.log(` ✅ LONG revenge: Price held below entry for ${(timeInZone/60000).toFixed(1)}min, confirmed!`)
console.log(` Entry ${originalEntryPrice.toFixed(2)} → Current ${currentPrice.toFixed(2)}`)
logger.log(` ✅ LONG revenge: Price held below entry for ${(timeInZone/60000).toFixed(1)}min, confirmed!`)
logger.log(` Entry ${originalEntryPrice.toFixed(2)} → Current ${currentPrice.toFixed(2)}`)
return true
} else {
console.log(` ⏱️ LONG revenge: ${(timeInZone/60000).toFixed(1)}min in zone (need 1.5min)`)
logger.log(` ⏱️ LONG revenge: ${(timeInZone/60000).toFixed(1)}min in zone (need 1.5min)`)
return false
}
} else {
// Price left revenge zone - reset timer and increment counter
if (stopHunt.firstCrossTime) {
console.log(` ❌ LONG revenge: Price bounced back up to ${currentPrice.toFixed(2)}, resetting timer`)
logger.log(` ❌ LONG revenge: Price bounced back up to ${currentPrice.toFixed(2)}, resetting timer`)
await this.prisma.stopHunt.update({
where: { id: stopHunt.id },
data: {
@@ -310,7 +311,7 @@ export class StopHuntTracker {
highestInZone: currentPrice,
}
})
console.log(` ⏱️ SHORT revenge zone entered at ${currentPrice.toFixed(2)}, waiting for 90s confirmation...`)
logger.log(` ⏱️ SHORT revenge zone entered at ${currentPrice.toFixed(2)}, waiting for 90s confirmation...`)
return false
}
@@ -324,17 +325,17 @@ export class StopHuntTracker {
// Check if we've been in zone for 90+ seconds (1.5 minutes)
const timeInZone = now - stopHunt.firstCrossTime.getTime()
if (timeInZone >= 90000) { // 90 seconds = 1.5 minutes
console.log(` ✅ SHORT revenge: Price held above entry for ${(timeInZone/60000).toFixed(1)}min, confirmed!`)
console.log(` Entry ${originalEntryPrice.toFixed(2)} → Current ${currentPrice.toFixed(2)}`)
logger.log(` ✅ SHORT revenge: Price held above entry for ${(timeInZone/60000).toFixed(1)}min, confirmed!`)
logger.log(` Entry ${originalEntryPrice.toFixed(2)} → Current ${currentPrice.toFixed(2)}`)
return true
} else {
console.log(` ⏱️ SHORT revenge: ${(timeInZone/60000).toFixed(1)}min in zone (need 1.5min)`)
logger.log(` ⏱️ SHORT revenge: ${(timeInZone/60000).toFixed(1)}min in zone (need 1.5min)`)
return false
}
} else {
// Price left revenge zone - reset timer and increment counter
if (stopHunt.firstCrossTime) {
console.log(` ❌ SHORT revenge: Price dropped back to ${currentPrice.toFixed(2)}, resetting timer`)
logger.log(` ❌ SHORT revenge: Price dropped back to ${currentPrice.toFixed(2)}, resetting timer`)
await this.prisma.stopHunt.update({
where: { id: stopHunt.id },
data: {
@@ -354,9 +355,9 @@ export class StopHuntTracker {
*/
private async executeRevengeTrade(stopHunt: StopHuntRecord, currentPrice: number): Promise<void> {
try {
console.log(`🔥 EXECUTING REVENGE TRADE: ${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}`)
console.log(` Original loss: $${stopHunt.stopLossAmount.toFixed(2)}`)
console.log(` Revenge size: 1.2x (getting our money back!)`)
logger.log(`🔥 EXECUTING REVENGE TRADE: ${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}`)
logger.log(` Original loss: $${stopHunt.stopLossAmount.toFixed(2)}`)
logger.log(` Revenge size: 1.2x (getting our money back!)`)
// CRITICAL: Validate current ADX from 1-minute data cache
// Block revenge if trend has weakened (ADX < 20)
@@ -368,10 +369,10 @@ export class StopHuntTracker {
const currentADX = cachedData.adx
const dataAge = Date.now() - cachedData.timestamp
console.log(` 📊 Fresh ADX check: ${currentADX.toFixed(1)} (${(dataAge/1000).toFixed(0)}s old)`)
logger.log(` 📊 Fresh ADX check: ${currentADX.toFixed(1)} (${(dataAge/1000).toFixed(0)}s old)`)
if (currentADX < 20) {
console.log(` ❌ REVENGE BLOCKED: ADX ${currentADX.toFixed(1)} < 20 (weak trend, not worth re-entry)`)
logger.log(` ❌ REVENGE BLOCKED: ADX ${currentADX.toFixed(1)} < 20 (weak trend, not worth re-entry)`)
// Update database with failed reason
await this.prisma.stopHunt.update({
@@ -397,10 +398,10 @@ export class StopHuntTracker {
return
}
console.log(` ✅ ADX validation passed: ${currentADX.toFixed(1)} ≥ 20 (strong trend)`)
logger.log(` ✅ ADX validation passed: ${currentADX.toFixed(1)} ≥ 20 (strong trend)`)
} else {
console.log(` ⚠️ No fresh ADX data (cache age: ${cachedData ? (Date.now() - cachedData.timestamp)/1000 : 'N/A'}s)`)
console.log(` ⚠️ Proceeding with revenge but using original ADX ${stopHunt.originalADX}`)
logger.log(` ⚠️ No fresh ADX data (cache age: ${cachedData ? (Date.now() - cachedData.timestamp)/1000 : 'N/A'}s)`)
logger.log(` ⚠️ Proceeding with revenge but using original ADX ${stopHunt.originalADX}`)
}
// Call execute endpoint with revenge parameters
@@ -454,9 +455,9 @@ export class StopHuntTracker {
}
})
console.log(`✅ REVENGE TRADE EXECUTED: ${result.trade?.id}`)
console.log(`📊 SL Distance: $${Math.abs(slDistance).toFixed(2)} (${stopHunt.originalATR ? `${(Math.abs(slDistance) / stopHunt.originalATR).toFixed(2)}× ATR` : 'no ATR'})`)
console.log(`🔥 LET'S GET OUR MONEY BACK!`)
logger.log(`✅ REVENGE TRADE EXECUTED: ${result.trade?.id}`)
logger.log(`📊 SL Distance: $${Math.abs(slDistance).toFixed(2)} (${stopHunt.originalATR ? `${(Math.abs(slDistance) / stopHunt.originalATR).toFixed(2)}× ATR` : 'no ATR'})`)
logger.log(`🔥 LET'S GET OUR MONEY BACK!`)
// Send special Telegram notification
await this.sendRevengeNotification(stopHunt, result.trade)
@@ -524,7 +525,7 @@ Reversal Confirmed: Price crossed back through entry
})
if (!stopHunt) {
console.log(`⚠️ No stop hunt found for revenge trade ${params.revengeTradeId}`)
logger.log(`⚠️ No stop hunt found for revenge trade ${params.revengeTradeId}`)
return
}
@@ -538,10 +539,10 @@ Reversal Confirmed: Price crossed back through entry
})
const emoji = params.outcome.includes('TP') ? '✅' : '❌'
console.log(`${emoji} REVENGE OUTCOME: ${params.outcome} (${params.pnl >= 0 ? '+' : ''}$${params.pnl.toFixed(2)})`)
logger.log(`${emoji} REVENGE OUTCOME: ${params.outcome} (${params.pnl >= 0 ? '+' : ''}$${params.pnl.toFixed(2)})`)
if (params.failedReason) {
console.log(` Reason: ${params.failedReason}`)
logger.log(` Reason: ${params.failedReason}`)
}
} catch (error) {
@@ -568,7 +569,7 @@ Reversal Confirmed: Price crossed back through entry
})
if (expired.count > 0) {
console.log(`⏰ Expired ${expired.count} stop hunt revenge window(s)`)
logger.log(`⏰ Expired ${expired.count} stop hunt revenge window(s)`)
}
} catch (error) {
console.error('❌ Error expiring stop hunts:', error)
@@ -605,10 +606,10 @@ export async function startStopHuntTracking(): Promise<void> {
})
if (activeCount > 0) {
console.log(`🎯 Found ${activeCount} active stop hunt(s) - starting revenge tracker`)
logger.log(`🎯 Found ${activeCount} active stop hunt(s) - starting revenge tracker`)
tracker.startMonitoring()
} else {
console.log('📊 No active stop hunts - tracker will start when needed')
logger.log('📊 No active stop hunts - tracker will start when needed')
}
} catch (error) {
console.error('❌ Error starting stop hunt tracker:', error)