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:
4
.env
4
.env
@@ -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.
1491
docs/analysis/OPTIMIZATION_EXECUTION_PLAN.md
Normal file
1491
docs/analysis/OPTIMIZATION_EXECUTION_PLAN.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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
51
lib/utils/logger.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
144
scripts/replace-console-logs.js
Executable 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')
|
||||
Reference in New Issue
Block a user