/** * Drift Order Cleanup Service * Automatically detects and cancels orphaned orders when positions are closed */ class DriftOrderCleanupService { constructor() { this.isRunning = false this.monitoringInterval = null this.lastCleanupTime = 0 this.cleanupCooldown = 30000 // 30 seconds between cleanups } /** * Start monitoring for orphaned orders */ start(intervalMs = 60000) { // Check every 60 seconds by default if (this.isRunning) { console.log('โš ๏ธ Drift order cleanup service already running') return } this.isRunning = true console.log(`๐Ÿงน Starting Drift order cleanup service (checking every ${intervalMs/1000}s)`) this.monitoringInterval = setInterval(async () => { try { await this.checkAndCleanupOrders() } catch (error) { console.error('โŒ Error in order cleanup monitoring:', error) } }, intervalMs) // Also run an initial check setTimeout(() => this.checkAndCleanupOrders().catch(console.error), 5000) } /** * Stop monitoring */ stop() { if (!this.isRunning) { console.log('โš ๏ธ Drift order cleanup service not running') return } this.isRunning = false if (this.monitoringInterval) { clearInterval(this.monitoringInterval) this.monitoringInterval = null } console.log('๐Ÿ›‘ Drift order cleanup service stopped') } /** * Check for orphaned orders and clean them up */ async checkAndCleanupOrders() { if (!this.isRunning) return // Prevent too frequent cleanups const now = Date.now() if (now - this.lastCleanupTime < this.cleanupCooldown) { return } try { console.log('๐Ÿ” Checking for orphaned orders...') // Get current positions and orders const [positionsResponse, ordersResponse] = await Promise.all([ fetch('http://localhost:9001/api/drift/positions'), fetch('http://localhost:9001/api/drift/orders') ]) if (!positionsResponse.ok || !ordersResponse.ok) { console.warn('โš ๏ธ Failed to fetch positions or orders for cleanup check') return } const positionsData = await positionsResponse.json() const ordersData = await ordersResponse.json() if (!positionsData.success || !ordersData.success) { console.warn('โš ๏ธ API responses indicate failure, skipping cleanup') return } const positions = positionsData.positions || [] const orders = ordersData.orders || [] console.log(`๐Ÿ“Š Current state: ${positions.length} positions, ${orders.length} orders`) // Quick check: if no orphaned orders, skip cleanup const positionMarkets = new Set(positions.map(pos => pos.marketIndex)) const orphanedOrders = orders.filter(order => // CRITICAL FIX: Only cancel non-reduce-only orders when no position exists // Stop Loss and Take Profit orders are reduce-only and should exist with positions !positionMarkets.has(order.marketIndex) && !order.reduceOnly ) if (orphanedOrders.length === 0) { console.log('โœ… No orphaned orders detected') return } console.log(`๐ŸŽฏ Found ${orphanedOrders.length} potentially orphaned orders`) // Trigger cleanup const cleanupResponse = await fetch('http://localhost:9001/api/drift/cleanup-orders', { method: 'POST', headers: { 'Content-Type': 'application/json' } }) if (!cleanupResponse.ok) { throw new Error(`Cleanup API failed: ${cleanupResponse.status}`) } const cleanupResult = await cleanupResponse.json() if (cleanupResult.success) { const summary = cleanupResult.summary console.log('๐Ÿงน Order cleanup completed:') console.log(` ๐Ÿ“Š Orphaned orders: ${summary.orphanedOrders}`) console.log(` โš ๏ธ Conflicting orders: ${summary.conflictingOrders}`) console.log(` โœ… Successfully canceled: ${summary.totalCanceled}`) console.log(` โŒ Failed to cancel: ${summary.totalFailed}`) this.lastCleanupTime = now // Log each canceled order cleanupResult.canceledOrders.forEach(order => { if (order.success) { console.log(` โœ… Canceled ${order.symbol} order ${order.orderId} (${order.reason})`) } else { console.log(` โŒ Failed to cancel order ${order.orderId}: ${order.error}`) } }) } else { console.error('โŒ Order cleanup failed:', cleanupResult.error) } } catch (error) { console.error('โŒ Error during order cleanup check:', error) } } /** * Manual cleanup trigger */ async forceCleanup() { console.log('๐Ÿงน Manual order cleanup triggered...') try { const response = await fetch('http://localhost:9001/api/drift/cleanup-orders', { method: 'POST', headers: { 'Content-Type': 'application/json' } }) if (!response.ok) { throw new Error(`Cleanup API failed: ${response.status}`) } const result = await response.json() if (result.success) { console.log('โœ… Manual cleanup completed:', result.summary) return result } else { throw new Error(result.error) } } catch (error) { console.error('โŒ Manual cleanup failed:', error) throw error } } /** * Get service status */ getStatus() { return { isRunning: this.isRunning, lastCleanupTime: this.lastCleanupTime, lastCleanupAgo: this.lastCleanupTime ? Date.now() - this.lastCleanupTime : null, cooldownRemaining: Math.max(0, this.cleanupCooldown - (Date.now() - this.lastCleanupTime)) } } } // Export singleton instance export const driftOrderCleanupService = new DriftOrderCleanupService() // For CommonJS compatibility if (typeof module !== 'undefined' && module.exports) { module.exports = { driftOrderCleanupService, DriftOrderCleanupService } }