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

4
.env
View File

@@ -452,4 +452,6 @@ WITHDRAWAL_PROFIT_PERCENT=10
MIN_WITHDRAWAL_AMOUNT=5 MIN_WITHDRAWAL_AMOUNT=5
MIN_ACCOUNT_BALANCE=500 MIN_ACCOUNT_BALANCE=500
LAST_WITHDRAWAL_TIME=2025-11-19T19:34:47.185Z LAST_WITHDRAWAL_TIME=2025-11-19T19:34:47.185Z
TOTAL_WITHDRAWN=12.50 TOTAL_WITHDRAWN=12.50
# Production Logging Configuration (Phase 1 Optimization - Dec 5, 2025)
DEBUG_LOGS=false

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,7 @@
*/ */
import { getPrismaClient } from '../database/trades' import { getPrismaClient } from '../database/trades'
import { logger } from '../utils/logger'
import { initializeDriftService } from '../drift/client' import { initializeDriftService } from '../drift/client'
import { getMergedConfig, SUPPORTED_MARKETS } from '../../config/trading' import { getMergedConfig, SUPPORTED_MARKETS } from '../../config/trading'
@@ -60,11 +61,11 @@ export class BlockedSignalTracker {
*/ */
public start(): void { public start(): void {
if (this.isRunning) { if (this.isRunning) {
console.log('⚠️ Blocked signal tracker already running') logger.log('⚠️ Blocked signal tracker already running')
return return
} }
console.log('🔬 Starting blocked signal price tracker...') logger.log('🔬 Starting blocked signal price tracker...')
this.isRunning = true this.isRunning = true
// Run immediately on start // Run immediately on start
@@ -79,7 +80,7 @@ export class BlockedSignalTracker {
}) })
}, 5 * 60 * 1000) // 5 minutes }, 5 * 60 * 1000) // 5 minutes
console.log('✅ Blocked signal tracker started (runs every 5 minutes)') logger.log('✅ Blocked signal tracker started (runs every 5 minutes)')
} }
/** /**
@@ -91,7 +92,7 @@ export class BlockedSignalTracker {
this.intervalId = null this.intervalId = null
} }
this.isRunning = false this.isRunning = false
console.log('⏹️ Blocked signal tracker stopped') logger.log('⏹️ Blocked signal tracker stopped')
} }
/** /**
@@ -102,7 +103,7 @@ export class BlockedSignalTracker {
// Initialize Drift service if needed // Initialize Drift service if needed
const driftService = await initializeDriftService() const driftService = await initializeDriftService()
if (!driftService) { if (!driftService) {
console.log('⚠️ Drift service not available, skipping price tracking') logger.log('⚠️ Drift service not available, skipping price tracking')
return return
} }
@@ -122,17 +123,17 @@ export class BlockedSignalTracker {
}) })
if (signals.length === 0) { if (signals.length === 0) {
console.log('📊 No blocked signals to track') logger.log('📊 No blocked signals to track')
return return
} }
console.log(`📊 Tracking ${signals.length} blocked signals...`) logger.log(`📊 Tracking ${signals.length} blocked signals...`)
for (const signal of signals) { for (const signal of signals) {
await this.trackSignal(signal as any) await this.trackSignal(signal as any)
} }
console.log(`✅ Price tracking complete for ${signals.length} signals`) logger.log(`✅ Price tracking complete for ${signals.length} signals`)
} catch (error) { } catch (error) {
console.error('❌ Error in trackPrices:', error) console.error('❌ Error in trackPrices:', error)
} }
@@ -152,7 +153,7 @@ export class BlockedSignalTracker {
const marketConfig = SUPPORTED_MARKETS[signal.symbol] const marketConfig = SUPPORTED_MARKETS[signal.symbol]
if (!marketConfig) { if (!marketConfig) {
console.log(`⚠️ No market config for ${signal.symbol}, skipping`) logger.log(`⚠️ No market config for ${signal.symbol}, skipping`)
return return
} }
@@ -160,7 +161,7 @@ export class BlockedSignalTracker {
const entryPrice = Number(signal.entryPrice) const entryPrice = Number(signal.entryPrice)
if (entryPrice === 0) { if (entryPrice === 0) {
console.log(`⚠️ Entry price is 0 for ${signal.symbol}, skipping`) logger.log(`⚠️ Entry price is 0 for ${signal.symbol}, skipping`)
return return
} }
@@ -184,56 +185,56 @@ export class BlockedSignalTracker {
if (elapsedMinutes >= 1 && !signal.priceAfter1Min) { if (elapsedMinutes >= 1 && !signal.priceAfter1Min) {
updates.priceAfter1Min = currentPrice updates.priceAfter1Min = currentPrice
console.log(` 📍 ${signal.symbol} ${signal.direction} @ 1min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) logger.log(` 📍 ${signal.symbol} ${signal.direction} @ 1min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`)
} }
if (elapsedMinutes >= 5 && !signal.priceAfter5Min) { if (elapsedMinutes >= 5 && !signal.priceAfter5Min) {
updates.priceAfter5Min = currentPrice updates.priceAfter5Min = currentPrice
console.log(` 📍 ${signal.symbol} ${signal.direction} @ 5min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) logger.log(` 📍 ${signal.symbol} ${signal.direction} @ 5min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`)
} }
if (elapsedMinutes >= 15 && !signal.priceAfter15Min) { if (elapsedMinutes >= 15 && !signal.priceAfter15Min) {
updates.priceAfter15Min = currentPrice updates.priceAfter15Min = currentPrice
console.log(` 📍 ${signal.symbol} ${signal.direction} @ 15min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) logger.log(` 📍 ${signal.symbol} ${signal.direction} @ 15min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`)
} }
if (elapsedMinutes >= 30 && !signal.priceAfter30Min) { if (elapsedMinutes >= 30 && !signal.priceAfter30Min) {
updates.priceAfter30Min = currentPrice updates.priceAfter30Min = currentPrice
console.log(` 📍 ${signal.symbol} ${signal.direction} @ 30min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) logger.log(` 📍 ${signal.symbol} ${signal.direction} @ 30min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`)
} }
// EXTENDED TRACKING (Dec 2, 2025): Track up to 8 hours for slow developers // EXTENDED TRACKING (Dec 2, 2025): Track up to 8 hours for slow developers
if (elapsedMinutes >= 60 && !signal.priceAfter1Hr) { if (elapsedMinutes >= 60 && !signal.priceAfter1Hr) {
updates.priceAfter1Hr = currentPrice updates.priceAfter1Hr = currentPrice
console.log(` 📍 ${signal.symbol} ${signal.direction} @ 1hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) logger.log(` 📍 ${signal.symbol} ${signal.direction} @ 1hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`)
} }
if (elapsedMinutes >= 120 && !signal.priceAfter2Hr) { if (elapsedMinutes >= 120 && !signal.priceAfter2Hr) {
updates.priceAfter2Hr = currentPrice updates.priceAfter2Hr = currentPrice
console.log(` 📍 ${signal.symbol} ${signal.direction} @ 2hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) logger.log(` 📍 ${signal.symbol} ${signal.direction} @ 2hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`)
} }
if (elapsedMinutes >= 240 && !signal.priceAfter4Hr) { if (elapsedMinutes >= 240 && !signal.priceAfter4Hr) {
updates.priceAfter4Hr = currentPrice updates.priceAfter4Hr = currentPrice
console.log(` 📍 ${signal.symbol} ${signal.direction} @ 4hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) logger.log(` 📍 ${signal.symbol} ${signal.direction} @ 4hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`)
} }
if (elapsedMinutes >= 480 && !signal.priceAfter8Hr) { if (elapsedMinutes >= 480 && !signal.priceAfter8Hr) {
updates.priceAfter8Hr = currentPrice updates.priceAfter8Hr = currentPrice
console.log(` 📍 ${signal.symbol} ${signal.direction} @ 8hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) logger.log(` 📍 ${signal.symbol} ${signal.direction} @ 8hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`)
} }
// Mark complete after 8 hours OR if TP/SL already hit // Mark complete after 8 hours OR if TP/SL already hit
if (elapsedMinutes >= 480 && !signal.analysisComplete) { if (elapsedMinutes >= 480 && !signal.analysisComplete) {
updates.analysisComplete = true updates.analysisComplete = true
console.log(`${signal.symbol} ${signal.direction} @ 8hr: TRACKING COMPLETE`) logger.log(`${signal.symbol} ${signal.direction} @ 8hr: TRACKING COMPLETE`)
} }
// Early completion if TP1/TP2/SL hit (no need to wait full 8 hours) // Early completion if TP1/TP2/SL hit (no need to wait full 8 hours)
if (!signal.analysisComplete && (signal.wouldHitTP1 || signal.wouldHitTP2 || signal.wouldHitSL)) { if (!signal.analysisComplete && (signal.wouldHitTP1 || signal.wouldHitTP2 || signal.wouldHitSL)) {
updates.analysisComplete = true updates.analysisComplete = true
const hitReason = signal.wouldHitTP1 ? 'TP1' : signal.wouldHitTP2 ? 'TP2' : 'SL' const hitReason = signal.wouldHitTP1 ? 'TP1' : signal.wouldHitTP2 ? 'TP2' : 'SL'
console.log(`${signal.symbol} ${signal.direction}: ${hitReason} hit at ${profitPercent.toFixed(2)}% - TRACKING COMPLETE`) logger.log(`${signal.symbol} ${signal.direction}: ${hitReason} hit at ${profitPercent.toFixed(2)}% - TRACKING COMPLETE`)
} }
// Update max favorable/adverse excursion // Update max favorable/adverse excursion
@@ -253,17 +254,17 @@ export class BlockedSignalTracker {
// Check if TP1/TP2/SL would have been hit // Check if TP1/TP2/SL would have been hit
if (signal.wouldHitTP1 === null && Math.abs(profitPercent) >= tp1Percent) { if (signal.wouldHitTP1 === null && Math.abs(profitPercent) >= tp1Percent) {
updates.wouldHitTP1 = profitPercent > 0 updates.wouldHitTP1 = profitPercent > 0
console.log(` 🎯 ${signal.symbol} ${signal.direction} hit ${profitPercent > 0 ? 'TP1' : 'SL'} (${profitPercent.toFixed(2)}%)`) logger.log(` 🎯 ${signal.symbol} ${signal.direction} hit ${profitPercent > 0 ? 'TP1' : 'SL'} (${profitPercent.toFixed(2)}%)`)
} }
if (signal.wouldHitTP2 === null && Math.abs(profitPercent) >= tp2Percent) { if (signal.wouldHitTP2 === null && Math.abs(profitPercent) >= tp2Percent) {
updates.wouldHitTP2 = profitPercent > 0 updates.wouldHitTP2 = profitPercent > 0
console.log(` 🎯 ${signal.symbol} ${signal.direction} hit TP2 (${profitPercent.toFixed(2)}%)`) logger.log(` 🎯 ${signal.symbol} ${signal.direction} hit TP2 (${profitPercent.toFixed(2)}%)`)
} }
if (signal.wouldHitSL === null && profitPercent <= -slPercent) { if (signal.wouldHitSL === null && profitPercent <= -slPercent) {
updates.wouldHitSL = true updates.wouldHitSL = true
console.log(` 🛑 ${signal.symbol} ${signal.direction} hit SL (${profitPercent.toFixed(2)}%)`) logger.log(` 🛑 ${signal.symbol} ${signal.direction} hit SL (${profitPercent.toFixed(2)}%)`)
} }
// Update database if we have changes // Update database if we have changes
@@ -344,7 +345,7 @@ export class BlockedSignalTracker {
} }
}) })
console.log(`📊 Retrieved ${marketData.length} 1-minute data points for ${symbol}`) logger.log(`📊 Retrieved ${marketData.length} 1-minute data points for ${symbol}`)
return marketData return marketData
} catch (error) { } catch (error) {
console.error('❌ Error querying historical prices:', error) console.error('❌ Error querying historical prices:', error)
@@ -388,7 +389,7 @@ export class BlockedSignalTracker {
slPrice = entryPrice * (1 + targets.slPercent / 100) slPrice = entryPrice * (1 + targets.slPercent / 100)
} }
console.log(`🎯 Analyzing ${signal.symbol} ${direction}: Entry $${entryPrice.toFixed(2)}, TP1 $${tp1Price.toFixed(2)}, TP2 $${tp2Price.toFixed(2)}, SL $${slPrice.toFixed(2)}`) logger.log(`🎯 Analyzing ${signal.symbol} ${direction}: Entry $${entryPrice.toFixed(2)}, TP1 $${tp1Price.toFixed(2)}, TP2 $${tp2Price.toFixed(2)}, SL $${slPrice.toFixed(2)}`)
// Track hits (only record first occurrence) // Track hits (only record first occurrence)
let tp1HitTime: Date | null = null let tp1HitTime: Date | null = null
@@ -439,7 +440,7 @@ export class BlockedSignalTracker {
: currentPrice <= tp1Price : currentPrice <= tp1Price
if (tp1Hit) { if (tp1Hit) {
tp1HitTime = timestamp tp1HitTime = timestamp
console.log(`✅ TP1 hit at ${timestamp.toISOString()} (${minutesElapsed}min) - Price: $${currentPrice.toFixed(2)}`) logger.log(`✅ TP1 hit at ${timestamp.toISOString()} (${minutesElapsed}min) - Price: $${currentPrice.toFixed(2)}`)
} }
} }
@@ -450,7 +451,7 @@ export class BlockedSignalTracker {
: currentPrice <= tp2Price : currentPrice <= tp2Price
if (tp2Hit) { if (tp2Hit) {
tp2HitTime = timestamp tp2HitTime = timestamp
console.log(`✅ TP2 hit at ${timestamp.toISOString()} (${minutesElapsed}min) - Price: $${currentPrice.toFixed(2)}`) logger.log(`✅ TP2 hit at ${timestamp.toISOString()} (${minutesElapsed}min) - Price: $${currentPrice.toFixed(2)}`)
} }
} }
@@ -461,7 +462,7 @@ export class BlockedSignalTracker {
: currentPrice >= slPrice : currentPrice >= slPrice
if (slHit) { if (slHit) {
slHitTime = timestamp slHitTime = timestamp
console.log(`❌ SL hit at ${timestamp.toISOString()} (${minutesElapsed}min) - Price: $${currentPrice.toFixed(2)}`) logger.log(`❌ SL hit at ${timestamp.toISOString()} (${minutesElapsed}min) - Price: $${currentPrice.toFixed(2)}`)
} }
} }
@@ -502,7 +503,7 @@ export class BlockedSignalTracker {
if (checkpoints['4hr']) updates.priceAfter4Hr = checkpoints['4hr'] if (checkpoints['4hr']) updates.priceAfter4Hr = checkpoints['4hr']
if (checkpoints['8hr']) updates.priceAfter8Hr = checkpoints['8hr'] if (checkpoints['8hr']) updates.priceAfter8Hr = checkpoints['8hr']
console.log(`📊 Analysis complete: TP1=${updates.wouldHitTP1}, TP2=${updates.wouldHitTP2}, SL=${updates.wouldHitSL}, MFE=${maxFavorableExcursion.toFixed(2)}%, MAE=${maxAdverseExcursion.toFixed(2)}%`) logger.log(`📊 Analysis complete: TP1=${updates.wouldHitTP1}, TP2=${updates.wouldHitTP2}, SL=${updates.wouldHitSL}, MFE=${maxFavorableExcursion.toFixed(2)}%, MAE=${maxAdverseExcursion.toFixed(2)}%`)
return updates return updates
} }
@@ -544,11 +545,11 @@ export class BlockedSignalTracker {
}) })
if (signalsToProcess.length === 0) { if (signalsToProcess.length === 0) {
console.log('📊 No signals ready for batch processing') logger.log('📊 No signals ready for batch processing')
return return
} }
console.log(`🔄 Processing ${signalsToProcess.length} signals with historical data...`) logger.log(`🔄 Processing ${signalsToProcess.length} signals with historical data...`)
let processed = 0 let processed = 0
let skipped = 0 let skipped = 0
@@ -567,12 +568,12 @@ export class BlockedSignalTracker {
) )
if (historicalPrices.length === 0) { if (historicalPrices.length === 0) {
console.log(`⏭️ Skipping ${signal.symbol} ${signal.direction} - no historical data`) logger.log(`⏭️ Skipping ${signal.symbol} ${signal.direction} - no historical data`)
skipped++ skipped++
continue continue
} }
console.log(`📊 Processing ${signal.symbol} ${signal.direction} with ${historicalPrices.length} data points...`) logger.log(`📊 Processing ${signal.symbol} ${signal.direction} with ${historicalPrices.length} data points...`)
// Analyze minute-by-minute // Analyze minute-by-minute
const updates = await this.analyzeHistoricalData( const updates = await this.analyzeHistoricalData(
@@ -591,7 +592,7 @@ export class BlockedSignalTracker {
}) })
processed++ processed++
console.log(`${signal.symbol} ${signal.direction} analyzed successfully`) logger.log(`${signal.symbol} ${signal.direction} analyzed successfully`)
} catch (error) { } catch (error) {
console.error(`❌ Error processing signal ${signal.id}:`, error) console.error(`❌ Error processing signal ${signal.id}:`, error)
@@ -599,7 +600,7 @@ export class BlockedSignalTracker {
} }
} }
console.log(`🎉 Batch processing complete: ${processed} analyzed, ${skipped} skipped`) logger.log(`🎉 Batch processing complete: ${processed} analyzed, ${skipped} skipped`)
} catch (error) { } catch (error) {
console.error('❌ Error in batch processing:', error) console.error('❌ Error in batch processing:', error)

View File

@@ -3,6 +3,7 @@
*/ */
import { PrismaClient } from '@prisma/client' import { PrismaClient } from '@prisma/client'
import { logger } from '../utils/logger'
import { logCriticalError, logDatabaseOperation } from '../utils/persistent-logger' import { logCriticalError, logDatabaseOperation } from '../utils/persistent-logger'
// Singleton Prisma client // Singleton Prisma client
@@ -13,7 +14,7 @@ export function getPrismaClient(): PrismaClient {
prisma = new PrismaClient({ prisma = new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'], log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
}) })
console.log('✅ Prisma client initialized') logger.log('✅ Prisma client initialized')
} }
return prisma return prisma
} }
@@ -177,7 +178,7 @@ export async function createTrade(params: CreateTradeParams) {
if (attempt < maxRetries) { if (attempt < maxRetries) {
const delay = baseDelay * Math.pow(2, attempt - 1) const delay = baseDelay * Math.pow(2, attempt - 1)
console.log(`⏳ Verification failed, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})...`) logger.log(`⏳ Verification failed, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})...`)
await new Promise(resolve => setTimeout(resolve, delay)) await new Promise(resolve => setTimeout(resolve, delay))
continue // Retry continue // Retry
} }
@@ -185,7 +186,7 @@ export async function createTrade(params: CreateTradeParams) {
throw new Error(errorMsg) throw new Error(errorMsg)
} }
console.log(`📊 Trade record created & VERIFIED: ${trade.id}`) logger.log(`📊 Trade record created & VERIFIED: ${trade.id}`)
logDatabaseOperation('createTrade', true, { logDatabaseOperation('createTrade', true, {
table: 'Trade', table: 'Trade',
recordId: trade.id, recordId: trade.id,
@@ -206,7 +207,7 @@ export async function createTrade(params: CreateTradeParams) {
if (attempt < maxRetries) { if (attempt < maxRetries) {
const delay = baseDelay * Math.pow(2, attempt - 1) const delay = baseDelay * Math.pow(2, attempt - 1)
console.log(`⏳ Retrying in ${delay}ms...`) logger.log(`⏳ Retrying in ${delay}ms...`)
await new Promise(resolve => setTimeout(resolve, delay)) await new Promise(resolve => setTimeout(resolve, delay))
continue continue
} }
@@ -267,7 +268,7 @@ export async function updateTradeExit(params: UpdateTradeExitParams) {
}, },
}) })
console.log(`📊 Trade closed: ${trade.id} | P&L: $${params.realizedPnL.toFixed(2)}`) logger.log(`📊 Trade closed: ${trade.id} | P&L: $${params.realizedPnL.toFixed(2)}`)
return trade return trade
} catch (error) { } catch (error) {
console.error('❌ Failed to update trade exit:', error) console.error('❌ Failed to update trade exit:', error)
@@ -331,7 +332,7 @@ export async function getOpenTrades() {
orderBy: { entryTime: 'asc' }, orderBy: { entryTime: 'asc' },
}) })
console.log(`📊 Found ${trades.length} open trades to restore`) logger.log(`📊 Found ${trades.length} open trades to restore`)
return trades return trades
} catch (error) { } catch (error) {
console.error('❌ Failed to get open trades:', error) console.error('❌ Failed to get open trades:', error)
@@ -589,7 +590,7 @@ export async function createBlockedSignal(params: CreateBlockedSignalParams) {
}, },
}) })
console.log(`📝 Blocked signal saved: ${params.symbol} ${params.direction} (score: ${params.signalQualityScore}/${params.minScoreRequired})`) logger.log(`📝 Blocked signal saved: ${params.symbol} ${params.direction} (score: ${params.signalQualityScore}/${params.minScoreRequired})`)
return blockedSignal return blockedSignal
} catch (error) { } catch (error) {
console.error('❌ Failed to save blocked signal:', error) console.error('❌ Failed to save blocked signal:', error)
@@ -746,6 +747,6 @@ export async function disconnectPrisma() {
if (prisma) { if (prisma) {
await prisma.$disconnect() await prisma.$disconnect()
prisma = null prisma = null
console.log('✅ Prisma client disconnected') logger.log('✅ Prisma client disconnected')
} }
} }

View File

@@ -5,6 +5,7 @@
*/ */
import { Connection, PublicKey, Keypair } from '@solana/web3.js' import { Connection, PublicKey, Keypair } from '@solana/web3.js'
import { logger } from '../utils/logger'
import { DriftClient, initialize, User, PerpMarkets } from '@drift-labs/sdk' import { DriftClient, initialize, User, PerpMarkets } from '@drift-labs/sdk'
import bs58 from 'bs58' import bs58 from 'bs58'
import { getDriftHealthMonitor } from '../monitoring/drift-health-monitor' import { getDriftHealthMonitor } from '../monitoring/drift-health-monitor'
@@ -43,9 +44,9 @@ export class DriftService {
: this.connection : this.connection
if (config.alchemyRpcUrl) { if (config.alchemyRpcUrl) {
console.log('🔀 Hybrid RPC mode: Helius for init, Alchemy for trades') logger.log('🔀 Hybrid RPC mode: Helius for init, Alchemy for trades')
} else { } else {
console.log('📡 Single RPC mode: Helius for all operations') logger.log('📡 Single RPC mode: Helius for all operations')
} }
// Create wallet from private key // Create wallet from private key
@@ -88,7 +89,7 @@ export class DriftService {
} }
} }
console.log('✅ Drift service created for wallet:', this.wallet.publicKey.toString()) logger.log('✅ Drift service created for wallet:', this.wallet.publicKey.toString())
} }
/** /**
@@ -118,17 +119,17 @@ export class DriftService {
error?.code === 'EAI_AGAIN' || error?.code === 'EAI_AGAIN' ||
error?.cause?.code === 'EAI_AGAIN' error?.cause?.code === 'EAI_AGAIN'
console.log(`🔍 Error detection: isTransient=${isTransient}, attempt=${attempt}/${maxRetries}`) logger.log(`🔍 Error detection: isTransient=${isTransient}, attempt=${attempt}/${maxRetries}`)
console.log(`🔍 Error details: message="${error?.message}", code="${error?.code}", cause.code="${error?.cause?.code}"`) logger.log(`🔍 Error details: message="${error?.message}", code="${error?.code}", cause.code="${error?.cause?.code}"`)
if (!isTransient || attempt === maxRetries) { if (!isTransient || attempt === maxRetries) {
// Non-transient error or max retries reached - fail immediately // Non-transient error or max retries reached - fail immediately
console.log(`❌ Not retrying: isTransient=${isTransient}, maxed=${attempt === maxRetries}`) logger.log(`❌ Not retrying: isTransient=${isTransient}, maxed=${attempt === maxRetries}`)
throw error throw error
} }
console.log(`⚠️ ${operationName} failed (attempt ${attempt}/${maxRetries}): ${error?.message || error}`) logger.log(`⚠️ ${operationName} failed (attempt ${attempt}/${maxRetries}): ${error?.message || error}`)
console.log(`⏳ Retrying in ${delayMs}ms...`) logger.log(`⏳ Retrying in ${delayMs}ms...`)
// Wait before retry // Wait before retry
await new Promise(resolve => setTimeout(resolve, delayMs)) await new Promise(resolve => setTimeout(resolve, delayMs))
@@ -144,12 +145,12 @@ export class DriftService {
*/ */
async initialize(): Promise<void> { async initialize(): Promise<void> {
if (this.isInitialized) { if (this.isInitialized) {
console.log('⚠️ Drift service already initialized') logger.log('⚠️ Drift service already initialized')
return return
} }
try { try {
console.log('🚀 Initializing Drift Protocol client...') logger.log('🚀 Initializing Drift Protocol client...')
// Wrap initialization in retry logic to handle DNS failures // Wrap initialization in retry logic to handle DNS failures
await this.retryOperation(async () => { await this.retryOperation(async () => {
@@ -170,20 +171,20 @@ export class DriftService {
// Subscribe to Drift account updates (this makes RPC calls) // Subscribe to Drift account updates (this makes RPC calls)
await this.driftClient.subscribe() await this.driftClient.subscribe()
console.log('✅ Drift client subscribed to account updates') logger.log('✅ Drift client subscribed to account updates')
// Get user account // Get user account
this.user = this.driftClient.getUser() this.user = this.driftClient.getUser()
}, 3, 2000, 'Drift initialization') }, 3, 2000, 'Drift initialization')
this.isInitialized = true this.isInitialized = true
console.log('✅ Drift service initialized successfully') logger.log('✅ Drift service initialized successfully')
// CRITICAL FIX (Nov 25, 2025): Intercept errors BEFORE starting monitor // CRITICAL FIX (Nov 25, 2025): Intercept errors BEFORE starting monitor
// Without this, errors aren't recorded and auto-restart never triggers // Without this, errors aren't recorded and auto-restart never triggers
console.log('🔧 Setting up error interception for health monitoring...') logger.log('🔧 Setting up error interception for health monitoring...')
this.interceptWebSocketErrors() this.interceptWebSocketErrors()
console.log('✅ Error interception active') logger.log('✅ Error interception active')
// Start health monitoring (error-based restart instead of blind timer) // Start health monitoring (error-based restart instead of blind timer)
const monitor = getDriftHealthMonitor() const monitor = getDriftHealthMonitor()
@@ -427,7 +428,7 @@ export class DriftService {
async disconnect(): Promise<void> { async disconnect(): Promise<void> {
if (this.driftClient) { if (this.driftClient) {
await this.driftClient.unsubscribe() await this.driftClient.unsubscribe()
console.log('✅ Drift client disconnected') logger.log('✅ Drift client disconnected')
} }
this.isInitialized = false this.isInitialized = false
} }
@@ -460,9 +461,9 @@ export function getDriftService(): DriftService {
} }
driftServiceInstance = new DriftService(config) driftServiceInstance = new DriftService(config)
console.log('🔄 Created new Drift service singleton') logger.log('🔄 Created new Drift service singleton')
} else { } else {
console.log('♻️ Reusing existing Drift service instance') logger.log('♻️ Reusing existing Drift service instance')
} }
return driftServiceInstance return driftServiceInstance
@@ -471,7 +472,7 @@ export function getDriftService(): DriftService {
export async function initializeDriftService(): Promise<DriftService> { export async function initializeDriftService(): Promise<DriftService> {
// If already initializing, return the same promise to avoid multiple concurrent inits // If already initializing, return the same promise to avoid multiple concurrent inits
if (initializationPromise) { if (initializationPromise) {
console.log('⏳ Waiting for ongoing initialization...') logger.log('⏳ Waiting for ongoing initialization...')
return initializationPromise return initializationPromise
} }
@@ -479,7 +480,7 @@ export async function initializeDriftService(): Promise<DriftService> {
// If already initialized, return immediately // If already initialized, return immediately
if (service['isInitialized']) { if (service['isInitialized']) {
console.log('✅ Drift service already initialized') logger.log('✅ Drift service already initialized')
return service return service
} }

View File

@@ -5,6 +5,7 @@
*/ */
import { getDriftService, initializeDriftService } from './client' import { getDriftService, initializeDriftService } from './client'
import { logger } from '../utils/logger'
import { getMarketConfig } from '../../config/trading' import { getMarketConfig } from '../../config/trading'
import BN from 'bn.js' import BN from 'bn.js'
import { import {
@@ -81,7 +82,7 @@ export async function openPosition(
params: OpenPositionParams params: OpenPositionParams
): Promise<OpenPositionResult> { ): Promise<OpenPositionResult> {
try { try {
console.log('📊 Opening position:', params) logger.log('📊 Opening position:', params)
const driftService = getDriftService() const driftService = getDriftService()
const marketConfig = getMarketConfig(params.symbol) const marketConfig = getMarketConfig(params.symbol)
@@ -89,7 +90,7 @@ export async function openPosition(
// Get current oracle price // Get current oracle price
const oraclePrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex) const oraclePrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex)
console.log(`💰 Current ${params.symbol} price: $${oraclePrice.toFixed(4)}`) logger.log(`💰 Current ${params.symbol} price: $${oraclePrice.toFixed(4)}`)
// Calculate position size in base asset // Calculate position size in base asset
const baseAssetSize = params.sizeUSD / oraclePrice const baseAssetSize = params.sizeUSD / oraclePrice
@@ -107,17 +108,17 @@ export async function openPosition(
: 1 - (params.slippageTolerance / 100) : 1 - (params.slippageTolerance / 100)
const worstPrice = oraclePrice * slippageMultiplier const worstPrice = oraclePrice * slippageMultiplier
console.log(`📝 Order details:`) logger.log(`📝 Order details:`)
console.log(` Size: ${baseAssetSize.toFixed(4)} ${params.symbol.split('-')[0]}`) logger.log(` Size: ${baseAssetSize.toFixed(4)} ${params.symbol.split('-')[0]}`)
console.log(` Notional: $${params.sizeUSD.toFixed(2)}`) logger.log(` Notional: $${params.sizeUSD.toFixed(2)}`)
console.log(` Oracle price: $${oraclePrice.toFixed(4)}`) logger.log(` Oracle price: $${oraclePrice.toFixed(4)}`)
console.log(` Worst price (${params.slippageTolerance}% slippage): $${worstPrice.toFixed(4)}`) logger.log(` Worst price (${params.slippageTolerance}% slippage): $${worstPrice.toFixed(4)}`)
// Check DRY_RUN mode // Check DRY_RUN mode
const isDryRun = process.env.DRY_RUN === 'true' const isDryRun = process.env.DRY_RUN === 'true'
if (isDryRun) { if (isDryRun) {
console.log('🧪 DRY RUN MODE: Simulating order (not executing on blockchain)') logger.log('🧪 DRY RUN MODE: Simulating order (not executing on blockchain)')
const mockTxSig = `DRY_RUN_${Date.now()}_${Math.random().toString(36).substring(7)}` const mockTxSig = `DRY_RUN_${Date.now()}_${Math.random().toString(36).substring(7)}`
return { return {
@@ -141,13 +142,13 @@ export async function openPosition(
} }
// Place market order using simple placePerpOrder (like v3) // Place market order using simple placePerpOrder (like v3)
console.log('🚀 Placing REAL market order...') logger.log('🚀 Placing REAL market order...')
const txSig = await driftClient.placePerpOrder(orderParams) const txSig = await driftClient.placePerpOrder(orderParams)
console.log(`📝 Transaction submitted: ${txSig}`) logger.log(`📝 Transaction submitted: ${txSig}`)
// CRITICAL: Confirm transaction actually executed on-chain // CRITICAL: Confirm transaction actually executed on-chain
console.log('⏳ Confirming transaction on-chain...') logger.log('⏳ Confirming transaction on-chain...')
const connection = driftService.getTradeConnection() // Use Alchemy for trade operations const connection = driftService.getTradeConnection() // Use Alchemy for trade operations
try { try {
@@ -161,7 +162,7 @@ export async function openPosition(
} }
} }
console.log(`✅ Transaction confirmed on-chain: ${txSig}`) logger.log(`✅ Transaction confirmed on-chain: ${txSig}`)
} catch (confirmError) { } catch (confirmError) {
console.error(`❌ Failed to confirm transaction:`, confirmError) console.error(`❌ Failed to confirm transaction:`, confirmError)
@@ -172,7 +173,7 @@ export async function openPosition(
} }
// Wait a moment for position to update // Wait a moment for position to update
console.log('⏳ Waiting for position to update...') logger.log('⏳ Waiting for position to update...')
await new Promise(resolve => setTimeout(resolve, 2000)) await new Promise(resolve => setTimeout(resolve, 2000))
// Get actual fill price from position // Get actual fill price from position
@@ -188,12 +189,12 @@ export async function openPosition(
const expectedSizeUSD = params.sizeUSD const expectedSizeUSD = params.sizeUSD
const sizeRatio = actualSizeUSD / expectedSizeUSD const sizeRatio = actualSizeUSD / expectedSizeUSD
console.log(`💰 Fill details:`) logger.log(`💰 Fill details:`)
console.log(` Fill price: $${fillPrice.toFixed(4)}`) logger.log(` Fill price: $${fillPrice.toFixed(4)}`)
console.log(` Slippage: ${slippage.toFixed(3)}%`) logger.log(` Slippage: ${slippage.toFixed(3)}%`)
console.log(` Expected size: $${expectedSizeUSD.toFixed(2)}`) logger.log(` Expected size: $${expectedSizeUSD.toFixed(2)}`)
console.log(` Actual size: $${actualSizeUSD.toFixed(2)}`) logger.log(` Actual size: $${actualSizeUSD.toFixed(2)}`)
console.log(` Size ratio: ${(sizeRatio * 100).toFixed(1)}%`) logger.log(` Size ratio: ${(sizeRatio * 100).toFixed(1)}%`)
// Flag as phantom if actual size is less than 50% of expected // Flag as phantom if actual size is less than 50% of expected
const isPhantom = sizeRatio < 0.5 const isPhantom = sizeRatio < 0.5
@@ -216,8 +217,8 @@ export async function openPosition(
} }
} else { } else {
// Position not found yet (may be DRY_RUN mode) // Position not found yet (may be DRY_RUN mode)
console.log(`⚠️ Position not immediately visible (may be DRY_RUN mode)`) logger.log(`⚠️ Position not immediately visible (may be DRY_RUN mode)`)
console.log(` Using oracle price as estimate: $${oraclePrice.toFixed(4)}`) logger.log(` Using oracle price as estimate: $${oraclePrice.toFixed(4)}`)
return { return {
success: true, success: true,
@@ -250,7 +251,7 @@ export async function openPosition(
*/ */
export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<PlaceExitOrdersResult> { export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<PlaceExitOrdersResult> {
try { try {
console.log('🛡️ Placing exit orders on-chain:', options.symbol) logger.log('🛡️ Placing exit orders on-chain:', options.symbol)
const driftService = getDriftService() const driftService = getDriftService()
const driftClient = driftService.getClient() const driftClient = driftService.getClient()
@@ -258,7 +259,7 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<
const isDryRun = process.env.DRY_RUN === 'true' const isDryRun = process.env.DRY_RUN === 'true'
if (isDryRun) { if (isDryRun) {
console.log('🧪 DRY RUN: Simulating placement of exit orders') logger.log('🧪 DRY RUN: Simulating placement of exit orders')
return { return {
success: true, success: true,
signatures: [ signatures: [
@@ -285,11 +286,11 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<
const remainingAfterTP1 = options.positionSizeUSD - tp1USD const remainingAfterTP1 = options.positionSizeUSD - tp1USD
const tp2USD = (remainingAfterTP1 * options.tp2SizePercent) / 100 const tp2USD = (remainingAfterTP1 * options.tp2SizePercent) / 100
console.log(`📊 Exit order sizes:`) logger.log(`📊 Exit order sizes:`)
console.log(` TP1: ${options.tp1SizePercent}% of $${options.positionSizeUSD.toFixed(2)} = $${tp1USD.toFixed(2)}`) logger.log(` TP1: ${options.tp1SizePercent}% of $${options.positionSizeUSD.toFixed(2)} = $${tp1USD.toFixed(2)}`)
console.log(` Remaining after TP1: $${remainingAfterTP1.toFixed(2)}`) logger.log(` Remaining after TP1: $${remainingAfterTP1.toFixed(2)}`)
console.log(` TP2: ${options.tp2SizePercent}% of remaining = $${tp2USD.toFixed(2)}`) logger.log(` TP2: ${options.tp2SizePercent}% of remaining = $${tp2USD.toFixed(2)}`)
console.log(` Runner (if any): $${(remainingAfterTP1 - tp2USD).toFixed(2)}`) logger.log(` Runner (if any): $${(remainingAfterTP1 - tp2USD).toFixed(2)}`)
// For orders that close a long, the order direction should be SHORT (sell) // For orders that close a long, the order direction should be SHORT (sell)
const orderDirection = options.direction === 'long' ? PositionDirection.SHORT : PositionDirection.LONG const orderDirection = options.direction === 'long' ? PositionDirection.SHORT : PositionDirection.LONG
@@ -307,14 +308,14 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<
reduceOnly: true, reduceOnly: true,
} }
console.log('🚧 Placing TP1 limit order (reduce-only)...') logger.log('🚧 Placing TP1 limit order (reduce-only)...')
const sig = await retryWithBackoff(async () => const sig = await retryWithBackoff(async () =>
await (driftClient as any).placePerpOrder(orderParams) await (driftClient as any).placePerpOrder(orderParams)
) )
console.log('✅ TP1 order placed:', sig) logger.log('✅ TP1 order placed:', sig)
signatures.push(sig) signatures.push(sig)
} else { } else {
console.log('⚠️ TP1 size below market min, skipping on-chain TP1') logger.log('⚠️ TP1 size below market min, skipping on-chain TP1')
} }
} }
@@ -331,14 +332,14 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<
reduceOnly: true, reduceOnly: true,
} }
console.log('🚧 Placing TP2 limit order (reduce-only)...') logger.log('🚧 Placing TP2 limit order (reduce-only)...')
const sig = await retryWithBackoff(async () => const sig = await retryWithBackoff(async () =>
await (driftClient as any).placePerpOrder(orderParams) await (driftClient as any).placePerpOrder(orderParams)
) )
console.log('✅ TP2 order placed:', sig) logger.log('✅ TP2 order placed:', sig)
signatures.push(sig) signatures.push(sig)
} else { } else {
console.log('⚠️ TP2 size below market min, skipping on-chain TP2') logger.log('⚠️ TP2 size below market min, skipping on-chain TP2')
} }
} }
@@ -356,7 +357,7 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<
if (useDualStops && options.softStopPrice && options.hardStopPrice) { if (useDualStops && options.softStopPrice && options.hardStopPrice) {
// ============== DUAL STOP SYSTEM ============== // ============== DUAL STOP SYSTEM ==============
console.log('🛡️🛡️ Placing DUAL STOP SYSTEM...') logger.log('🛡️🛡️ Placing DUAL STOP SYSTEM...')
// 1. Soft Stop (TRIGGER_LIMIT) - Avoids wicks // 1. Soft Stop (TRIGGER_LIMIT) - Avoids wicks
const softStopBuffer = options.softStopBuffer ?? 0.4 const softStopBuffer = options.softStopBuffer ?? 0.4
@@ -377,15 +378,15 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<
reduceOnly: true, reduceOnly: true,
} }
console.log(` 1⃣ Soft Stop (TRIGGER_LIMIT):`) logger.log(` 1⃣ Soft Stop (TRIGGER_LIMIT):`)
console.log(` Trigger: $${options.softStopPrice.toFixed(4)}`) logger.log(` Trigger: $${options.softStopPrice.toFixed(4)}`)
console.log(` Limit: $${(options.softStopPrice * softStopMultiplier).toFixed(4)}`) logger.log(` Limit: $${(options.softStopPrice * softStopMultiplier).toFixed(4)}`)
console.log(` Purpose: Avoid false breakouts/wicks`) logger.log(` Purpose: Avoid false breakouts/wicks`)
const softStopSig = await retryWithBackoff(async () => const softStopSig = await retryWithBackoff(async () =>
await (driftClient as any).placePerpOrder(softStopParams) await (driftClient as any).placePerpOrder(softStopParams)
) )
console.log(` ✅ Soft stop placed: ${softStopSig}`) logger.log(` ✅ Soft stop placed: ${softStopSig}`)
signatures.push(softStopSig) signatures.push(softStopSig)
// 2. Hard Stop (TRIGGER_MARKET) - Guarantees exit // 2. Hard Stop (TRIGGER_MARKET) - Guarantees exit
@@ -401,17 +402,17 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<
reduceOnly: true, reduceOnly: true,
} }
console.log(` 2⃣ Hard Stop (TRIGGER_MARKET):`) logger.log(` 2⃣ Hard Stop (TRIGGER_MARKET):`)
console.log(` Trigger: $${options.hardStopPrice.toFixed(4)}`) logger.log(` Trigger: $${options.hardStopPrice.toFixed(4)}`)
console.log(` Purpose: Guaranteed exit if soft stop doesn't fill`) logger.log(` Purpose: Guaranteed exit if soft stop doesn't fill`)
const hardStopSig = await retryWithBackoff(async () => const hardStopSig = await retryWithBackoff(async () =>
await (driftClient as any).placePerpOrder(hardStopParams) await (driftClient as any).placePerpOrder(hardStopParams)
) )
console.log(` ✅ Hard stop placed: ${hardStopSig}`) logger.log(` ✅ Hard stop placed: ${hardStopSig}`)
signatures.push(hardStopSig) signatures.push(hardStopSig)
console.log(`🎯 Dual stop system active: Soft @ $${options.softStopPrice.toFixed(2)} | Hard @ $${options.hardStopPrice.toFixed(2)}`) logger.log(`🎯 Dual stop system active: Soft @ $${options.softStopPrice.toFixed(2)} | Hard @ $${options.hardStopPrice.toFixed(2)}`)
} else { } else {
// ============== SINGLE STOP SYSTEM ============== // ============== SINGLE STOP SYSTEM ==============
@@ -437,15 +438,15 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<
reduceOnly: true, reduceOnly: true,
} }
console.log(`🛡️ Placing SL as TRIGGER_LIMIT (${stopLimitBuffer}% buffer)...`) logger.log(`🛡️ Placing SL as TRIGGER_LIMIT (${stopLimitBuffer}% buffer)...`)
console.log(` Trigger: ${options.direction === 'long' ? 'BELOW' : 'ABOVE'} $${options.stopLossPrice.toFixed(4)}`) logger.log(` Trigger: ${options.direction === 'long' ? 'BELOW' : 'ABOVE'} $${options.stopLossPrice.toFixed(4)}`)
console.log(` Limit: $${(options.stopLossPrice * limitPriceMultiplier).toFixed(4)}`) logger.log(` Limit: $${(options.stopLossPrice * limitPriceMultiplier).toFixed(4)}`)
console.log(` ⚠️ May not fill during fast moves - use for liquid markets only!`) logger.log(` ⚠️ May not fill during fast moves - use for liquid markets only!`)
const sig = await retryWithBackoff(async () => const sig = await retryWithBackoff(async () =>
await (driftClient as any).placePerpOrder(orderParams) await (driftClient as any).placePerpOrder(orderParams)
) )
console.log('✅ SL trigger-limit order placed:', sig) logger.log('✅ SL trigger-limit order placed:', sig)
signatures.push(sig) signatures.push(sig)
} else { } else {
// TRIGGER_MARKET: Default, guaranteed execution // TRIGGER_MARKET: Default, guaranteed execution
@@ -461,19 +462,19 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<
reduceOnly: true, reduceOnly: true,
} }
console.log(`🛡️ Placing SL as TRIGGER_MARKET (guaranteed execution - RECOMMENDED)...`) logger.log(`🛡️ Placing SL as TRIGGER_MARKET (guaranteed execution - RECOMMENDED)...`)
console.log(` Trigger: ${options.direction === 'long' ? 'BELOW' : 'ABOVE'} $${options.stopLossPrice.toFixed(4)}`) logger.log(` Trigger: ${options.direction === 'long' ? 'BELOW' : 'ABOVE'} $${options.stopLossPrice.toFixed(4)}`)
console.log(` ✅ Will execute at market price when triggered (may slip but WILL fill)`) logger.log(` ✅ Will execute at market price when triggered (may slip but WILL fill)`)
const sig = await retryWithBackoff(async () => const sig = await retryWithBackoff(async () =>
await (driftClient as any).placePerpOrder(orderParams) await (driftClient as any).placePerpOrder(orderParams)
) )
console.log('✅ SL trigger-market order placed:', sig) logger.log('✅ SL trigger-market order placed:', sig)
signatures.push(sig) signatures.push(sig)
} }
} }
} else { } else {
console.log('⚠️ SL size below market min, skipping on-chain SL') logger.log('⚠️ SL size below market min, skipping on-chain SL')
} }
return { success: true, signatures } return { success: true, signatures }
@@ -490,7 +491,7 @@ export async function closePosition(
params: ClosePositionParams params: ClosePositionParams
): Promise<ClosePositionResult> { ): Promise<ClosePositionResult> {
try { try {
console.log('📊 Closing position:', params) logger.log('📊 Closing position:', params)
const driftService = getDriftService() const driftService = getDriftService()
const marketConfig = getMarketConfig(params.symbol) const marketConfig = getMarketConfig(params.symbol)
@@ -509,26 +510,26 @@ export async function closePosition(
// CRITICAL FIX: If calculated size is below minimum, close 100% instead // CRITICAL FIX: If calculated size is below minimum, close 100% instead
// This prevents "runner" positions from being too small to close // This prevents "runner" positions from being too small to close
if (sizeToClose < marketConfig.minOrderSize) { if (sizeToClose < marketConfig.minOrderSize) {
console.log(`⚠️ Calculated close size ${sizeToClose.toFixed(4)} is below minimum ${marketConfig.minOrderSize}`) logger.log(`⚠️ Calculated close size ${sizeToClose.toFixed(4)} is below minimum ${marketConfig.minOrderSize}`)
console.log(` Forcing 100% close to avoid Drift rejection`) logger.log(` Forcing 100% close to avoid Drift rejection`)
sizeToClose = position.size // Close entire position sizeToClose = position.size // Close entire position
} }
console.log(`📝 Close order details:`) logger.log(`📝 Close order details:`)
console.log(` Current position: ${position.size.toFixed(4)} ${position.side}`) logger.log(` Current position: ${position.size.toFixed(4)} ${position.side}`)
console.log(` Closing: ${params.percentToClose}% (${sizeToClose.toFixed(4)})`) logger.log(` Closing: ${params.percentToClose}% (${sizeToClose.toFixed(4)})`)
console.log(` Entry price: $${position.entryPrice.toFixed(4)}`) logger.log(` Entry price: $${position.entryPrice.toFixed(4)}`)
console.log(` Unrealized P&L: $${position.unrealizedPnL.toFixed(2)}`) logger.log(` Unrealized P&L: $${position.unrealizedPnL.toFixed(2)}`)
// Get current oracle price // Get current oracle price
const oraclePrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex) const oraclePrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex)
console.log(` Current price: $${oraclePrice.toFixed(4)}`) logger.log(` Current price: $${oraclePrice.toFixed(4)}`)
// Check DRY_RUN mode // Check DRY_RUN mode
const isDryRun = process.env.DRY_RUN === 'true' const isDryRun = process.env.DRY_RUN === 'true'
if (isDryRun) { if (isDryRun) {
console.log('🧪 DRY RUN MODE: Simulating close order (not executing on blockchain)') logger.log('🧪 DRY RUN MODE: Simulating close order (not executing on blockchain)')
// Calculate realized P&L with leverage (default 10x in dry run) // Calculate realized P&L with leverage (default 10x in dry run)
const profitPercent = ((oraclePrice - position.entryPrice) / position.entryPrice) * 100 * (position.side === 'long' ? 1 : -1) const profitPercent = ((oraclePrice - position.entryPrice) / position.entryPrice) * 100 * (position.side === 'long' ? 1 : -1)
@@ -538,10 +539,10 @@ export async function closePosition(
const mockTxSig = `DRY_RUN_CLOSE_${Date.now()}_${Math.random().toString(36).substring(7)}` const mockTxSig = `DRY_RUN_CLOSE_${Date.now()}_${Math.random().toString(36).substring(7)}`
console.log(`💰 Simulated close:`) logger.log(`💰 Simulated close:`)
console.log(` Close price: $${oraclePrice.toFixed(4)}`) logger.log(` Close price: $${oraclePrice.toFixed(4)}`)
console.log(` Profit %: ${profitPercent.toFixed(3)}% → Account P&L (10x): ${accountPnLPercent.toFixed(2)}%`) logger.log(` Profit %: ${profitPercent.toFixed(3)}% → Account P&L (10x): ${accountPnLPercent.toFixed(2)}%`)
console.log(` Realized P&L: $${realizedPnL.toFixed(2)}`) logger.log(` Realized P&L: $${realizedPnL.toFixed(2)}`)
return { return {
success: true, success: true,
@@ -565,16 +566,16 @@ export async function closePosition(
// Place market close order using simple placePerpOrder (like v3) // Place market close order using simple placePerpOrder (like v3)
// CRITICAL: Wrap in retry logic for rate limit protection // CRITICAL: Wrap in retry logic for rate limit protection
console.log('🚀 Placing REAL market close order with retry protection...') logger.log('🚀 Placing REAL market close order with retry protection...')
const txSig = await retryWithBackoff(async () => { const txSig = await retryWithBackoff(async () => {
return await driftClient.placePerpOrder(orderParams) return await driftClient.placePerpOrder(orderParams)
}, 3, 8000) // 8s base delay, 3 max retries }, 3, 8000) // 8s base delay, 3 max retries
console.log(`✅ Close order placed! Transaction: ${txSig}`) logger.log(`✅ Close order placed! Transaction: ${txSig}`)
// CRITICAL: Confirm transaction on-chain to prevent phantom closes // CRITICAL: Confirm transaction on-chain to prevent phantom closes
// BUT: Use timeout to prevent API hangs during network congestion // BUT: Use timeout to prevent API hangs during network congestion
console.log('⏳ Confirming transaction on-chain (30s timeout)...') logger.log('⏳ Confirming transaction on-chain (30s timeout)...')
const connection = driftService.getTradeConnection() // Use Alchemy for trade operations const connection = driftService.getTradeConnection() // Use Alchemy for trade operations
try { try {
@@ -590,7 +591,7 @@ export async function closePosition(
throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`) throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`)
} }
console.log('✅ Transaction confirmed on-chain') logger.log('✅ Transaction confirmed on-chain')
} catch (timeoutError: any) { } catch (timeoutError: any) {
if (timeoutError.message === 'Transaction confirmation timeout') { if (timeoutError.message === 'Transaction confirmation timeout') {
console.warn('⚠️ Transaction confirmation timed out after 30s') console.warn('⚠️ Transaction confirmation timed out after 30s')
@@ -615,7 +616,7 @@ export async function closePosition(
leverage = 10000 / Number(userAccount.maxMarginRatio) leverage = 10000 / Number(userAccount.maxMarginRatio)
} }
} catch (err) { } catch (err) {
console.log('⚠️ Could not determine leverage from account, using 10x default') logger.log('⚠️ Could not determine leverage from account, using 10x default')
} }
// Calculate closed notional value (USD) // Calculate closed notional value (USD)
@@ -623,24 +624,24 @@ export async function closePosition(
const realizedPnL = (closedNotional * profitPercent) / 100 const realizedPnL = (closedNotional * profitPercent) / 100
const accountPnLPercent = profitPercent * leverage const accountPnLPercent = profitPercent * leverage
console.log(`💰 Close details:`) logger.log(`💰 Close details:`)
console.log(` Close price: $${oraclePrice.toFixed(4)}`) logger.log(` Close price: $${oraclePrice.toFixed(4)}`)
console.log(` Profit %: ${profitPercent.toFixed(3)}% | Account P&L (${leverage}x): ${accountPnLPercent.toFixed(2)}%`) logger.log(` Profit %: ${profitPercent.toFixed(3)}% | Account P&L (${leverage}x): ${accountPnLPercent.toFixed(2)}%`)
console.log(` Closed notional: $${closedNotional.toFixed(2)}`) logger.log(` Closed notional: $${closedNotional.toFixed(2)}`)
console.log(` Realized P&L: $${realizedPnL.toFixed(2)}`) logger.log(` Realized P&L: $${realizedPnL.toFixed(2)}`)
// If closing 100%, verify position actually closed and cancel remaining orders // If closing 100%, verify position actually closed and cancel remaining orders
if (params.percentToClose === 100) { if (params.percentToClose === 100) {
console.log('🗑️ Position fully closed, cancelling remaining orders...') logger.log('🗑️ Position fully closed, cancelling remaining orders...')
const cancelResult = await cancelAllOrders(params.symbol) const cancelResult = await cancelAllOrders(params.symbol)
if (cancelResult.success && cancelResult.cancelledCount! > 0) { if (cancelResult.success && cancelResult.cancelledCount! > 0) {
console.log(`✅ Cancelled ${cancelResult.cancelledCount} orders`) logger.log(`✅ Cancelled ${cancelResult.cancelledCount} orders`)
} }
// CRITICAL: Verify position actually closed on Drift (Nov 16, 2025) // CRITICAL: Verify position actually closed on Drift (Nov 16, 2025)
// Transaction confirmed ≠ Drift state updated immediately // Transaction confirmed ≠ Drift state updated immediately
// Wait 5 seconds for Drift internal state to propagate // Wait 5 seconds for Drift internal state to propagate
console.log('⏳ Waiting 5s for Drift state to propagate...') logger.log('⏳ Waiting 5s for Drift state to propagate...')
await new Promise(resolve => setTimeout(resolve, 5000)) await new Promise(resolve => setTimeout(resolve, 5000))
try { try {
@@ -661,7 +662,7 @@ export async function closePosition(
needsVerification: true, // Flag for Position Manager needsVerification: true, // Flag for Position Manager
} }
} else { } else {
console.log('✅ Position verified closed on Drift') logger.log('✅ Position verified closed on Drift')
} }
} catch (verifyError) { } catch (verifyError) {
console.warn('⚠️ Could not verify position closure:', verifyError) console.warn('⚠️ Could not verify position closure:', verifyError)
@@ -712,7 +713,7 @@ async function retryWithBackoff<T>(
// Log successful execution time for rate limit monitoring // Log successful execution time for rate limit monitoring
if (attempt > 0) { if (attempt > 0) {
const totalTime = Date.now() - startTime const totalTime = Date.now() - startTime
console.log(`✅ Retry successful after ${totalTime}ms (${attempt} retries)`) logger.log(`✅ Retry successful after ${totalTime}ms (${attempt} retries)`)
// Log to database for analytics // Log to database for analytics
try { try {
@@ -756,8 +757,8 @@ async function retryWithBackoff<T>(
} }
const delay = baseDelay * Math.pow(2, attempt) const delay = baseDelay * Math.pow(2, attempt)
console.log(`⏳ Rate limited (429), retrying in ${delay / 1000}s... (attempt ${attempt + 1}/${maxRetries})`) logger.log(`⏳ Rate limited (429), retrying in ${delay / 1000}s... (attempt ${attempt + 1}/${maxRetries})`)
console.log(` Error context: ${errorMessage.substring(0, 100)}`) logger.log(` Error context: ${errorMessage.substring(0, 100)}`)
// Log rate limit hit to database // Log rate limit hit to database
try { try {
@@ -783,12 +784,12 @@ export async function cancelAllOrders(
symbol: string symbol: string
): Promise<{ success: boolean; cancelledCount?: number; error?: string }> { ): Promise<{ success: boolean; cancelledCount?: number; error?: string }> {
try { try {
console.log(`🗑️ Cancelling all orders for ${symbol}...`) logger.log(`🗑️ Cancelling all orders for ${symbol}...`)
// Ensure Drift service is initialized // Ensure Drift service is initialized
let driftService = getDriftService() let driftService = getDriftService()
if (!driftService) { if (!driftService) {
console.log('⚠️ Drift service not initialized, initializing now...') logger.log('⚠️ Drift service not initialized, initializing now...')
driftService = await initializeDriftService() driftService = await initializeDriftService()
} }
@@ -797,7 +798,7 @@ export async function cancelAllOrders(
const isDryRun = process.env.DRY_RUN === 'true' const isDryRun = process.env.DRY_RUN === 'true'
if (isDryRun) { if (isDryRun) {
console.log('🧪 DRY RUN: Simulating order cancellation') logger.log('🧪 DRY RUN: Simulating order cancellation')
return { success: true, cancelledCount: 0 } return { success: true, cancelledCount: 0 }
} }
@@ -827,12 +828,12 @@ export async function cancelAllOrders(
) )
if (ordersToCancel.length === 0) { if (ordersToCancel.length === 0) {
console.log('✅ No open orders to cancel') logger.log('✅ No open orders to cancel')
return { success: true, cancelledCount: 0 } return { success: true, cancelledCount: 0 }
} }
console.log(`📋 Found ${ordersToCancel.length} open orders to cancel (including trigger orders)`) logger.log(`📋 Found ${ordersToCancel.length} open orders to cancel (including trigger orders)`)
console.log(` (checked ${userAccount.orders.length} total order slots)`) logger.log(` (checked ${userAccount.orders.length} total order slots)`)
// Cancel all orders with retry logic for rate limits // Cancel all orders with retry logic for rate limits
const txSig = await retryWithBackoff(async () => { const txSig = await retryWithBackoff(async () => {
@@ -843,7 +844,7 @@ export async function cancelAllOrders(
) )
}) })
console.log(`✅ Orders cancelled! Transaction: ${txSig}`) logger.log(`✅ Orders cancelled! Transaction: ${txSig}`)
return { return {
success: true, success: true,
@@ -883,21 +884,21 @@ export async function emergencyCloseAll(): Promise<{
result: ClosePositionResult result: ClosePositionResult
}> }>
}> { }> {
console.log('🚨 EMERGENCY: Closing all positions') logger.log('🚨 EMERGENCY: Closing all positions')
try { try {
const driftService = getDriftService() const driftService = getDriftService()
const positions = await driftService.getAllPositions() const positions = await driftService.getAllPositions()
if (positions.length === 0) { if (positions.length === 0) {
console.log('✅ No positions to close') logger.log('✅ No positions to close')
return { success: true, results: [] } return { success: true, results: [] }
} }
const results = [] const results = []
for (const position of positions) { for (const position of positions) {
console.log(`🔴 Emergency closing ${position.symbol}...`) logger.log(`🔴 Emergency closing ${position.symbol}...`)
const result = await closeEntirePosition(position.symbol, 2.0) // Allow 2% slippage const result = await closeEntirePosition(position.symbol, 2.0) // Allow 2% slippage
results.push({ results.push({
symbol: position.symbol, symbol: position.symbol,
@@ -905,7 +906,7 @@ export async function emergencyCloseAll(): Promise<{
}) })
} }
console.log('✅ Emergency close complete') logger.log('✅ Emergency close complete')
return { return {
success: true, success: true,

View File

@@ -6,6 +6,7 @@
*/ */
import fs from 'fs' import fs from 'fs'
import { logger } from '../utils/logger'
import path from 'path' import path from 'path'
class DriftHealthMonitor { class DriftHealthMonitor {
@@ -20,13 +21,13 @@ class DriftHealthMonitor {
*/ */
start(): void { start(): void {
if (this.isMonitoring) { if (this.isMonitoring) {
console.log('⚠️ Drift health monitor already running') logger.log('⚠️ Drift health monitor already running')
return return
} }
this.isMonitoring = true this.isMonitoring = true
console.log('🏥 Drift health monitor started') logger.log('🏥 Drift health monitor started')
console.log(` Threshold: ${this.errorThreshold} accountUnsubscribe errors in ${this.errorWindow/1000}s`) logger.log(` Threshold: ${this.errorThreshold} accountUnsubscribe errors in ${this.errorWindow/1000}s`)
// Check error counts every 3 seconds (was 10s - faster response to memory leak) // Check error counts every 3 seconds (was 10s - faster response to memory leak)
this.checkInterval = setInterval(() => { this.checkInterval = setInterval(() => {
@@ -43,7 +44,7 @@ class DriftHealthMonitor {
this.checkInterval = null this.checkInterval = null
} }
this.isMonitoring = false this.isMonitoring = false
console.log('🏥 Drift health monitor stopped') logger.log('🏥 Drift health monitor stopped')
} }
/** /**
@@ -103,8 +104,8 @@ class DriftHealthMonitor {
`Drift SDK health check failed: ${this.errorCounts.size} accountUnsubscribe errors\nTimestamp: ${new Date().toISOString()}\n`, `Drift SDK health check failed: ${this.errorCounts.size} accountUnsubscribe errors\nTimestamp: ${new Date().toISOString()}\n`,
'utf-8' 'utf-8'
) )
console.log(`✅ Restart flag created at ${restartFlagPath}`) logger.log(`✅ Restart flag created at ${restartFlagPath}`)
console.log(' watch-restart.sh will restart container within 10 seconds') logger.log(' watch-restart.sh will restart container within 10 seconds')
} catch (error) { } catch (error) {
console.error('❌ Failed to create restart flag:', error) console.error('❌ Failed to create restart flag:', error)
} }

View File

@@ -1,3 +1,5 @@
import { logger } from '../utils/logger'
/** /**
* Telegram notification utilities * Telegram notification utilities
* *
@@ -36,7 +38,7 @@ export async function sendPositionClosedNotification(options: TelegramNotificati
const chatId = process.env.TELEGRAM_CHAT_ID const chatId = process.env.TELEGRAM_CHAT_ID
if (!token || !chatId) { if (!token || !chatId) {
console.log('⚠️ Telegram credentials not configured, skipping notification') logger.log('⚠️ Telegram credentials not configured, skipping notification')
return return
} }
@@ -77,7 +79,7 @@ ${options.maxDrawdown ? `\n📉 Max Drawdown: -${options.maxDrawdown.toFixed(2)}
const errorData = await response.json() const errorData = await response.json()
console.error('❌ Telegram notification failed:', errorData) console.error('❌ Telegram notification failed:', errorData)
} else { } else {
console.log('✅ Telegram notification sent') logger.log('✅ Telegram notification sent')
} }
} catch (error) { } catch (error) {
console.error('❌ Error sending Telegram notification:', error) console.error('❌ Error sending Telegram notification:', error)
@@ -243,7 +245,7 @@ async function sendWithdrawalNotification(options: TelegramWithdrawalOptions): P
const chatId = process.env.TELEGRAM_CHAT_ID const chatId = process.env.TELEGRAM_CHAT_ID
if (!token || !chatId) { if (!token || !chatId) {
console.log('⚠️ Telegram credentials not configured, skipping notification') logger.log('⚠️ Telegram credentials not configured, skipping notification')
return return
} }
@@ -273,7 +275,7 @@ async function sendWithdrawalNotification(options: TelegramWithdrawalOptions): P
const errorData = await response.json() const errorData = await response.json()
console.error('❌ Telegram notification failed:', errorData) console.error('❌ Telegram notification failed:', errorData)
} else { } else {
console.log('✅ Telegram withdrawal notification sent') logger.log('✅ Telegram withdrawal notification sent')
} }
} catch (error) { } catch (error) {
console.error('❌ Error sending Telegram notification:', error) console.error('❌ Error sending Telegram notification:', error)
@@ -290,7 +292,7 @@ export async function sendTelegramMessage(message: string): Promise<void> {
const chatId = process.env.TELEGRAM_CHAT_ID const chatId = process.env.TELEGRAM_CHAT_ID
if (!token || !chatId) { if (!token || !chatId) {
console.log('⚠️ Telegram credentials not configured, skipping notification') logger.log('⚠️ Telegram credentials not configured, skipping notification')
return return
} }

View File

@@ -5,6 +5,7 @@
*/ */
import { Connection, PublicKey } from '@solana/web3.js' import { Connection, PublicKey } from '@solana/web3.js'
import { logger } from '../utils/logger'
import { PriceServiceConnection } from '@pythnetwork/price-service-client' import { PriceServiceConnection } from '@pythnetwork/price-service-client'
import { getMarketConfig } from '../../config/trading' import { getMarketConfig } from '../../config/trading'
@@ -47,7 +48,7 @@ export class PythPriceMonitor {
}, },
}) })
console.log('✅ Pyth price monitor created') logger.log('✅ Pyth price monitor created')
} }
/** /**
@@ -59,7 +60,7 @@ export class PythPriceMonitor {
return return
} }
console.log('🚀 Starting Pyth price monitor for:', config.symbols) logger.log('🚀 Starting Pyth price monitor for:', config.symbols)
try { try {
// Get Pyth price feed IDs for all symbols // Get Pyth price feed IDs for all symbols
@@ -68,7 +69,7 @@ export class PythPriceMonitor {
return marketConfig.pythPriceFeedId return marketConfig.pythPriceFeedId
}) })
console.log('📡 Subscribing to Pyth WebSocket...') logger.log('📡 Subscribing to Pyth WebSocket...')
// Subscribe to Pyth WebSocket for real-time updates // Subscribe to Pyth WebSocket for real-time updates
this.priceService.subscribePriceFeedUpdates(priceIds, (priceFeed) => { this.priceService.subscribePriceFeedUpdates(priceIds, (priceFeed) => {
@@ -112,13 +113,13 @@ export class PythPriceMonitor {
} }
}) })
console.log('✅ Pyth WebSocket subscribed') logger.log('✅ Pyth WebSocket subscribed')
// Start polling fallback (every 2 seconds) in case WebSocket fails // Start polling fallback (every 2 seconds) in case WebSocket fails
this.startPollingFallback(config) this.startPollingFallback(config)
this.isMonitoring = true this.isMonitoring = true
console.log('✅ Price monitoring active') logger.log('✅ Price monitoring active')
} catch (error) { } catch (error) {
console.error('❌ Failed to start price monitor:', error) console.error('❌ Failed to start price monitor:', error)
@@ -130,7 +131,7 @@ export class PythPriceMonitor {
* Polling fallback - checks prices every 2 seconds via RPC * Polling fallback - checks prices every 2 seconds via RPC
*/ */
private startPollingFallback(config: PriceMonitorConfig): void { private startPollingFallback(config: PriceMonitorConfig): void {
console.log('🔄 Starting polling fallback (every 2s)...') logger.log('🔄 Starting polling fallback (every 2s)...')
for (const symbol of config.symbols) { for (const symbol of config.symbols) {
const interval = setInterval(async () => { const interval = setInterval(async () => {
@@ -140,7 +141,7 @@ export class PythPriceMonitor {
const timeSinceUpdate = Date.now() - lastUpdate const timeSinceUpdate = Date.now() - lastUpdate
if (timeSinceUpdate > 5000) { if (timeSinceUpdate > 5000) {
console.log(`⚠️ WebSocket stale for ${symbol}, using polling fallback`) logger.log(`⚠️ WebSocket stale for ${symbol}, using polling fallback`)
await this.fetchPriceViaRPC(symbol, config.onPriceUpdate) await this.fetchPriceViaRPC(symbol, config.onPriceUpdate)
} }
} catch (error) { } catch (error) {
@@ -154,7 +155,7 @@ export class PythPriceMonitor {
this.pollingIntervals.set(symbol, interval) this.pollingIntervals.set(symbol, interval)
} }
console.log('✅ Polling fallback active') logger.log('✅ Polling fallback active')
} }
/** /**
@@ -223,7 +224,7 @@ export class PythPriceMonitor {
return return
} }
console.log('🛑 Stopping price monitor...') logger.log('🛑 Stopping price monitor...')
// Clear polling intervals // Clear polling intervals
this.pollingIntervals.forEach(interval => clearInterval(interval)) this.pollingIntervals.forEach(interval => clearInterval(interval))
@@ -237,7 +238,7 @@ export class PythPriceMonitor {
this.lastUpdateTime.clear() this.lastUpdateTime.clear()
this.isMonitoring = false this.isMonitoring = false
console.log('✅ Price monitor stopped') logger.log('✅ Price monitor stopped')
} }
} }

View File

@@ -6,6 +6,7 @@
*/ */
import { getInitializedPositionManager } from '../trading/position-manager' import { getInitializedPositionManager } from '../trading/position-manager'
import { logger } from '../utils/logger'
import { initializeDriftService } from '../drift/client' import { initializeDriftService } from '../drift/client'
import { getPrismaClient, createTrade } from '../database/trades' import { getPrismaClient, createTrade } from '../database/trades'
import { getMarketConfig, getMergedConfig } from '../../config/trading' import { getMarketConfig, getMergedConfig } from '../../config/trading'
@@ -25,16 +26,16 @@ export async function initializePositionManagerOnStartup() {
initStarted = true initStarted = true
console.log('🚀 Initializing Position Manager on startup...') logger.log('🚀 Initializing Position Manager on startup...')
try { try {
// CRITICAL: Run database sync validator FIRST to clean up duplicates // CRITICAL: Run database sync validator FIRST to clean up duplicates
const { validateAllOpenTrades } = await import('../database/sync-validator') const { validateAllOpenTrades } = await import('../database/sync-validator')
console.log('🔍 Running database sync validation before Position Manager init...') logger.log('🔍 Running database sync validation before Position Manager init...')
const validationResult = await validateAllOpenTrades() const validationResult = await validateAllOpenTrades()
if (validationResult.ghosts > 0) { if (validationResult.ghosts > 0) {
console.log(`✅ Cleaned up ${validationResult.ghosts} ghost/duplicate trades`) logger.log(`✅ Cleaned up ${validationResult.ghosts} ghost/duplicate trades`)
} }
// Then validate open trades against Drift positions // Then validate open trades against Drift positions
@@ -46,28 +47,28 @@ export async function initializePositionManagerOnStartup() {
const manager = await getInitializedPositionManager() const manager = await getInitializedPositionManager()
const status = manager.getStatus() const status = manager.getStatus()
console.log(`✅ Position Manager ready - ${status.activeTradesCount} active trades`) logger.log(`✅ Position Manager ready - ${status.activeTradesCount} active trades`)
if (status.activeTradesCount > 0) { if (status.activeTradesCount > 0) {
console.log(`📊 Monitoring: ${status.symbols.join(', ')}`) logger.log(`📊 Monitoring: ${status.symbols.join(', ')}`)
} }
// CRITICAL (Dec 2, 2025): Start data cleanup service for 4-week retention // CRITICAL (Dec 2, 2025): Start data cleanup service for 4-week retention
// User directive: "we want to store the data for 4 weeks" // User directive: "we want to store the data for 4 weeks"
// Runs daily at 3 AM to delete MarketData records older than 28 days // Runs daily at 3 AM to delete MarketData records older than 28 days
console.log('🧹 Starting data cleanup service...') logger.log('🧹 Starting data cleanup service...')
startDataCleanup() startDataCleanup()
// Start blocked signal price tracking // Start blocked signal price tracking
console.log('🔬 Starting blocked signal price tracker...') logger.log('🔬 Starting blocked signal price tracker...')
startBlockedSignalTracking() startBlockedSignalTracking()
// Start stop hunt revenge tracker // Start stop hunt revenge tracker
console.log('🎯 Starting stop hunt revenge tracker...') logger.log('🎯 Starting stop hunt revenge tracker...')
await startStopHuntTracking() await startStopHuntTracking()
// Start smart entry validation queue (Nov 30, 2025) // Start smart entry validation queue (Nov 30, 2025)
console.log('🧠 Starting smart entry validation system...') logger.log('🧠 Starting smart entry validation system...')
await startSmartValidation() await startSmartValidation()
} catch (error) { } catch (error) {
console.error('❌ Failed to initialize Position Manager on startup:', error) console.error('❌ Failed to initialize Position Manager on startup:', error)
@@ -107,11 +108,11 @@ async function validateOpenTrades() {
const allTradesToCheck = [...openTrades, ...recentlyClosedTrades] const allTradesToCheck = [...openTrades, ...recentlyClosedTrades]
if (allTradesToCheck.length === 0) { if (allTradesToCheck.length === 0) {
console.log('✅ No open trades to validate') logger.log('✅ No open trades to validate')
return return
} }
console.log(`🔍 Validating ${openTrades.length} open + ${recentlyClosedTrades.length} recently closed trades against Drift...`) logger.log(`🔍 Validating ${openTrades.length} open + ${recentlyClosedTrades.length} recently closed trades against Drift...`)
// CRITICAL: Group trades by symbol to handle multiple DB entries for same Drift position // CRITICAL: Group trades by symbol to handle multiple DB entries for same Drift position
// This prevents reopening old closed trades when only the most recent should be restored // This prevents reopening old closed trades when only the most recent should be restored
@@ -135,7 +136,7 @@ async function validateOpenTrades() {
// Close any older trades BEFORE validating the most recent // Close any older trades BEFORE validating the most recent
for (const oldTrade of olderTrades) { for (const oldTrade of olderTrades) {
if (oldTrade.exitReason === null) { if (oldTrade.exitReason === null) {
console.log(`🗑️ Closing duplicate old trade: ${oldTrade.id} (${symbol}, created ${oldTrade.createdAt.toISOString()})`) logger.log(`🗑️ Closing duplicate old trade: ${oldTrade.id} (${symbol}, created ${oldTrade.createdAt.toISOString()})`)
await prisma.trade.update({ await prisma.trade.update({
where: { id: oldTrade.id }, where: { id: oldTrade.id },
data: { data: {
@@ -161,8 +162,8 @@ async function validateOpenTrades() {
if (!position || position.size === 0) { if (!position || position.size === 0) {
// No position on Drift // No position on Drift
if (trade.status === 'open') { if (trade.status === 'open') {
console.log(`⚠️ PHANTOM TRADE: ${trade.symbol} marked open in DB but not found on Drift`) logger.log(`⚠️ PHANTOM TRADE: ${trade.symbol} marked open in DB but not found on Drift`)
console.log(` 🗑️ Auto-closing phantom trade...`) logger.log(` 🗑️ Auto-closing phantom trade...`)
await prisma.trade.update({ await prisma.trade.update({
where: { id: trade.id }, where: { id: trade.id },
@@ -184,17 +185,17 @@ async function validateOpenTrades() {
const driftDirection = position.side.toLowerCase() as 'long' | 'short' const driftDirection = position.side.toLowerCase() as 'long' | 'short'
if (driftDirection !== trade.direction) { if (driftDirection !== trade.direction) {
console.log(`⚠️ DIRECTION MISMATCH: ${trade.symbol} DB=${trade.direction} Drift=${driftDirection}`) logger.log(`⚠️ DIRECTION MISMATCH: ${trade.symbol} DB=${trade.direction} Drift=${driftDirection}`)
continue continue
} }
// CRITICAL: If DB says closed but Drift shows open, restore tracking! // CRITICAL: If DB says closed but Drift shows open, restore tracking!
if (trade.exitReason !== null) { if (trade.exitReason !== null) {
console.log(`🔴 CRITICAL: ${trade.symbol} marked as CLOSED in DB but still OPEN on Drift!`) logger.log(`🔴 CRITICAL: ${trade.symbol} marked as CLOSED in DB but still OPEN on Drift!`)
console.log(` DB entry: $${trade.entryPrice.toFixed(2)} | Drift entry: $${position.entryPrice.toFixed(2)}`) logger.log(` DB entry: $${trade.entryPrice.toFixed(2)} | Drift entry: $${position.entryPrice.toFixed(2)}`)
console.log(` DB exit: ${trade.exitReason} at ${trade.exitTime?.toISOString()}`) logger.log(` DB exit: ${trade.exitReason} at ${trade.exitTime?.toISOString()}`)
console.log(` Drift: ${position.size} ${trade.symbol} ${driftDirection} @ $${position.entryPrice.toFixed(2)}`) logger.log(` Drift: ${position.size} ${trade.symbol} ${driftDirection} @ $${position.entryPrice.toFixed(2)}`)
console.log(` 🔄 Reopening trade and correcting entry price to match Drift...`) logger.log(` 🔄 Reopening trade and correcting entry price to match Drift...`)
// Calculate position size in USD using Drift's entry price // Calculate position size in USD using Drift's entry price
const currentPrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex) const currentPrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex)
@@ -213,9 +214,9 @@ async function validateOpenTrades() {
} }
}) })
console.log(` ✅ Trade restored with corrected entry: $${position.entryPrice.toFixed(2)} (was $${trade.entryPrice.toFixed(2)})`) logger.log(` ✅ Trade restored with corrected entry: $${position.entryPrice.toFixed(2)} (was $${trade.entryPrice.toFixed(2)})`)
} else { } else {
console.log(`${trade.symbol} ${trade.direction}: Position verified on Drift`) logger.log(`${trade.symbol} ${trade.direction}: Position verified on Drift`)
} }
// CRITICAL FIX (Nov 16, 2025): Restore missing on-chain orders // CRITICAL FIX (Nov 16, 2025): Restore missing on-chain orders
@@ -255,11 +256,11 @@ async function restoreOrdersIfMissing(
const hasOrders = position.orders && position.orders.length > 0 const hasOrders = position.orders && position.orders.length > 0
if (hasOrders) { if (hasOrders) {
console.log(`${trade.symbol} has ${position.orders.length} on-chain orders - protection active`) logger.log(`${trade.symbol} has ${position.orders.length} on-chain orders - protection active`)
return // Orders exist, nothing to do return // Orders exist, nothing to do
} }
console.log(`⚠️ ${trade.symbol} has NO on-chain orders - restoring TP/SL protection...`) logger.log(`⚠️ ${trade.symbol} has NO on-chain orders - restoring TP/SL protection...`)
// Import order placement function // Import order placement function
const { placeExitOrders } = await import('../drift/orders') const { placeExitOrders } = await import('../drift/orders')
@@ -278,11 +279,11 @@ async function restoreOrdersIfMissing(
}) })
if (result.success) { if (result.success) {
console.log(`✅ Orders restored for ${trade.symbol}:`) logger.log(`✅ Orders restored for ${trade.symbol}:`)
console.log(` TP1: $${trade.takeProfit1Price.toFixed(4)} (75%)`) logger.log(` TP1: $${trade.takeProfit1Price.toFixed(4)} (75%)`)
console.log(` TP2: $${trade.takeProfit2Price.toFixed(4)} (runner trigger)`) logger.log(` TP2: $${trade.takeProfit2Price.toFixed(4)} (runner trigger)`)
console.log(` SL: $${trade.stopLossPrice.toFixed(4)}`) logger.log(` SL: $${trade.stopLossPrice.toFixed(4)}`)
console.log(` TX: ${result.signatures?.[0]?.slice(0, 8)}...`) logger.log(` TX: ${result.signatures?.[0]?.slice(0, 8)}...`)
// Update database with order transaction signatures // Update database with order transaction signatures
await prisma.trade.update({ await prisma.trade.update({
@@ -324,17 +325,17 @@ async function detectOrphanedPositions() {
const prisma = getPrismaClient() const prisma = getPrismaClient()
const driftService = await initializeDriftService() const driftService = await initializeDriftService()
console.log('🔍 Checking for orphaned positions on Drift...') logger.log('🔍 Checking for orphaned positions on Drift...')
// Get all open positions from Drift // Get all open positions from Drift
const driftPositions = await driftService.getAllPositions() const driftPositions = await driftService.getAllPositions()
if (driftPositions.length === 0) { if (driftPositions.length === 0) {
console.log('✅ No positions on Drift') logger.log('✅ No positions on Drift')
return return
} }
console.log(`🔍 Found ${driftPositions.length} positions on Drift, checking database...`) logger.log(`🔍 Found ${driftPositions.length} positions on Drift, checking database...`)
// Get all open trades from database // Get all open trades from database
const openTrades = await prisma.trade.findMany({ const openTrades = await prisma.trade.findMany({
@@ -349,7 +350,7 @@ async function detectOrphanedPositions() {
const positionKey = `${position.symbol}-${position.side.toLowerCase()}` const positionKey = `${position.symbol}-${position.side.toLowerCase()}`
if (trackedSymbols.has(positionKey)) { if (trackedSymbols.has(positionKey)) {
console.log(`${position.symbol} ${position.side} tracked in database`) logger.log(`${position.symbol} ${position.side} tracked in database`)
continue continue
} }
@@ -359,12 +360,12 @@ async function detectOrphanedPositions() {
const currentPrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex) const currentPrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex)
const positionSizeUSD = Math.abs(position.size) * currentPrice const positionSizeUSD = Math.abs(position.size) * currentPrice
console.log(`🚨 ORPHAN POSITION DETECTED!`) logger.log(`🚨 ORPHAN POSITION DETECTED!`)
console.log(` Symbol: ${position.symbol}`) logger.log(` Symbol: ${position.symbol}`)
console.log(` Direction: ${position.side}`) logger.log(` Direction: ${position.side}`)
console.log(` Size: ${Math.abs(position.size)} (notional: $${positionSizeUSD.toFixed(2)})`) logger.log(` Size: ${Math.abs(position.size)} (notional: $${positionSizeUSD.toFixed(2)})`)
console.log(` Entry: $${position.entryPrice.toFixed(4)}`) logger.log(` Entry: $${position.entryPrice.toFixed(4)}`)
console.log(` Current: $${currentPrice.toFixed(4)}`) logger.log(` Current: $${currentPrice.toFixed(4)}`)
// Log to persistent file // Log to persistent file
logCriticalError('ORPHAN POSITION DETECTED - Creating retroactive database record', { logCriticalError('ORPHAN POSITION DETECTED - Creating retroactive database record', {
@@ -398,7 +399,7 @@ async function detectOrphanedPositions() {
: entryPrice * (1 - config.takeProfit2Percent / 100) : entryPrice * (1 - config.takeProfit2Percent / 100)
// Create retroactive database record // Create retroactive database record
console.log(`🔄 Creating retroactive database record...`) logger.log(`🔄 Creating retroactive database record...`)
const trade = await createTrade({ const trade = await createTrade({
positionId: `ORPHAN-${Date.now()}`, // Fake position ID since we don't have transaction positionId: `ORPHAN-${Date.now()}`, // Fake position ID since we don't have transaction
symbol: position.symbol, symbol: position.symbol,
@@ -418,7 +419,7 @@ async function detectOrphanedPositions() {
status: 'open', status: 'open',
}) })
console.log(`✅ Retroactive database record created: ${trade.id}`) logger.log(`✅ Retroactive database record created: ${trade.id}`)
// Send Telegram notification // Send Telegram notification
try { try {
@@ -438,7 +439,7 @@ async function detectOrphanedPositions() {
console.error('Failed to send orphan notification:', telegramError) console.error('Failed to send orphan notification:', telegramError)
} }
console.log(`🎯 Orphan position now tracked and monitored`) logger.log(`🎯 Orphan position now tracked and monitored`)
} catch (recoveryError) { } catch (recoveryError) {
console.error(`❌ Failed to recover orphan position ${position.symbol}:`, recoveryError) console.error(`❌ Failed to recover orphan position ${position.symbol}:`, recoveryError)
@@ -449,7 +450,7 @@ async function detectOrphanedPositions() {
} }
} }
console.log('✅ Orphan position detection complete') logger.log('✅ Orphan position detection complete')
} catch (error) { } catch (error) {
console.error('❌ Error detecting orphaned positions:', error) console.error('❌ Error detecting orphaned positions:', error)

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@
*/ */
import { getRecentSignals } from '../database/trades' import { getRecentSignals } from '../database/trades'
import { logger } from '../utils/logger'
export interface SignalQualityResult { export interface SignalQualityResult {
score: number score: number
@@ -235,7 +236,7 @@ export async function scoreSignalQuality(params: {
(params.currentPrice - recentSignals.oppositeDirectionPrice) / recentSignals.oppositeDirectionPrice * 100 (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) { if (priceChangePercent < 2.0) {
// Small price move = consolidation/chop = BAD // Small price move = consolidation/chop = BAD

View File

@@ -14,6 +14,7 @@
*/ */
import { getMarketDataCache } from './market-data-cache' import { getMarketDataCache } from './market-data-cache'
import { logger } from '../utils/logger'
import { getPythPriceMonitor } from '../pyth/price-monitor' import { getPythPriceMonitor } from '../pyth/price-monitor'
export interface QueuedSignal { export interface QueuedSignal {
@@ -85,7 +86,7 @@ export class SmartEntryTimer {
monitorIntervalMs: 15000 // 15 seconds monitorIntervalMs: 15000 // 15 seconds
} }
console.log('💡 Smart Entry Timer initialized:', { logger.log('💡 Smart Entry Timer initialized:', {
enabled: this.config.enabled, enabled: this.config.enabled,
maxWait: `${this.config.maxWaitMs / 1000}s`, maxWait: `${this.config.maxWaitMs / 1000}s`,
pullback: `${this.config.pullbackMin}-${this.config.pullbackMax}%`, pullback: `${this.config.pullbackMin}-${this.config.pullbackMax}%`,
@@ -137,10 +138,10 @@ export class SmartEntryTimer {
this.queuedSignals.set(signal.id, signal) this.queuedSignals.set(signal.id, signal)
console.log(`📥 Smart Entry: Queued signal ${signal.id}`) logger.log(`📥 Smart Entry: Queued signal ${signal.id}`)
console.log(` ${signal.direction.toUpperCase()} ${signal.symbol} @ $${signal.signalPrice.toFixed(2)}`) logger.log(` ${signal.direction.toUpperCase()} ${signal.symbol} @ $${signal.signalPrice.toFixed(2)}`)
console.log(` Target pullback: ${this.config.pullbackMin}-${this.config.pullbackMax}%`) logger.log(` Target pullback: ${this.config.pullbackMin}-${this.config.pullbackMax}%`)
console.log(` Max wait: ${this.config.maxWaitMs / 1000}s`) logger.log(` Max wait: ${this.config.maxWaitMs / 1000}s`)
// Start monitoring if not already running // Start monitoring if not already running
if (!this.monitoringInterval) { if (!this.monitoringInterval) {
@@ -156,7 +157,7 @@ export class SmartEntryTimer {
private startMonitoring(): void { private startMonitoring(): void {
if (this.monitoringInterval) return 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.monitoringInterval = setInterval(() => {
this.checkAllSignals() this.checkAllSignals()
@@ -170,7 +171,7 @@ export class SmartEntryTimer {
if (this.monitoringInterval) { if (this.monitoringInterval) {
clearInterval(this.monitoringInterval) clearInterval(this.monitoringInterval)
this.monitoringInterval = null 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 // Check for timeout
if (now >= signal.expiresAt) { 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 priceMonitor = getPythPriceMonitor()
const latestPrice = priceMonitor.getCachedPrice(signal.symbol) const latestPrice = priceMonitor.getCachedPrice(signal.symbol)
const currentPrice = latestPrice?.price || signal.signalPrice const currentPrice = latestPrice?.price || signal.signalPrice
@@ -210,7 +211,7 @@ export class SmartEntryTimer {
const latestPrice = priceMonitor.getCachedPrice(signal.symbol) const latestPrice = priceMonitor.getCachedPrice(signal.symbol)
if (!latestPrice || !latestPrice.price) { 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 return
} }
@@ -234,18 +235,18 @@ export class SmartEntryTimer {
} }
// Log check // Log check
console.log(`🔍 Smart Entry: Checking ${signal.symbol} (check #${signal.checksPerformed})`) logger.log(`🔍 Smart Entry: Checking ${signal.symbol} (check #${signal.checksPerformed})`)
console.log(` Signal: $${signal.signalPrice.toFixed(2)} → Current: $${currentPrice.toFixed(2)}`) logger.log(` Signal: $${signal.signalPrice.toFixed(2)} → Current: $${currentPrice.toFixed(2)}`)
console.log(` Pullback: ${pullbackMagnitude.toFixed(2)}% (target ${signal.targetPullbackMin}-${signal.targetPullbackMax}%)`) logger.log(` Pullback: ${pullbackMagnitude.toFixed(2)}% (target ${signal.targetPullbackMin}-${signal.targetPullbackMax}%)`)
// Check if pullback is in target range // Check if pullback is in target range
if (pullbackMagnitude < signal.targetPullbackMin) { 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 return
} }
if (pullbackMagnitude > signal.targetPullbackMax) { 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 return
} }
@@ -262,22 +263,22 @@ export class SmartEntryTimer {
const now = Date.now() const now = Date.now()
const dataAge = (now - latestMetrics.timestamp) / 1000 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) // 1. ADX degradation check (original logic)
if (latestMetrics.adx) { if (latestMetrics.adx) {
const adxDrop = signal.signalADX - latestMetrics.adx const adxDrop = signal.signalADX - latestMetrics.adx
if (adxDrop > signal.adxTolerance) { 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.status = 'cancelled'
signal.executionReason = 'manual_override' signal.executionReason = 'manual_override'
this.queuedSignals.delete(signal.id) this.queuedSignals.delete(signal.id)
console.log(` 🚫 Signal cancelled: ADX degradation exceeded tolerance`) logger.log(` 🚫 Signal cancelled: ADX degradation exceeded tolerance`)
return 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) // 2. Volume degradation check (NEW)
@@ -289,15 +290,15 @@ export class SmartEntryTimer {
// Cancel if volume dropped >40% // Cancel if volume dropped >40%
if (volumeDrop > 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.status = 'cancelled'
signal.executionReason = 'manual_override' signal.executionReason = 'manual_override'
this.queuedSignals.delete(signal.id) this.queuedSignals.delete(signal.id)
console.log(` 🚫 Signal cancelled: Volume degradation - momentum fading`) logger.log(` 🚫 Signal cancelled: Volume degradation - momentum fading`)
return 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) // 3. RSI reversal check (NEW)
@@ -309,26 +310,26 @@ export class SmartEntryTimer {
if (signal.direction === 'long') { if (signal.direction === 'long') {
// LONG: Cancel if RSI dropped into oversold (<30) // LONG: Cancel if RSI dropped into oversold (<30)
if (originalRSI >= 40 && currentRSI < 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.status = 'cancelled'
signal.executionReason = 'manual_override' signal.executionReason = 'manual_override'
this.queuedSignals.delete(signal.id) this.queuedSignals.delete(signal.id)
console.log(` 🚫 Signal cancelled: RSI reversal - trend weakening`) logger.log(` 🚫 Signal cancelled: RSI reversal - trend weakening`)
return return
} }
} else { } else {
// SHORT: Cancel if RSI rose into overbought (>70) // SHORT: Cancel if RSI rose into overbought (>70)
if (originalRSI <= 60 && currentRSI > 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.status = 'cancelled'
signal.executionReason = 'manual_override' signal.executionReason = 'manual_override'
this.queuedSignals.delete(signal.id) this.queuedSignals.delete(signal.id)
console.log(` 🚫 Signal cancelled: RSI reversal - trend weakening`) logger.log(` 🚫 Signal cancelled: RSI reversal - trend weakening`)
return return
} }
} }
console.log(` ✅ RSI: ${originalRSI.toFixed(1)}${currentRSI.toFixed(1)}`) logger.log(` ✅ RSI: ${originalRSI.toFixed(1)}${currentRSI.toFixed(1)}`)
} }
// 4. MAGAP divergence check (NEW) // 4. MAGAP divergence check (NEW)
@@ -338,36 +339,36 @@ export class SmartEntryTimer {
if (signal.direction === 'long' && currentMAGap < -1.0) { if (signal.direction === 'long' && currentMAGap < -1.0) {
// LONG but MAs now bearish diverging // 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.status = 'cancelled'
signal.executionReason = 'manual_override' signal.executionReason = 'manual_override'
this.queuedSignals.delete(signal.id) this.queuedSignals.delete(signal.id)
console.log(` 🚫 Signal cancelled: MA structure turned bearish`) logger.log(` 🚫 Signal cancelled: MA structure turned bearish`)
return return
} }
if (signal.direction === 'short' && currentMAGap > 1.0) { if (signal.direction === 'short' && currentMAGap > 1.0) {
// SHORT but MAs now bullish diverging // 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.status = 'cancelled'
signal.executionReason = 'manual_override' signal.executionReason = 'manual_override'
this.queuedSignals.delete(signal.id) this.queuedSignals.delete(signal.id)
console.log(` 🚫 Signal cancelled: MA structure turned bullish`) logger.log(` 🚫 Signal cancelled: MA structure turned bullish`)
return 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 { } 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! // All conditions met - execute!
console.log(`✅ Smart Entry: OPTIMAL ENTRY CONFIRMED`) logger.log(`✅ Smart Entry: OPTIMAL ENTRY CONFIRMED`)
console.log(` Pullback: ${pullbackMagnitude.toFixed(2)}% (target ${signal.targetPullbackMin}-${signal.targetPullbackMax}%)`) logger.log(` Pullback: ${pullbackMagnitude.toFixed(2)}% (target ${signal.targetPullbackMin}-${signal.targetPullbackMax}%)`)
console.log(` Price improvement: $${signal.signalPrice.toFixed(2)}$${currentPrice.toFixed(2)}`) logger.log(` Price improvement: $${signal.signalPrice.toFixed(2)}$${currentPrice.toFixed(2)}`)
await this.executeSignal(signal, currentPrice, 'pullback_confirmed') await this.executeSignal(signal, currentPrice, 'pullback_confirmed')
} }
@@ -388,10 +389,10 @@ export class SmartEntryTimer {
const improvement = ((signal.signalPrice - entryPrice) / signal.signalPrice) * 100 const improvement = ((signal.signalPrice - entryPrice) / signal.signalPrice) * 100
const improvementDirection = signal.direction === 'long' ? improvement : -improvement const improvementDirection = signal.direction === 'long' ? improvement : -improvement
console.log(`🎯 Smart Entry: EXECUTING ${signal.direction.toUpperCase()} ${signal.symbol}`) logger.log(`🎯 Smart Entry: EXECUTING ${signal.direction.toUpperCase()} ${signal.symbol}`)
console.log(` Signal Price: $${signal.signalPrice.toFixed(2)}`) logger.log(` Signal Price: $${signal.signalPrice.toFixed(2)}`)
console.log(` Entry Price: $${entryPrice.toFixed(2)}`) logger.log(` Entry Price: $${entryPrice.toFixed(2)}`)
console.log(` Improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%`) logger.log(` Improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%`)
// Execute the actual trade through Drift // Execute the actual trade through Drift
try { try {
@@ -423,7 +424,7 @@ export class SmartEntryTimer {
signal.qualityScore 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 // Open position
const openResult = await openPosition({ const openResult = await openPosition({
@@ -439,7 +440,7 @@ export class SmartEntryTimer {
} }
const fillPrice = openResult.fillPrice! 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 // Calculate TP/SL prices
let tp1Percent = config.takeProfit1Percent let tp1Percent = config.takeProfit1Percent
@@ -505,7 +506,7 @@ export class SmartEntryTimer {
if (exitRes.success) { if (exitRes.success) {
exitOrderSignatures = exitRes.signatures || [] 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) { } catch (err) {
console.error(`❌ Smart Entry: Error placing exit orders:`, 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) { } catch (dbError) {
console.error(`❌ Smart Entry: Failed to save trade:`, dbError) console.error(`❌ Smart Entry: Failed to save trade:`, dbError)
const { logCriticalError } = await import('../utils/persistent-logger') const { logCriticalError } = await import('../utils/persistent-logger')
@@ -616,14 +617,14 @@ export class SmartEntryTimer {
} }
await positionManager.addTrade(activeTrade) await positionManager.addTrade(activeTrade)
console.log(`📊 Smart Entry: Added to Position Manager`) logger.log(`📊 Smart Entry: Added to Position Manager`)
} catch (pmError) { } catch (pmError) {
console.error(`❌ Smart Entry: Failed to add to Position Manager:`, pmError) console.error(`❌ Smart Entry: Failed to add to Position Manager:`, pmError)
} }
console.log(`✅ Smart Entry: Execution complete for ${signal.symbol}`) logger.log(`✅ Smart Entry: Execution complete for ${signal.symbol}`)
console.log(` Entry improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%`) logger.log(` Entry improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%`)
console.log(` Estimated value: $${(Math.abs(improvementDirection) / 100 * positionSizeUSD).toFixed(2)}`) logger.log(` Estimated value: $${(Math.abs(improvementDirection) / 100 * positionSizeUSD).toFixed(2)}`)
} catch (error) { } catch (error) {
console.error(`❌ Smart Entry: Execution error:`, error) console.error(`❌ Smart Entry: Execution error:`, error)
@@ -632,7 +633,7 @@ export class SmartEntryTimer {
// Remove from queue after brief delay (for logging) // Remove from queue after brief delay (for logging)
setTimeout(() => { setTimeout(() => {
this.queuedSignals.delete(signal.id) 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) { if (this.queuedSignals.size === 0) {
this.stopMonitoring() this.stopMonitoring()
@@ -688,7 +689,7 @@ export class SmartEntryTimer {
signal.status = 'cancelled' signal.status = 'cancelled'
this.queuedSignals.delete(signalId) this.queuedSignals.delete(signalId)
console.log(`🚫 Smart Entry: Cancelled signal ${signalId}`) logger.log(`🚫 Smart Entry: Cancelled signal ${signalId}`)
return true return true
} }
@@ -713,5 +714,5 @@ export function getSmartEntryTimer(): SmartEntryTimer {
export function startSmartEntryTracking(): void { export function startSmartEntryTracking(): void {
getSmartEntryTimer() 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 { getMarketDataCache } from './market-data-cache'
import { logger } from '../utils/logger'
import { getMergedConfig } from '../../config/trading' import { getMergedConfig } from '../../config/trading'
import { sendValidationNotification } from '../notifications/telegram' import { sendValidationNotification } from '../notifications/telegram'
@@ -49,7 +50,7 @@ class SmartValidationQueue {
private isMonitoring = false private isMonitoring = false
constructor() { constructor() {
console.log('🧠 Smart Validation Queue initialized') logger.log('🧠 Smart Validation Queue initialized')
} }
/** /**
@@ -110,8 +111,8 @@ class SmartValidationQueue {
} }
this.queue.set(signalId, queuedSignal) this.queue.set(signalId, queuedSignal)
console.log(`⏰ Smart validation queued: ${params.symbol} ${params.direction.toUpperCase()} @ $${params.originalPrice.toFixed(2)} (quality: ${params.qualityScore})`) logger.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(` Watching for ${queuedSignal.entryWindowMinutes}min: +${queuedSignal.confirmationThreshold}% confirms, ${queuedSignal.maxDrawdown}% abandons`)
// Send Telegram notification // Send Telegram notification
await sendValidationNotification({ await sendValidationNotification({
@@ -139,7 +140,7 @@ class SmartValidationQueue {
} }
this.isMonitoring = true 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 // Check every 30 seconds
this.monitoringInterval = setInterval(async () => { this.monitoringInterval = setInterval(async () => {
@@ -156,7 +157,7 @@ class SmartValidationQueue {
this.monitoringInterval = undefined this.monitoringInterval = undefined
} }
this.isMonitoring = false this.isMonitoring = false
console.log('⏸️ Smart validation monitoring stopped') logger.log('⏸️ Smart validation monitoring stopped')
} }
/** /**
@@ -171,7 +172,7 @@ class SmartValidationQueue {
return return
} }
console.log(`👁️ Smart validation check: ${pending.length} pending signals`) logger.log(`👁️ Smart validation check: ${pending.length} pending signals`)
for (const signal of pending) { for (const signal of pending) {
try { try {
@@ -195,7 +196,7 @@ class SmartValidationQueue {
// Check if expired (beyond entry window) // Check if expired (beyond entry window)
if (ageMinutes > signal.entryWindowMinutes) { if (ageMinutes > signal.entryWindowMinutes) {
signal.status = 'expired' 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 // Send Telegram notification
await sendValidationNotification({ await sendValidationNotification({
@@ -215,7 +216,7 @@ class SmartValidationQueue {
const cachedData = marketDataCache.get(signal.symbol) const cachedData = marketDataCache.get(signal.symbol)
if (!cachedData || !cachedData.currentPrice) { 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 return
} }
@@ -237,8 +238,8 @@ class SmartValidationQueue {
// Price moved up enough - CONFIRMED! // Price moved up enough - CONFIRMED!
signal.status = 'confirmed' signal.status = 'confirmed'
signal.validatedAt = now signal.validatedAt = now
console.log(`✅ LONG CONFIRMED: ${signal.symbol} moved +${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)}$${currentPrice.toFixed(2)})`) logger.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(` Validation time: ${ageMinutes.toFixed(1)} minutes, executing trade...`)
// Send Telegram notification // Send Telegram notification
await sendValidationNotification({ await sendValidationNotification({
@@ -257,8 +258,8 @@ class SmartValidationQueue {
} else if (priceChange <= signal.maxDrawdown) { } else if (priceChange <= signal.maxDrawdown) {
// Price moved down too much - ABANDON // Price moved down too much - ABANDON
signal.status = 'abandoned' signal.status = 'abandoned'
console.log(`❌ LONG ABANDONED: ${signal.symbol} dropped ${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)}$${currentPrice.toFixed(2)})`) logger.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(` Saved from potential loser after ${ageMinutes.toFixed(1)} minutes`)
// Send Telegram notification // Send Telegram notification
await sendValidationNotification({ await sendValidationNotification({
@@ -273,7 +274,7 @@ class SmartValidationQueue {
}) })
} else { } else {
// Still pending, log progress // 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 { } else {
// SHORT: Need price to move DOWN to confirm // SHORT: Need price to move DOWN to confirm
@@ -281,8 +282,8 @@ class SmartValidationQueue {
// Price moved down enough - CONFIRMED! // Price moved down enough - CONFIRMED!
signal.status = 'confirmed' signal.status = 'confirmed'
signal.validatedAt = now signal.validatedAt = now
console.log(`✅ SHORT CONFIRMED: ${signal.symbol} moved ${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)}$${currentPrice.toFixed(2)})`) logger.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(` Validation time: ${ageMinutes.toFixed(1)} minutes, executing trade...`)
// Send Telegram notification // Send Telegram notification
await sendValidationNotification({ await sendValidationNotification({
@@ -301,8 +302,8 @@ class SmartValidationQueue {
} else if (priceChange >= -signal.maxDrawdown) { } else if (priceChange >= -signal.maxDrawdown) {
// Price moved up too much - ABANDON // Price moved up too much - ABANDON
signal.status = 'abandoned' signal.status = 'abandoned'
console.log(`❌ SHORT ABANDONED: ${signal.symbol} rose +${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)}$${currentPrice.toFixed(2)})`) logger.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(` Saved from potential loser after ${ageMinutes.toFixed(1)} minutes`)
// Send Telegram notification // Send Telegram notification
await sendValidationNotification({ await sendValidationNotification({
@@ -317,7 +318,7 @@ class SmartValidationQueue {
}) })
} else { } else {
// Still pending, log progress // 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), validationDelayMinutes: (Date.now() - signal.blockedAt) / (1000 * 60),
} }
console.log(`🚀 Executing validated trade: ${signal.symbol} ${signal.direction.toUpperCase()} @ $${currentPrice.toFixed(2)}`) logger.log(`🚀 Executing validated trade: ${signal.symbol} ${signal.direction.toUpperCase()} @ $${currentPrice.toFixed(2)}`)
console.log(` Original signal: $${signal.originalPrice.toFixed(2)}, Quality: ${signal.qualityScore}`) logger.log(` Original signal: $${signal.originalPrice.toFixed(2)}, Quality: ${signal.qualityScore}`)
console.log(` Entry delay: ${payload.validationDelayMinutes.toFixed(1)} minutes`) logger.log(` Entry delay: ${payload.validationDelayMinutes.toFixed(1)} minutes`)
const response = await fetch(executeUrl, { const response = await fetch(executeUrl, {
method: 'POST', method: 'POST',
@@ -367,9 +368,9 @@ class SmartValidationQueue {
signal.executedAt = Date.now() signal.executedAt = Date.now()
signal.executionPrice = currentPrice signal.executionPrice = currentPrice
signal.tradeId = result.trade?.id signal.tradeId = result.trade?.id
console.log(`✅ Trade executed successfully: ${signal.symbol} ${signal.direction}`) logger.log(`✅ Trade executed successfully: ${signal.symbol} ${signal.direction}`)
console.log(` Trade ID: ${signal.tradeId}`) logger.log(` Trade ID: ${signal.tradeId}`)
console.log(` Entry: $${currentPrice.toFixed(2)}, Size: $${result.trade?.positionSizeUSD || 'unknown'}`) logger.log(` Entry: $${currentPrice.toFixed(2)}, Size: $${result.trade?.positionSizeUSD || 'unknown'}`)
// Send execution notification // Send execution notification
const slippage = ((currentPrice - signal.originalPrice) / signal.originalPrice) * 100 const slippage = ((currentPrice - signal.originalPrice) / signal.originalPrice) * 100
@@ -408,7 +409,7 @@ class SmartValidationQueue {
} }
if (cleaned > 0) { 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 { export function startSmartValidation(): void {
const queue = getSmartValidationQueue() 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 { getPrismaClient } from '../database/trades'
import { logger } from '../utils/logger'
import { initializeDriftService } from '../drift/client' import { initializeDriftService } from '../drift/client'
import { getPythPriceMonitor } from '../pyth/price-monitor' import { getPythPriceMonitor } from '../pyth/price-monitor'
@@ -70,7 +71,7 @@ export class StopHuntTracker {
}): Promise<void> { }): Promise<void> {
// Only track quality 85+ stop-outs (high-confidence trades) // Only track quality 85+ stop-outs (high-confidence trades)
if (params.originalQualityScore < 85) { 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 return
} }
@@ -93,9 +94,9 @@ export class StopHuntTracker {
} }
}) })
console.log(`🎯 STOP HUNT RECORDED: ${params.symbol} ${params.direction.toUpperCase()}`) logger.log(`🎯 STOP HUNT RECORDED: ${params.symbol} ${params.direction.toUpperCase()}`)
console.log(` Quality: ${params.originalQualityScore}, Loss: $${params.stopLossAmount.toFixed(2)}`) logger.log(` Quality: ${params.originalQualityScore}, Loss: $${params.stopLossAmount.toFixed(2)}`)
console.log(` Revenge window: 4 hours (expires ${revengeExpiresAt.toLocaleTimeString()})`) logger.log(` Revenge window: 4 hours (expires ${revengeExpiresAt.toLocaleTimeString()})`)
// Start monitoring if not already running // Start monitoring if not already running
if (!this.isMonitoring) { if (!this.isMonitoring) {
@@ -113,7 +114,7 @@ export class StopHuntTracker {
if (this.isMonitoring) return if (this.isMonitoring) return
this.isMonitoring = true this.isMonitoring = true
console.log('🔍 Stop Hunt Revenge Tracker: Monitoring started') logger.log('🔍 Stop Hunt Revenge Tracker: Monitoring started')
// Check every 30 seconds // Check every 30 seconds
monitoringInterval = setInterval(async () => { monitoringInterval = setInterval(async () => {
@@ -130,7 +131,7 @@ export class StopHuntTracker {
monitoringInterval = null monitoringInterval = null
} }
this.isMonitoring = false 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) { if (activeStopHunts.length === 0) {
// No active stop hunts, stop monitoring to save resources // No active stop hunts, stop monitoring to save resources
if (this.isMonitoring) { if (this.isMonitoring) {
console.log('📊 No active stop hunts - pausing monitoring') logger.log('📊 No active stop hunts - pausing monitoring')
this.stopMonitoring() this.stopMonitoring()
} }
return return
} }
console.log(`🔍 Checking ${activeStopHunts.length} active stop hunt(s)...`) logger.log(`🔍 Checking ${activeStopHunts.length} active stop hunt(s)...`)
for (const stopHunt of activeStopHunts) { for (const stopHunt of activeStopHunts) {
await this.checkStopHunt(stopHunt as StopHuntRecord) await this.checkStopHunt(stopHunt as StopHuntRecord)
@@ -211,7 +212,7 @@ export class StopHuntTracker {
const shouldRevenge = await this.shouldExecuteRevenge(stopHunt, currentPrice) const shouldRevenge = await this.shouldExecuteRevenge(stopHunt, currentPrice)
if (shouldRevenge) { 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) await this.executeRevengeTrade(stopHunt, currentPrice)
} }
@@ -260,7 +261,7 @@ export class StopHuntTracker {
lowestInZone: currentPrice, 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 return false
} }
@@ -274,17 +275,17 @@ export class StopHuntTracker {
// Check if we've been in zone for 90+ seconds (1.5 minutes) // Check if we've been in zone for 90+ seconds (1.5 minutes)
const timeInZone = now - stopHunt.firstCrossTime.getTime() const timeInZone = now - stopHunt.firstCrossTime.getTime()
if (timeInZone >= 90000) { // 90 seconds = 1.5 minutes if (timeInZone >= 90000) { // 90 seconds = 1.5 minutes
console.log(` ✅ LONG revenge: Price held below entry for ${(timeInZone/60000).toFixed(1)}min, confirmed!`) logger.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(` Entry ${originalEntryPrice.toFixed(2)} → Current ${currentPrice.toFixed(2)}`)
return true return true
} else { } 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 return false
} }
} else { } else {
// Price left revenge zone - reset timer and increment counter // Price left revenge zone - reset timer and increment counter
if (stopHunt.firstCrossTime) { 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({ await this.prisma.stopHunt.update({
where: { id: stopHunt.id }, where: { id: stopHunt.id },
data: { data: {
@@ -310,7 +311,7 @@ export class StopHuntTracker {
highestInZone: currentPrice, 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 return false
} }
@@ -324,17 +325,17 @@ export class StopHuntTracker {
// Check if we've been in zone for 90+ seconds (1.5 minutes) // Check if we've been in zone for 90+ seconds (1.5 minutes)
const timeInZone = now - stopHunt.firstCrossTime.getTime() const timeInZone = now - stopHunt.firstCrossTime.getTime()
if (timeInZone >= 90000) { // 90 seconds = 1.5 minutes if (timeInZone >= 90000) { // 90 seconds = 1.5 minutes
console.log(` ✅ SHORT revenge: Price held above entry for ${(timeInZone/60000).toFixed(1)}min, confirmed!`) logger.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(` Entry ${originalEntryPrice.toFixed(2)} → Current ${currentPrice.toFixed(2)}`)
return true return true
} else { } 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 return false
} }
} else { } else {
// Price left revenge zone - reset timer and increment counter // Price left revenge zone - reset timer and increment counter
if (stopHunt.firstCrossTime) { 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({ await this.prisma.stopHunt.update({
where: { id: stopHunt.id }, where: { id: stopHunt.id },
data: { data: {
@@ -354,9 +355,9 @@ export class StopHuntTracker {
*/ */
private async executeRevengeTrade(stopHunt: StopHuntRecord, currentPrice: number): Promise<void> { private async executeRevengeTrade(stopHunt: StopHuntRecord, currentPrice: number): Promise<void> {
try { try {
console.log(`🔥 EXECUTING REVENGE TRADE: ${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}`) logger.log(`🔥 EXECUTING REVENGE TRADE: ${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}`)
console.log(` Original loss: $${stopHunt.stopLossAmount.toFixed(2)}`) logger.log(` Original loss: $${stopHunt.stopLossAmount.toFixed(2)}`)
console.log(` Revenge size: 1.2x (getting our money back!)`) logger.log(` Revenge size: 1.2x (getting our money back!)`)
// CRITICAL: Validate current ADX from 1-minute data cache // CRITICAL: Validate current ADX from 1-minute data cache
// Block revenge if trend has weakened (ADX < 20) // Block revenge if trend has weakened (ADX < 20)
@@ -368,10 +369,10 @@ export class StopHuntTracker {
const currentADX = cachedData.adx const currentADX = cachedData.adx
const dataAge = Date.now() - cachedData.timestamp 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) { 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 // Update database with failed reason
await this.prisma.stopHunt.update({ await this.prisma.stopHunt.update({
@@ -397,10 +398,10 @@ export class StopHuntTracker {
return return
} }
console.log(` ✅ ADX validation passed: ${currentADX.toFixed(1)} ≥ 20 (strong trend)`) logger.log(` ✅ ADX validation passed: ${currentADX.toFixed(1)} ≥ 20 (strong trend)`)
} else { } else {
console.log(` ⚠️ No fresh ADX data (cache age: ${cachedData ? (Date.now() - cachedData.timestamp)/1000 : 'N/A'}s)`) logger.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(` ⚠️ Proceeding with revenge but using original ADX ${stopHunt.originalADX}`)
} }
// Call execute endpoint with revenge parameters // Call execute endpoint with revenge parameters
@@ -454,9 +455,9 @@ export class StopHuntTracker {
} }
}) })
console.log(`✅ REVENGE TRADE EXECUTED: ${result.trade?.id}`) logger.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'})`) logger.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(`🔥 LET'S GET OUR MONEY BACK!`)
// Send special Telegram notification // Send special Telegram notification
await this.sendRevengeNotification(stopHunt, result.trade) await this.sendRevengeNotification(stopHunt, result.trade)
@@ -524,7 +525,7 @@ Reversal Confirmed: Price crossed back through entry
}) })
if (!stopHunt) { 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 return
} }
@@ -538,10 +539,10 @@ Reversal Confirmed: Price crossed back through entry
}) })
const emoji = params.outcome.includes('TP') ? '✅' : '❌' 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) { if (params.failedReason) {
console.log(` Reason: ${params.failedReason}`) logger.log(` Reason: ${params.failedReason}`)
} }
} catch (error) { } catch (error) {
@@ -568,7 +569,7 @@ Reversal Confirmed: Price crossed back through entry
}) })
if (expired.count > 0) { 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) { } catch (error) {
console.error('❌ Error expiring stop hunts:', error) console.error('❌ Error expiring stop hunts:', error)
@@ -605,10 +606,10 @@ export async function startStopHuntTracking(): Promise<void> {
}) })
if (activeCount > 0) { 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() tracker.startMonitoring()
} else { } 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) { } catch (error) {
console.error('❌ Error starting stop hunt tracker:', error) console.error('❌ Error starting stop hunt tracker:', error)

51
lib/utils/logger.ts Normal file
View File

@@ -0,0 +1,51 @@
/**
* Production-safe logging utility
* Automatically gates console.log based on environment
* Always allows console.error for critical issues
*/
const isDev = process.env.NODE_ENV !== 'production'
const debugEnabled = process.env.DEBUG_LOGS === 'true'
export const logger = {
/**
* Debug logging - only in development or when DEBUG_LOGS=true
*/
log: (...args: any[]) => {
if (isDev || debugEnabled) {
console.log(...args)
}
},
/**
* Error logging - always enabled (critical for production issues)
*/
error: (...args: any[]) => {
console.error(...args)
},
/**
* Warning logging - always enabled (important for production monitoring)
*/
warn: (...args: any[]) => {
console.warn(...args)
},
/**
* Info logging - only in development or when DEBUG_LOGS=true
*/
info: (...args: any[]) => {
if (isDev || debugEnabled) {
console.info(...args)
}
},
/**
* Debug-specific logging - only when DEBUG_LOGS=true
*/
debug: (...args: any[]) => {
if (debugEnabled) {
console.log('[DEBUG]', ...args)
}
}
}

View File

@@ -4,6 +4,7 @@
*/ */
import * as fs from 'fs' import * as fs from 'fs'
import { logger } from '../utils/logger'
import * as path from 'path' import * as path from 'path'
const LOG_DIR = '/app/logs' const LOG_DIR = '/app/logs'
@@ -31,7 +32,7 @@ function rotateLogIfNeeded(logPath: string) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-') const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
const rotatedPath = `${logPath}.${timestamp}` const rotatedPath = `${logPath}.${timestamp}`
fs.renameSync(logPath, rotatedPath) fs.renameSync(logPath, rotatedPath)
console.log(`📦 Rotated log: ${rotatedPath}`) logger.log(`📦 Rotated log: ${rotatedPath}`)
} }
} }
} catch (error) { } catch (error) {
@@ -86,7 +87,7 @@ export function logTradeExecution(
: `Trade failed: ${tradeDetails.symbol} ${tradeDetails.direction} - ${tradeDetails.error}` : `Trade failed: ${tradeDetails.symbol} ${tradeDetails.direction} - ${tradeDetails.error}`
const entry = formatLogEntry(level, message, tradeDetails) const entry = formatLogEntry(level, message, tradeDetails)
console.log(success ? '✅' : '❌', message) logger.log(success ? '✅' : '❌', message)
appendToLog(TRADE_LOG, entry) appendToLog(TRADE_LOG, entry)
} }
@@ -109,7 +110,7 @@ export function logDatabaseOperation(
: `${operation} failed: ${details.error?.message || 'Unknown error'}` : `${operation} failed: ${details.error?.message || 'Unknown error'}`
const entry = formatLogEntry(level, message, details) const entry = formatLogEntry(level, message, details)
console.log(success ? '💾' : '❌', message) logger.log(success ? '💾' : '❌', message)
appendToLog(ERROR_LOG, entry) appendToLog(ERROR_LOG, entry)
} }

144
scripts/replace-console-logs.js Executable file
View File

@@ -0,0 +1,144 @@
#!/usr/bin/env node
/**
* Automated console.log replacement script
* Replaces console.log → logger.log
* Preserves console.error and console.warn
* Adds logger import to files
*/
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
// Files to process (from grep analysis)
const targetFiles = [
'lib/trading/position-manager.ts',
'lib/drift/orders.ts',
'lib/database/trades.ts',
'lib/trading/smart-entry-timer.ts',
'lib/trading/signal-quality.ts',
'lib/pyth/price-monitor.ts',
'lib/drift/client.ts',
'lib/startup/init-position-manager.ts',
'lib/trading/market-data-cache.ts',
'lib/analysis/blocked-signal-tracker.ts',
'lib/trading/stop-hunt-tracker.ts',
'lib/notifications/telegram.ts',
'lib/trading/smart-validation-queue.ts',
'lib/monitoring/drift-health-monitor.ts',
'lib/database/client.ts',
'lib/utils/persistent-logger.ts',
'lib/drift/drift-service.ts',
'lib/startup/index.ts'
]
const loggerImport = "import { logger } from '../utils/logger'"
const loggerImportAlt = "import { logger } from './utils/logger'" // For root level files
let totalReplacements = 0
let filesModified = 0
function addLoggerImport(content, filePath) {
// Skip if logger already imported
if (content.includes("from '../utils/logger'") || content.includes("from './utils/logger'")) {
return content
}
// Determine correct import path based on file location
const depth = filePath.split('/').length - 2 // Subtract 'lib/' and filename
const importPath = depth === 1 ? './utils/logger' : '../'.repeat(depth - 1) + 'utils/logger'
const importStatement = `import { logger } from '${importPath}'`
// Find first import statement and add logger import after it
const lines = content.split('\n')
let insertIndex = 0
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('import ')) {
insertIndex = i + 1
break
}
}
// If no imports found, add at top after any comments
if (insertIndex === 0) {
for (let i = 0; i < lines.length; i++) {
if (!lines[i].trim().startsWith('//') && !lines[i].trim().startsWith('/*') && lines[i].trim() !== '') {
insertIndex = i
break
}
}
}
lines.splice(insertIndex, 0, importStatement)
return lines.join('\n')
}
function replaceConsoleLogs(content) {
let count = 0
// Replace console.log with logger.log
// Match: console.log(...) but not console.error or console.warn
const logRegex = /console\.log\(/g
const matches = content.match(logRegex)
if (matches) {
count = matches.length
content = content.replace(logRegex, 'logger.log(')
}
// Replace console.info with logger.info
const infoRegex = /console\.info\(/g
const infoMatches = content.match(infoRegex)
if (infoMatches) {
count += infoMatches.length
content = content.replace(infoRegex, 'logger.info(')
}
// Keep console.error and console.warn as-is (already correct)
return { content, count }
}
function processFile(filePath) {
const fullPath = path.join(process.cwd(), filePath)
if (!fs.existsSync(fullPath)) {
console.log(`⚠️ File not found: ${filePath}`)
return
}
let content = fs.readFileSync(fullPath, 'utf8')
const originalContent = content
// Add logger import
content = addLoggerImport(content, filePath)
// Replace console.log statements
const { content: newContent, count } = replaceConsoleLogs(content)
if (count > 0) {
fs.writeFileSync(fullPath, newContent, 'utf8')
console.log(`${filePath}: ${count} replacements`)
totalReplacements += count
filesModified++
} else {
console.log(`⏭️ ${filePath}: No console.log found`)
}
}
console.log('🚀 Starting console.log replacement...\n')
targetFiles.forEach(processFile)
console.log('\n📊 Summary:')
console.log(` Files modified: ${filesModified}`)
console.log(` Total replacements: ${totalReplacements}`)
console.log(` Estimated log reduction: ${Math.round((totalReplacements / 731) * 100)}%`)
console.log('\n✅ Replacement complete!')
console.log('\n📝 Next steps:')
console.log(' 1. Review changes: git diff')
console.log(' 2. Test build: npm run build')
console.log(' 3. Update .env: NODE_ENV=production, DEBUG_LOGS=false')
console.log(' 4. Rebuild Docker: docker compose build trading-bot')
console.log(' 5. Deploy: docker compose up -d --force-recreate trading-bot')