🧹 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!
This commit is contained in:
203
lib/drift-order-cleanup-service.js
Normal file
203
lib/drift-order-cleanup-service.js
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user