/** * Database-Drift Synchronization Validator * * Periodically validates that database "open" trades match actual Drift positions * Runs independently of Position Manager to catch ghost positions * * Ghost positions occur when: * - On-chain orders fill but database update fails * - Position Manager closes position but DB write fails * - Container restarts before cleanup completes * * Created: November 16, 2025 */ import { getPrismaClient } from './trades' import { initializeDriftService } from '../drift/client' import { getMarketConfig } from '../../config/trading' let validationInterval: NodeJS.Timeout | null = null let isRunning = false interface ValidationResult { checked: number ghosts: number orphans: number valid: number errors: string[] } /** * Start periodic validation (runs every 10 minutes) */ export function startDatabaseSyncValidator(): void { if (validationInterval) { console.log('⚠️ Database sync validator already running') return } // Run immediately on start setTimeout(() => validateAllOpenTrades(), 5000) // 5s delay to let system initialize // Then run every 10 minutes validationInterval = setInterval(async () => { await validateAllOpenTrades() }, 10 * 60 * 1000) console.log('🔍 Database sync validator started (runs every 10 minutes)') } /** * Stop periodic validation */ export function stopDatabaseSyncValidator(): void { if (validationInterval) { clearInterval(validationInterval) validationInterval = null console.log('🛑 Database sync validator stopped') } } /** * Validate all "open" trades in database against Drift positions * * This is the master validation that ensures database accuracy */ export async function validateAllOpenTrades(): Promise { if (isRunning) { console.log('⏭️ Validation already in progress, skipping...') return { checked: 0, ghosts: 0, orphans: 0, valid: 0, errors: [] } } isRunning = true const result: ValidationResult = { checked: 0, ghosts: 0, orphans: 0, valid: 0, errors: [] } try { const prisma = getPrismaClient() // Get all trades marked as "open" in database const openTrades = await prisma.trade.findMany({ where: { exitReason: null }, orderBy: { createdAt: 'desc' } }) if (openTrades.length === 0) { console.log('✅ No open trades to validate') isRunning = false return result } console.log(`🔍 Validating ${openTrades.length} open trades against Drift...`) result.checked = openTrades.length // Initialize Drift service let driftService try { driftService = await initializeDriftService() } catch (error) { const errorMsg = `Failed to initialize Drift service: ${error}` console.error(`❌ ${errorMsg}`) result.errors.push(errorMsg) isRunning = false return result } // Get all Drift positions (one API call) let driftPositions try { driftPositions = await driftService.getAllPositions() console.log(`📊 Found ${driftPositions.length} positions on Drift`) } catch (error) { const errorMsg = `Failed to fetch Drift positions: ${error}` console.error(`❌ ${errorMsg}`) result.errors.push(errorMsg) isRunning = false return result } // Validate each database trade for (const trade of openTrades) { try { await validateSingleTrade(trade, driftPositions, result) } catch (error) { const errorMsg = `Error validating ${trade.symbol}: ${error}` console.error(`❌ ${errorMsg}`) result.errors.push(errorMsg) } } // Log summary console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') console.log('📊 DATABASE SYNC VALIDATION COMPLETE') console.log(` Checked: ${result.checked} trades`) console.log(` ✅ Valid: ${result.valid} (DB matches Drift)`) console.log(` 👻 Ghosts: ${result.ghosts} (DB open, Drift closed) - FIXED`) console.log(` 🔄 Orphans: ${result.orphans} (DB closed, Drift open) - FIXED`) if (result.errors.length > 0) { console.log(` ⚠️ Errors: ${result.errors.length}`) } console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') } catch (error) { console.error('❌ Database sync validation failed:', error) result.errors.push(`Validation failed: ${error}`) } finally { isRunning = false } return result } /** * Validate a single trade against Drift positions */ async function validateSingleTrade( trade: any, driftPositions: any[], result: ValidationResult ): Promise { const prisma = getPrismaClient() // Find matching Drift position const driftPosition = driftPositions.find(p => p.symbol === trade.symbol) if (!driftPosition || Math.abs(driftPosition.size) < 0.01) { // GHOST DETECTED: Database says open, but Drift says closed/missing console.log(`👻 GHOST DETECTED: ${trade.symbol} ${trade.direction}`) console.log(` DB: Open since ${trade.createdAt.toISOString()}`) console.log(` Drift: Position not found or size = 0`) console.log(` P&L: ${trade.realizedPnL ? `$${trade.realizedPnL.toFixed(2)}` : 'null'}`) // Check if this is a closed position with P&L but missing exitReason const hasRealizedPnL = trade.realizedPnL !== null && trade.realizedPnL !== undefined const exitReason = hasRealizedPnL ? 'manual' : 'GHOST_CLEANUP' const exitPrice = trade.exitPrice || trade.entryPrice const realizedPnL = trade.realizedPnL || 0 // Mark as closed in database await prisma.trade.update({ where: { id: trade.id }, data: { exitReason: exitReason, exitTime: new Date(), exitPrice: exitPrice, realizedPnL: realizedPnL, status: 'closed' } }) console.log(` ✅ Marked as closed (reason: ${exitReason})`) result.ghosts++ return } // Position exists on Drift - validate it matches const driftDirection = driftPosition.side.toLowerCase() if (driftDirection !== trade.direction) { console.log(`⚠️ DIRECTION MISMATCH: ${trade.symbol}`) console.log(` DB: ${trade.direction} | Drift: ${driftDirection}`) result.errors.push(`${trade.symbol}: Direction mismatch`) return } // Valid: DB and Drift both show position open with matching direction result.valid++ } /** * One-time manual validation (for API endpoint or debugging) */ export async function runManualValidation(): Promise { console.log('🔧 Running manual database validation...') return await validateAllOpenTrades() }