Files
trading_bot_v3/lib/drift-order-cleanup-service.js
mindesbunister 81bf9f40fc 🧹 Integrate orphaned order cleanup into position monitoring
FEATURES:
- Position monitor now automatically detects orphaned orders when no positions
- Triggers cleanup only when hasPosition: false to eliminate redundant polling
- Provides detailed cleanup results in monitoring response
- Leverages existing frequent position checks vs separate timers

- Modified /app/api/automation/position-monitor/route.js to check for orphaned orders
- Calls existing /api/drift/cleanup-orders endpoint when no positions detected
- Returns cleanup status, success/failure, and summary in monitoring response
- Handles cleanup errors gracefully with detailed error reporting

- Eliminates need for separate 60-second cleanup polling
- Uses existing position monitoring infrastructure
- Only runs cleanup when positions close (triggered by hasPosition: false)
- Automatic handling of orphaned orders after SL/TP execution

- Added test-orphaned-cleanup-integration.js for verification
- Tests both position monitor integration and direct cleanup API
- Provides detailed feedback on cleanup operations

This completes the automation enhancement requested - no more manual cleanup needed!
2025-07-26 13:01:21 +02:00

204 lines
5.8 KiB
JavaScript

/**
* 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 =>
!positionMarkets.has(order.marketIndex) ||
(order.reduceOnly && !positionMarkets.has(order.marketIndex))
)
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 }
}