/** * Position Manager Startup Initialization * * Ensures Position Manager starts monitoring on bot startup * This prevents orphaned trades when the bot restarts */ import { getInitializedPositionManager } from '../trading/position-manager' import { initializeDriftService } from '../drift/client' import { getPrismaClient } from '../database/trades' import { getMarketConfig } from '../../config/trading' let initStarted = false export async function initializePositionManagerOnStartup() { if (initStarted) { return } initStarted = true console.log('🚀 Initializing Position Manager on startup...') try { // Validate open trades against Drift positions BEFORE starting Position Manager await validateOpenTrades() const manager = await getInitializedPositionManager() const status = manager.getStatus() console.log(`✅ Position Manager ready - ${status.activeTradesCount} active trades`) if (status.activeTradesCount > 0) { console.log(`📊 Monitoring: ${status.symbols.join(', ')}`) } } catch (error) { console.error('❌ Failed to initialize Position Manager on startup:', error) } } /** * Validate that open trades in database match actual Drift positions * Closes phantom trades that don't exist on-chain */ async function validateOpenTrades() { try { const prisma = getPrismaClient() const openTrades = await prisma.trade.findMany({ where: { status: 'open' }, orderBy: { entryTime: 'asc' } }) if (openTrades.length === 0) { console.log('✅ No open trades to validate') return } console.log(`🔍 Validating ${openTrades.length} open trade(s) against Drift positions...`) const driftService = await initializeDriftService() for (const trade of openTrades) { try { const marketConfig = getMarketConfig(trade.symbol) const position = await driftService.getPosition(marketConfig.driftMarketIndex) // Prefer Position Manager snapshot (captures partial closes) before falling back to original size const configSnapshot = trade.configSnapshot as any const pmState = configSnapshot?.positionManagerState const expectedSizeUSD = typeof pmState?.currentSize === 'number' && pmState.currentSize > 0 ? pmState.currentSize : trade.positionSizeUSD // Calculate expected position size in base assets (approximate using entry price for consistency) const expectedSizeBase = expectedSizeUSD / trade.entryPrice const actualSizeBase = position?.size || 0 // Check if position exists and size matches (with 50% tolerance for partial fills) const sizeDiff = Math.abs(expectedSizeBase - actualSizeBase) const sizeRatio = expectedSizeBase > 0 ? actualSizeBase / expectedSizeBase : 0 if (!position || position.side === 'none' || sizeRatio < 0.2) { console.log(`âš ī¸ PHANTOM TRADE DETECTED:`) console.log(` Trade ID: ${trade.id.substring(0, 20)}...`) console.log(` Symbol: ${trade.symbol} ${trade.direction}`) console.log(` Expected size: ${expectedSizeBase.toFixed(4)}`) console.log(` Actual size: ${actualSizeBase.toFixed(4)}`) console.log(` Entry: $${trade.entryPrice} at ${trade.entryTime.toISOString()}`) console.log(` đŸ—‘ī¸ Auto-closing phantom trade...`) // Close phantom trade await prisma.trade.update({ where: { id: trade.id }, data: { status: 'closed', exitTime: new Date(), exitReason: 'PHANTOM_TRADE_CLEANUP', exitPrice: trade.entryPrice, realizedPnL: 0, realizedPnLPercent: 0, } }) console.log(` ✅ Phantom trade closed`) } else if (sizeDiff > expectedSizeBase * 0.1) { console.log(`âš ī¸ SIZE MISMATCH (${(sizeRatio * 100).toFixed(1)}% of expected):`) console.log(` Trade ID: ${trade.id.substring(0, 20)}...`) console.log(` Symbol: ${trade.symbol} ${trade.direction}`) console.log(` Expected: ${expectedSizeBase.toFixed(4)}, Actual: ${actualSizeBase.toFixed(4)}`) console.log(` â„šī¸ Will monitor with adjusted size`) } else { console.log(`✅ ${trade.symbol} ${trade.direction}: Size OK (${actualSizeBase.toFixed(4)})`) } } catch (posError) { console.error(`❌ Error validating trade ${trade.symbol}:`, posError) // Don't auto-close on error - might be temporary } } } catch (error) { console.error('❌ Error in validateOpenTrades:', error) // Don't throw - allow Position Manager to start anyway } }