feat: fix orphaned order detection and enhance automation UI
- Fixed Drift orders API to handle new object-based status format
- Updated cleanup API to properly detect orphaned TAKE PROFIT orders
- Changed status filtering from order.status === 0 to order.status.hasOwnProperty('open')
- Restored automation-v2 page with emergency stop functionality
- Added position monitor integration with real-time cleanup status
- Enhanced UI with emoji indicators and better status display
- Added emergency stop API endpoint for immediate trading halt
- Enhanced orphaned order detection for lingering SL/TP orders
- Added comprehensive debug logging for order processing
- Improved error handling and status reporting
- Real-time cleanup reporting in position monitor
Verified working:
- Orders API correctly detects active orders with new Drift format
- Cleanup system successfully processes orphaned orders
- Position monitor shows accurate cleanup status
- Emergency stop functionality integrated
This commit is contained in:
@@ -26,16 +26,21 @@ export default function AutomationPageV2() {
|
||||
const [balance, setBalance] = useState(null)
|
||||
const [positions, setPositions] = useState([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [monitorData, setMonitorData] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetchStatus()
|
||||
fetchBalance()
|
||||
fetchPositions()
|
||||
fetchMonitorData()
|
||||
fetchMonitorData()
|
||||
|
||||
const interval = setInterval(() => {
|
||||
fetchStatus()
|
||||
fetchBalance()
|
||||
fetchPositions()
|
||||
fetchMonitorData()
|
||||
fetchMonitorData()
|
||||
}, 30000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
@@ -77,6 +82,18 @@ export default function AutomationPageV2() {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchMonitorData = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/automation/position-monitor')
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
setMonitorData(data.monitor)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch monitor data:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchPositions = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/drift/positions')
|
||||
@@ -154,31 +171,69 @@ export default function AutomationPageV2() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleEmergencyStop = async () => {
|
||||
console.log('🚨 Emergency stop triggered!')
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await fetch('/api/automation/emergency-stop', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success) {
|
||||
console.log('✅ Emergency stop completed successfully')
|
||||
fetchStatus()
|
||||
fetchPositions()
|
||||
fetchMonitorData()
|
||||
fetchMonitorData()
|
||||
} else {
|
||||
console.error('Emergency stop failed:', data.error)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Emergency stop error:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
||||
{/* Configuration Panel */}
|
||||
{/* 🤖 Automation Control Panel */}
|
||||
<div className="xl:col-span-2 space-y-6">
|
||||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||||
{/* Header with Start/Stop Button */}
|
||||
{/* Header with Start/Stop Button */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-xl font-bold text-white">Configuration</h3>
|
||||
<h3 className="text-xl font-bold text-white">🤖 Automation Control</h3>
|
||||
<div className="flex space-x-3">
|
||||
{status?.isActive ? (
|
||||
<button
|
||||
onClick={handleStop}
|
||||
disabled={loading}
|
||||
className="px-6 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors disabled:opacity-50 font-semibold"
|
||||
>
|
||||
{loading ? 'Stopping...' : 'STOP'}
|
||||
</button>
|
||||
<>
|
||||
<button
|
||||
onClick={handleStop}
|
||||
disabled={loading}
|
||||
className="px-6 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors disabled:opacity-50 font-semibold"
|
||||
>
|
||||
{loading ? 'Stopping...' : '🛑 STOP'}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleEmergencyStop}
|
||||
disabled={loading}
|
||||
className="px-4 py-3 bg-red-800 text-white rounded-lg hover:bg-red-900 transition-colors disabled:opacity-50 font-semibold border-2 border-red-600"
|
||||
title="Emergency Stop - Closes all positions immediately"
|
||||
>
|
||||
🚨 EMERGENCY
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
onClick={handleStart}
|
||||
disabled={loading}
|
||||
className="px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors disabled:opacity-50 font-semibold"
|
||||
>
|
||||
{loading ? 'Starting...' : status?.rateLimitHit ? 'RESTART' : 'START'}
|
||||
{loading ? 'Starting...' : status?.rateLimitHit ? '🔄 RESTART' : '🚀 START'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -364,7 +419,7 @@ export default function AutomationPageV2() {
|
||||
<div className="space-y-6">
|
||||
{/* Status */}
|
||||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||||
<h3 className="text-lg font-bold text-white mb-4">Bot Status</h3>
|
||||
<h3 className="text-lg font-bold text-white mb-4">🤖 Bot Status</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
@@ -418,20 +473,66 @@ export default function AutomationPageV2() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Position Monitor */}
|
||||
{monitorData && (
|
||||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||||
<h3 className="text-lg font-bold text-white mb-4">📊 Position Monitor</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-400">Has Position:</span>
|
||||
<span className={`px-2 py-1 rounded text-xs font-semibold ${
|
||||
monitorData.hasPosition ? 'bg-green-600 text-white' : 'bg-gray-600 text-gray-300'
|
||||
}`}>
|
||||
{monitorData.hasPosition ? '✅ YES' : '❌ NO'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-400">Risk Level:</span>
|
||||
<span className={`px-2 py-1 rounded text-xs font-semibold ${
|
||||
monitorData.riskLevel === 'HIGH' ? 'bg-red-600 text-white' :
|
||||
monitorData.riskLevel === 'MEDIUM' ? 'bg-yellow-600 text-white' :
|
||||
'bg-green-600 text-white'
|
||||
}`}>
|
||||
{monitorData.riskLevel}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-gray-400">
|
||||
<strong>Next Action:</strong> {monitorData.nextAction}
|
||||
</div>
|
||||
|
||||
{monitorData.orphanedOrderCleanup && (
|
||||
<div className={`mt-3 p-2 rounded-lg ${
|
||||
monitorData.orphanedOrderCleanup.success ? 'bg-green-900 border border-green-600' : 'bg-red-900 border border-red-600'
|
||||
}`}>
|
||||
<div className="text-xs font-semibold">
|
||||
{monitorData.orphanedOrderCleanup.success ? '✅ Cleanup Success' : '❌ Cleanup Failed'}
|
||||
</div>
|
||||
<div className="text-xs mt-1">
|
||||
{monitorData.orphanedOrderCleanup.message}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Balance */}
|
||||
{balance && (
|
||||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||||
<h3 className="text-lg font-bold text-white mb-4">Account Balance</h3>
|
||||
<h3 className="text-lg font-bold text-white mb-4"><EFBFBD> Account Balance</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-400">Available:</span>
|
||||
<span className="text-green-400 font-semibold">${balance.availableBalance}</span>
|
||||
<span className="text-green-400 font-semibold">${parseFloat(balance.availableBalance).toFixed(2)}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-400">Total:</span>
|
||||
<span className="text-white font-medium">${balance.totalCollateral}</span>
|
||||
<span className="text-white font-medium">${parseFloat(balance.totalCollateral).toFixed(2)}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
@@ -445,17 +546,51 @@ export default function AutomationPageV2() {
|
||||
{/* Positions */}
|
||||
{positions.length > 0 && (
|
||||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||||
<h3 className="text-lg font-bold text-white mb-4">Open Positions</h3>
|
||||
<h3 className="text-lg font-bold text-white mb-4">📈 Open Positions</h3>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-3">
|
||||
{positions.map((position, index) => (
|
||||
<div key={index} className="flex justify-between items-center p-2 bg-gray-700 rounded">
|
||||
<span className="text-white">{position.symbol}</span>
|
||||
<span className={`font-semibold ${
|
||||
position.side === 'LONG' ? 'text-green-400' : 'text-red-400'
|
||||
}`}>
|
||||
{position.side} ${position.size}
|
||||
</span>
|
||||
<div key={index} className="p-4 bg-gray-700 rounded-lg border border-gray-600">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-white font-semibold">{position.symbol}</span>
|
||||
<span className={`px-2 py-1 rounded text-xs font-semibold ${
|
||||
position.side === 'LONG' ? 'bg-green-600 text-white' : 'bg-red-600 text-white'
|
||||
}`}>
|
||||
{position.side}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-400">Size:</span>
|
||||
<span className="text-white">${position.size}</span>
|
||||
</div>
|
||||
|
||||
{position.entryPrice && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-400">Entry:</span>
|
||||
<span className="text-white">${position.entryPrice}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{position.markPrice && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-400">Mark:</span>
|
||||
<span className="text-white">${position.markPrice}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{position.pnl !== undefined && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-400">PnL:</span>
|
||||
<span className={`font-semibold ${
|
||||
position.pnl >= 0 ? 'text-green-400' : 'text-red-400'
|
||||
}`}>
|
||||
${position.pnl >= 0 ? '+' : ''}${position.pnl}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user