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_ACCOUNT_BALANCE=500
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 { logger } from '../utils/logger'
import { initializeDriftService } from '../drift/client'
import { getMergedConfig, SUPPORTED_MARKETS } from '../../config/trading'
@@ -60,11 +61,11 @@ export class BlockedSignalTracker {
*/
public start(): void {
if (this.isRunning) {
console.log('⚠️ Blocked signal tracker already running')
logger.log('⚠️ Blocked signal tracker already running')
return
}
console.log('🔬 Starting blocked signal price tracker...')
logger.log('🔬 Starting blocked signal price tracker...')
this.isRunning = true
// Run immediately on start
@@ -79,7 +80,7 @@ export class BlockedSignalTracker {
})
}, 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.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
const driftService = await initializeDriftService()
if (!driftService) {
console.log('⚠️ Drift service not available, skipping price tracking')
logger.log('⚠️ Drift service not available, skipping price tracking')
return
}
@@ -122,17 +123,17 @@ export class BlockedSignalTracker {
})
if (signals.length === 0) {
console.log('📊 No blocked signals to track')
logger.log('📊 No blocked signals to track')
return
}
console.log(`📊 Tracking ${signals.length} blocked signals...`)
logger.log(`📊 Tracking ${signals.length} blocked signals...`)
for (const signal of signals) {
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) {
console.error('❌ Error in trackPrices:', error)
}
@@ -152,7 +153,7 @@ export class BlockedSignalTracker {
const marketConfig = SUPPORTED_MARKETS[signal.symbol]
if (!marketConfig) {
console.log(`⚠️ No market config for ${signal.symbol}, skipping`)
logger.log(`⚠️ No market config for ${signal.symbol}, skipping`)
return
}
@@ -160,7 +161,7 @@ export class BlockedSignalTracker {
const entryPrice = Number(signal.entryPrice)
if (entryPrice === 0) {
console.log(`⚠️ Entry price is 0 for ${signal.symbol}, skipping`)
logger.log(`⚠️ Entry price is 0 for ${signal.symbol}, skipping`)
return
}
@@ -184,56 +185,56 @@ export class BlockedSignalTracker {
if (elapsedMinutes >= 1 && !signal.priceAfter1Min) {
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) {
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) {
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) {
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
if (elapsedMinutes >= 60 && !signal.priceAfter1Hr) {
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) {
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) {
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) {
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
if (elapsedMinutes >= 480 && !signal.analysisComplete) {
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)
if (!signal.analysisComplete && (signal.wouldHitTP1 || signal.wouldHitTP2 || signal.wouldHitSL)) {
updates.analysisComplete = true
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
@@ -253,17 +254,17 @@ export class BlockedSignalTracker {
// Check if TP1/TP2/SL would have been hit
if (signal.wouldHitTP1 === null && Math.abs(profitPercent) >= tp1Percent) {
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) {
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) {
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
@@ -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
} catch (error) {
console.error('❌ Error querying historical prices:', error)
@@ -388,7 +389,7 @@ export class BlockedSignalTracker {
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)
let tp1HitTime: Date | null = null
@@ -439,7 +440,7 @@ export class BlockedSignalTracker {
: currentPrice <= tp1Price
if (tp1Hit) {
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
if (tp2Hit) {
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
if (slHit) {
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['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
}
@@ -544,11 +545,11 @@ export class BlockedSignalTracker {
})
if (signalsToProcess.length === 0) {
console.log('📊 No signals ready for batch processing')
logger.log('📊 No signals ready for batch processing')
return
}
console.log(`🔄 Processing ${signalsToProcess.length} signals with historical data...`)
logger.log(`🔄 Processing ${signalsToProcess.length} signals with historical data...`)
let processed = 0
let skipped = 0
@@ -567,12 +568,12 @@ export class BlockedSignalTracker {
)
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++
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
const updates = await this.analyzeHistoricalData(
@@ -591,7 +592,7 @@ export class BlockedSignalTracker {
})
processed++
console.log(`${signal.symbol} ${signal.direction} analyzed successfully`)
logger.log(`${signal.symbol} ${signal.direction} analyzed successfully`)
} catch (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) {
console.error('❌ Error in batch processing:', error)

View File

@@ -3,6 +3,7 @@
*/
import { PrismaClient } from '@prisma/client'
import { logger } from '../utils/logger'
import { logCriticalError, logDatabaseOperation } from '../utils/persistent-logger'
// Singleton Prisma client
@@ -13,7 +14,7 @@ export function getPrismaClient(): PrismaClient {
prisma = new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
})
console.log('✅ Prisma client initialized')
logger.log('✅ Prisma client initialized')
}
return prisma
}
@@ -177,7 +178,7 @@ export async function createTrade(params: CreateTradeParams) {
if (attempt < maxRetries) {
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))
continue // Retry
}
@@ -185,7 +186,7 @@ export async function createTrade(params: CreateTradeParams) {
throw new Error(errorMsg)
}
console.log(`📊 Trade record created & VERIFIED: ${trade.id}`)
logger.log(`📊 Trade record created & VERIFIED: ${trade.id}`)
logDatabaseOperation('createTrade', true, {
table: 'Trade',
recordId: trade.id,
@@ -206,7 +207,7 @@ export async function createTrade(params: CreateTradeParams) {
if (attempt < maxRetries) {
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))
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
} catch (error) {
console.error('❌ Failed to update trade exit:', error)
@@ -331,7 +332,7 @@ export async function getOpenTrades() {
orderBy: { entryTime: 'asc' },
})
console.log(`📊 Found ${trades.length} open trades to restore`)
logger.log(`📊 Found ${trades.length} open trades to restore`)
return trades
} catch (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
} catch (error) {
console.error('❌ Failed to save blocked signal:', error)
@@ -746,6 +747,6 @@ export async function disconnectPrisma() {
if (prisma) {
await prisma.$disconnect()
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 { logger } from '../utils/logger'
import { DriftClient, initialize, User, PerpMarkets } from '@drift-labs/sdk'
import bs58 from 'bs58'
import { getDriftHealthMonitor } from '../monitoring/drift-health-monitor'
@@ -43,9 +44,9 @@ export class DriftService {
: this.connection
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 {
console.log('📡 Single RPC mode: Helius for all operations')
logger.log('📡 Single RPC mode: Helius for all operations')
}
// 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?.cause?.code === 'EAI_AGAIN'
console.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 detection: isTransient=${isTransient}, attempt=${attempt}/${maxRetries}`)
logger.log(`🔍 Error details: message="${error?.message}", code="${error?.code}", cause.code="${error?.cause?.code}"`)
if (!isTransient || attempt === maxRetries) {
// 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
}
console.log(`⚠️ ${operationName} failed (attempt ${attempt}/${maxRetries}): ${error?.message || error}`)
console.log(`⏳ Retrying in ${delayMs}ms...`)
logger.log(`⚠️ ${operationName} failed (attempt ${attempt}/${maxRetries}): ${error?.message || error}`)
logger.log(`⏳ Retrying in ${delayMs}ms...`)
// Wait before retry
await new Promise(resolve => setTimeout(resolve, delayMs))
@@ -144,12 +145,12 @@ export class DriftService {
*/
async initialize(): Promise<void> {
if (this.isInitialized) {
console.log('⚠️ Drift service already initialized')
logger.log('⚠️ Drift service already initialized')
return
}
try {
console.log('🚀 Initializing Drift Protocol client...')
logger.log('🚀 Initializing Drift Protocol client...')
// Wrap initialization in retry logic to handle DNS failures
await this.retryOperation(async () => {
@@ -170,20 +171,20 @@ export class DriftService {
// Subscribe to Drift account updates (this makes RPC calls)
await this.driftClient.subscribe()
console.log('✅ Drift client subscribed to account updates')
logger.log('✅ Drift client subscribed to account updates')
// Get user account
this.user = this.driftClient.getUser()
}, 3, 2000, 'Drift initialization')
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
// 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()
console.log('✅ Error interception active')
logger.log('✅ Error interception active')
// Start health monitoring (error-based restart instead of blind timer)
const monitor = getDriftHealthMonitor()
@@ -427,7 +428,7 @@ export class DriftService {
async disconnect(): Promise<void> {
if (this.driftClient) {
await this.driftClient.unsubscribe()
console.log('✅ Drift client disconnected')
logger.log('✅ Drift client disconnected')
}
this.isInitialized = false
}
@@ -460,9 +461,9 @@ export function getDriftService(): DriftService {
}
driftServiceInstance = new DriftService(config)
console.log('🔄 Created new Drift service singleton')
logger.log('🔄 Created new Drift service singleton')
} else {
console.log('♻️ Reusing existing Drift service instance')
logger.log('♻️ Reusing existing Drift service instance')
}
return driftServiceInstance
@@ -471,7 +472,7 @@ export function getDriftService(): DriftService {
export async function initializeDriftService(): Promise<DriftService> {
// If already initializing, return the same promise to avoid multiple concurrent inits
if (initializationPromise) {
console.log('⏳ Waiting for ongoing initialization...')
logger.log('⏳ Waiting for ongoing initialization...')
return initializationPromise
}
@@ -479,7 +480,7 @@ export async function initializeDriftService(): Promise<DriftService> {
// If already initialized, return immediately
if (service['isInitialized']) {
console.log('✅ Drift service already initialized')
logger.log('✅ Drift service already initialized')
return service
}

View File

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

View File

@@ -6,6 +6,7 @@
*/
import fs from 'fs'
import { logger } from '../utils/logger'
import path from 'path'
class DriftHealthMonitor {
@@ -20,13 +21,13 @@ class DriftHealthMonitor {
*/
start(): void {
if (this.isMonitoring) {
console.log('⚠️ Drift health monitor already running')
logger.log('⚠️ Drift health monitor already running')
return
}
this.isMonitoring = true
console.log('🏥 Drift health monitor started')
console.log(` Threshold: ${this.errorThreshold} accountUnsubscribe errors in ${this.errorWindow/1000}s`)
logger.log('🏥 Drift health monitor started')
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)
this.checkInterval = setInterval(() => {
@@ -43,7 +44,7 @@ class DriftHealthMonitor {
this.checkInterval = null
}
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`,
'utf-8'
)
console.log(`✅ Restart flag created at ${restartFlagPath}`)
console.log(' watch-restart.sh will restart container within 10 seconds')
logger.log(`✅ Restart flag created at ${restartFlagPath}`)
logger.log(' watch-restart.sh will restart container within 10 seconds')
} catch (error) {
console.error('❌ Failed to create restart flag:', error)
}

View File

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

View File

@@ -5,6 +5,7 @@
*/
import { Connection, PublicKey } from '@solana/web3.js'
import { logger } from '../utils/logger'
import { PriceServiceConnection } from '@pythnetwork/price-service-client'
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
}
console.log('🚀 Starting Pyth price monitor for:', config.symbols)
logger.log('🚀 Starting Pyth price monitor for:', config.symbols)
try {
// Get Pyth price feed IDs for all symbols
@@ -68,7 +69,7 @@ export class PythPriceMonitor {
return marketConfig.pythPriceFeedId
})
console.log('📡 Subscribing to Pyth WebSocket...')
logger.log('📡 Subscribing to Pyth WebSocket...')
// Subscribe to Pyth WebSocket for real-time updates
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
this.startPollingFallback(config)
this.isMonitoring = true
console.log('✅ Price monitoring active')
logger.log('✅ Price monitoring active')
} catch (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
*/
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) {
const interval = setInterval(async () => {
@@ -140,7 +141,7 @@ export class PythPriceMonitor {
const timeSinceUpdate = Date.now() - lastUpdate
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)
}
} catch (error) {
@@ -154,7 +155,7 @@ export class PythPriceMonitor {
this.pollingIntervals.set(symbol, interval)
}
console.log('✅ Polling fallback active')
logger.log('✅ Polling fallback active')
}
/**
@@ -223,7 +224,7 @@ export class PythPriceMonitor {
return
}
console.log('🛑 Stopping price monitor...')
logger.log('🛑 Stopping price monitor...')
// Clear polling intervals
this.pollingIntervals.forEach(interval => clearInterval(interval))
@@ -237,7 +238,7 @@ export class PythPriceMonitor {
this.lastUpdateTime.clear()
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 { logger } from '../utils/logger'
import { initializeDriftService } from '../drift/client'
import { getPrismaClient, createTrade } from '../database/trades'
import { getMarketConfig, getMergedConfig } from '../../config/trading'
@@ -25,16 +26,16 @@ export async function initializePositionManagerOnStartup() {
initStarted = true
console.log('🚀 Initializing Position Manager on startup...')
logger.log('🚀 Initializing Position Manager on startup...')
try {
// CRITICAL: Run database sync validator FIRST to clean up duplicates
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()
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
@@ -46,28 +47,28 @@ export async function initializePositionManagerOnStartup() {
const manager = await getInitializedPositionManager()
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) {
console.log(`📊 Monitoring: ${status.symbols.join(', ')}`)
logger.log(`📊 Monitoring: ${status.symbols.join(', ')}`)
}
// CRITICAL (Dec 2, 2025): Start data cleanup service for 4-week retention
// User directive: "we want to store the data for 4 weeks"
// 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()
// Start blocked signal price tracking
console.log('🔬 Starting blocked signal price tracker...')
logger.log('🔬 Starting blocked signal price tracker...')
startBlockedSignalTracking()
// Start stop hunt revenge tracker
console.log('🎯 Starting stop hunt revenge tracker...')
logger.log('🎯 Starting stop hunt revenge tracker...')
await startStopHuntTracking()
// Start smart entry validation queue (Nov 30, 2025)
console.log('🧠 Starting smart entry validation system...')
logger.log('🧠 Starting smart entry validation system...')
await startSmartValidation()
} catch (error) {
console.error('❌ Failed to initialize Position Manager on startup:', error)
@@ -107,11 +108,11 @@ async function validateOpenTrades() {
const allTradesToCheck = [...openTrades, ...recentlyClosedTrades]
if (allTradesToCheck.length === 0) {
console.log('✅ No open trades to validate')
logger.log('✅ No open trades to validate')
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
// 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
for (const oldTrade of olderTrades) {
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({
where: { id: oldTrade.id },
data: {
@@ -161,8 +162,8 @@ async function validateOpenTrades() {
if (!position || position.size === 0) {
// No position on Drift
if (trade.status === 'open') {
console.log(`⚠️ PHANTOM TRADE: ${trade.symbol} marked open in DB but not found on Drift`)
console.log(` 🗑️ Auto-closing phantom trade...`)
logger.log(`⚠️ PHANTOM TRADE: ${trade.symbol} marked open in DB but not found on Drift`)
logger.log(` 🗑️ Auto-closing phantom trade...`)
await prisma.trade.update({
where: { id: trade.id },
@@ -184,17 +185,17 @@ async function validateOpenTrades() {
const driftDirection = position.side.toLowerCase() as 'long' | 'short'
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
}
// CRITICAL: If DB says closed but Drift shows open, restore tracking!
if (trade.exitReason !== null) {
console.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)}`)
console.log(` DB exit: ${trade.exitReason} at ${trade.exitTime?.toISOString()}`)
console.log(` Drift: ${position.size} ${trade.symbol} ${driftDirection} @ $${position.entryPrice.toFixed(2)}`)
console.log(` 🔄 Reopening trade and correcting entry price to match Drift...`)
logger.log(`🔴 CRITICAL: ${trade.symbol} marked as CLOSED in DB but still OPEN on Drift!`)
logger.log(` DB entry: $${trade.entryPrice.toFixed(2)} | Drift entry: $${position.entryPrice.toFixed(2)}`)
logger.log(` DB exit: ${trade.exitReason} at ${trade.exitTime?.toISOString()}`)
logger.log(` Drift: ${position.size} ${trade.symbol} ${driftDirection} @ $${position.entryPrice.toFixed(2)}`)
logger.log(` 🔄 Reopening trade and correcting entry price to match Drift...`)
// Calculate position size in USD using Drift's entry price
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 {
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
@@ -255,11 +256,11 @@ async function restoreOrdersIfMissing(
const hasOrders = position.orders && position.orders.length > 0
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
}
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
const { placeExitOrders } = await import('../drift/orders')
@@ -278,11 +279,11 @@ async function restoreOrdersIfMissing(
})
if (result.success) {
console.log(`✅ Orders restored for ${trade.symbol}:`)
console.log(` TP1: $${trade.takeProfit1Price.toFixed(4)} (75%)`)
console.log(` TP2: $${trade.takeProfit2Price.toFixed(4)} (runner trigger)`)
console.log(` SL: $${trade.stopLossPrice.toFixed(4)}`)
console.log(` TX: ${result.signatures?.[0]?.slice(0, 8)}...`)
logger.log(`✅ Orders restored for ${trade.symbol}:`)
logger.log(` TP1: $${trade.takeProfit1Price.toFixed(4)} (75%)`)
logger.log(` TP2: $${trade.takeProfit2Price.toFixed(4)} (runner trigger)`)
logger.log(` SL: $${trade.stopLossPrice.toFixed(4)}`)
logger.log(` TX: ${result.signatures?.[0]?.slice(0, 8)}...`)
// Update database with order transaction signatures
await prisma.trade.update({
@@ -324,17 +325,17 @@ async function detectOrphanedPositions() {
const prisma = getPrismaClient()
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
const driftPositions = await driftService.getAllPositions()
if (driftPositions.length === 0) {
console.log('✅ No positions on Drift')
logger.log('✅ No positions on Drift')
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
const openTrades = await prisma.trade.findMany({
@@ -349,7 +350,7 @@ async function detectOrphanedPositions() {
const positionKey = `${position.symbol}-${position.side.toLowerCase()}`
if (trackedSymbols.has(positionKey)) {
console.log(`${position.symbol} ${position.side} tracked in database`)
logger.log(`${position.symbol} ${position.side} tracked in database`)
continue
}
@@ -359,12 +360,12 @@ async function detectOrphanedPositions() {
const currentPrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex)
const positionSizeUSD = Math.abs(position.size) * currentPrice
console.log(`🚨 ORPHAN POSITION DETECTED!`)
console.log(` Symbol: ${position.symbol}`)
console.log(` Direction: ${position.side}`)
console.log(` Size: ${Math.abs(position.size)} (notional: $${positionSizeUSD.toFixed(2)})`)
console.log(` Entry: $${position.entryPrice.toFixed(4)}`)
console.log(` Current: $${currentPrice.toFixed(4)}`)
logger.log(`🚨 ORPHAN POSITION DETECTED!`)
logger.log(` Symbol: ${position.symbol}`)
logger.log(` Direction: ${position.side}`)
logger.log(` Size: ${Math.abs(position.size)} (notional: $${positionSizeUSD.toFixed(2)})`)
logger.log(` Entry: $${position.entryPrice.toFixed(4)}`)
logger.log(` Current: $${currentPrice.toFixed(4)}`)
// Log to persistent file
logCriticalError('ORPHAN POSITION DETECTED - Creating retroactive database record', {
@@ -398,7 +399,7 @@ async function detectOrphanedPositions() {
: entryPrice * (1 - config.takeProfit2Percent / 100)
// Create retroactive database record
console.log(`🔄 Creating retroactive database record...`)
logger.log(`🔄 Creating retroactive database record...`)
const trade = await createTrade({
positionId: `ORPHAN-${Date.now()}`, // Fake position ID since we don't have transaction
symbol: position.symbol,
@@ -418,7 +419,7 @@ async function detectOrphanedPositions() {
status: 'open',
})
console.log(`✅ Retroactive database record created: ${trade.id}`)
logger.log(`✅ Retroactive database record created: ${trade.id}`)
// Send Telegram notification
try {
@@ -438,7 +439,7 @@ async function detectOrphanedPositions() {
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) {
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) {
console.error('❌ Error detecting orphaned positions:', error)

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

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