diff --git a/app/api/automation/emergency-stop/route.js b/app/api/automation/emergency-stop/route.js new file mode 100644 index 0000000..667d412 --- /dev/null +++ b/app/api/automation/emergency-stop/route.js @@ -0,0 +1,112 @@ +import { NextResponse } from 'next/server' + +export async function POST() { + try { + console.log('๐Ÿšจ EMERGENCY STOP INITIATED') + + const results = { + automationStopped: false, + processesKilled: false, + cleanupCompleted: false, + errors: [] + } + + // 1. Stop automation normally first + try { + const stopResponse = await fetch('http://localhost:3000/api/automation/stop', { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }) + + if (stopResponse.ok) { + results.automationStopped = true + console.log('โœ… Automation stopped successfully') + } + } catch (error) { + results.errors.push(`Automation stop failed: ${error.message}`) + console.error('โŒ Automation stop failed:', error) + } + + // 2. Kill background processes + try { + const { exec } = require('child_process') + const util = require('util') + const execAsync = util.promisify(exec) + + // Kill Chromium/Chrome processes + try { + await execAsync('pkill -f "chrome|chromium" 2>/dev/null || true') + console.log('๐Ÿ”ซ Chrome/Chromium processes terminated') + } catch (e) { + console.log('โ„น๏ธ No Chrome processes to kill') + } + + // Kill any screenshot services + try { + await execAsync('pkill -f "screenshot|puppeteer" 2>/dev/null || true') + console.log('๐Ÿ”ซ Screenshot processes terminated') + } catch (e) { + console.log('โ„น๏ธ No screenshot processes to kill') + } + + results.processesKilled = true + } catch (error) { + results.errors.push(`Process cleanup failed: ${error.message}`) + console.error('โŒ Process cleanup failed:', error) + } + + // 3. Cleanup temporary files + try { + const fs = require('fs').promises + const path = require('path') + + // Clean up screenshot directories + const tempDirs = [ + '/tmp/trading-screenshots', + '/app/screenshots', + '/app/temp' + ] + + for (const dir of tempDirs) { + try { + await fs.rmdir(dir, { recursive: true }) + console.log(`๐Ÿงน Cleaned up ${dir}`) + } catch (e) { + // Directory doesn't exist or already clean + } + } + + results.cleanupCompleted = true + } catch (error) { + results.errors.push(`Cleanup failed: ${error.message}`) + console.error('โŒ Cleanup failed:', error) + } + + console.log('๐Ÿšจ EMERGENCY STOP COMPLETED') + console.log('Results:', results) + + return NextResponse.json({ + success: true, + message: 'Emergency stop completed', + results, + timestamp: new Date().toISOString() + }) + + } catch (error) { + console.error('๐Ÿšจ EMERGENCY STOP ERROR:', error) + + return NextResponse.json({ + success: false, + error: 'Emergency stop failed', + message: error.message, + timestamp: new Date().toISOString() + }, { status: 500 }) + } +} + +export async function GET() { + return NextResponse.json({ + message: 'Emergency Stop API - use POST method to trigger emergency stop', + description: 'Immediately stops all automation processes and cleans up resources' + }) +} diff --git a/app/api/drift/cleanup-orders/route.js b/app/api/drift/cleanup-orders/route.js index 6af57d7..f0aa577 100644 --- a/app/api/drift/cleanup-orders/route.js +++ b/app/api/drift/cleanup-orders/route.js @@ -65,10 +65,30 @@ export async function POST() { // Get current orders const orders = userAccount.orders || [] - const activeOrders = orders.filter(order => - order.status === 0 && !order.baseAssetAmount.isZero() - ) + + // Filter for active orders - handle both numeric and object status formats + const activeOrders = orders.filter(order => { + if (order.baseAssetAmount.isZero()) return false + + // Handle object-based status (new format) + if (typeof order.status === 'object') { + return order.status.hasOwnProperty('open') + } + + // Handle numeric status (old format) + return order.status === 0 + }) + console.log(`๐Ÿ“‹ Raw orders in cleanup: ${orders.length}`); + orders.forEach((order, index) => { + if (!order.baseAssetAmount.isZero()) { + console.log(`๐Ÿ“‹ Cleanup Order ${index}:`, { + orderId: order.orderId, + status: order.status, + baseAssetAmount: order.baseAssetAmount.toString() + }); + } + }); console.log(`๐Ÿ“Š Analysis: ${activePositions.length} active positions, ${activeOrders.length} active orders`) // Map positions by market index diff --git a/app/api/drift/orders/route.js b/app/api/drift/orders/route.js index 063d076..473d5d6 100644 --- a/app/api/drift/orders/route.js +++ b/app/api/drift/orders/route.js @@ -60,12 +60,37 @@ export async function GET() { // Get open orders const orders = userAccount.orders || [] - // Filter for active orders (status 0 = open) - const activeOrders = orders.filter(order => - order.status === 0 && !order.baseAssetAmount.isZero() - ) + // Debug: log ALL orders to see what we have + console.log(`๐Ÿ“‹ Raw orders length: ${orders.length}`) + orders.forEach((order, index) => { + if (!order.baseAssetAmount.isZero()) { + console.log(`๐Ÿ“‹ Order ${index}:`, { + orderId: order.orderId, + status: order.status, + orderType: order.orderType, + baseAssetAmount: order.baseAssetAmount.toString(), + price: order.price ? order.price.toString() : null, + triggerPrice: order.triggerPrice ? order.triggerPrice.toString() : null, + reduceOnly: order.reduceOnly, + marketIndex: order.marketIndex + }) + } + }) + + // Filter for active orders - handle both numeric and object status formats + const activeOrders = orders.filter(order => { + if (order.baseAssetAmount.isZero()) return false + + // Handle object-based status (new format) + if (typeof order.status === 'object') { + return order.status.hasOwnProperty('open') + } + + // Handle numeric status (old format) + return order.status === 0 + }) - // Show ALL orders for debugging - not just active ones + // Show ALL orders with non-zero amounts for debugging const allOrders = orders.filter(order => !order.baseAssetAmount.isZero() // Only filter out empty orders ) @@ -74,28 +99,61 @@ export async function GET() { // Debug: log all order statuses const statusCounts = orders.reduce((acc, order) => { - acc[order.status] = (acc[order.status] || 0) + 1 + if (!order.baseAssetAmount.isZero()) { + acc[order.status] = (acc[order.status] || 0) + 1 + } return acc }, {}) console.log('๐Ÿ“Š Order status breakdown:', statusCounts) - const formattedOrders = activeOrders.map(order => { + const formattedOrders = allOrders.map(order => { const marketIndex = order.marketIndex const symbol = ['SOL', 'BTC', 'ETH', 'APT', 'AVAX', 'BNB', 'MATIC', 'ARB', 'DOGE', 'OP'][marketIndex] || `UNKNOWN_${marketIndex}` let orderType = 'UNKNOWN' - switch (order.orderType) { - case 0: orderType = 'MARKET'; break - case 1: orderType = 'LIMIT'; break - case 2: orderType = 'TRIGGER_MARKET'; break - case 3: orderType = 'TRIGGER_LIMIT'; break - case 4: orderType = 'ORACLE'; break + // Handle object-based orderType + if (typeof order.orderType === 'object') { + if (order.orderType.market !== undefined) orderType = 'MARKET' + else if (order.orderType.limit !== undefined) orderType = 'LIMIT' + else if (order.orderType.triggerMarket !== undefined) orderType = 'TRIGGER_MARKET' + else if (order.orderType.triggerLimit !== undefined) orderType = 'TRIGGER_LIMIT' + else if (order.orderType.oracle !== undefined) orderType = 'ORACLE' + } else { + // Handle numeric orderType + switch (order.orderType) { + case 0: orderType = 'MARKET'; break + case 1: orderType = 'LIMIT'; break + case 2: orderType = 'TRIGGER_MARKET'; break + case 3: orderType = 'TRIGGER_LIMIT'; break + case 4: orderType = 'ORACLE'; break + } } let direction = 'UNKNOWN' - switch (order.direction) { - case 0: direction = 'LONG'; break - case 1: direction = 'SHORT'; break + // Handle object-based direction + if (typeof order.direction === 'object') { + if (order.direction.long !== undefined) direction = 'LONG' + else if (order.direction.short !== undefined) direction = 'SHORT' + } else { + // Handle numeric direction + switch (order.direction) { + case 0: direction = 'LONG'; break + case 1: direction = 'SHORT'; break + } + } + + // Get status as string + let statusString = 'UNKNOWN' + if (typeof order.status === 'object') { + if (order.status.open !== undefined) statusString = 'OPEN' + else if (order.status.filled !== undefined) statusString = 'FILLED' + else if (order.status.canceled !== undefined) statusString = 'CANCELED' + } else { + switch (order.status) { + case 0: statusString = 'OPEN'; break + case 1: statusString = 'FILLED'; break + case 2: statusString = 'CANCELED'; break + } } return { @@ -107,21 +165,30 @@ export async function GET() { price: order.price ? (Number(order.price) / 1e6).toFixed(4) : null, triggerPrice: order.triggerPrice ? (Number(order.triggerPrice) / 1e6).toFixed(4) : null, reduceOnly: order.reduceOnly, - status: order.status, - marketIndex + status: statusString, + marketIndex, + isActive: typeof order.status === 'object' ? order.status.hasOwnProperty('open') : order.status === 0, + raw: { + status: order.status, + orderType: order.orderType, + direction: order.direction, + baseAssetAmount: order.baseAssetAmount.toString() + } } }) const ordersResult = { success: true, orders: formattedOrders, - totalOrders: activeOrders.length, + totalOrders: allOrders.length, // Return ALL orders, not just active + activeOrders: activeOrders.length, // But also show active count timestamp: Date.now(), rpcEndpoint: getRpcStatus().currentEndpoint, details: { wallet: keypair.publicKey.toString(), totalOrderSlots: orders.length, - activeOrderSlots: activeOrders.length + activeOrderSlots: activeOrders.length, + allOrderSlots: allOrders.length } } diff --git a/app/automation-v2/page.js b/app/automation-v2/page.js index ce6cd05..196b481 100644 --- a/app/automation-v2/page.js +++ b/app/automation-v2/page.js @@ -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 (
- {/* Configuration Panel */} + {/* ๐Ÿค– Automation Control Panel */}
{/* Header with Start/Stop Button */} + {/* Header with Start/Stop Button */}
-

Configuration

+

๐Ÿค– Automation Control

{status?.isActive ? ( - + <> + + + ) : ( )}
@@ -364,7 +419,7 @@ export default function AutomationPageV2() {
{/* Status */}
-

Bot Status

+

๐Ÿค– Bot Status

@@ -418,20 +473,66 @@ export default function AutomationPageV2() {
+ {/* Position Monitor */} + {monitorData && ( +
+

๐Ÿ“Š Position Monitor

+ +
+
+ Has Position: + + {monitorData.hasPosition ? 'โœ… YES' : 'โŒ NO'} + +
+ +
+ Risk Level: + + {monitorData.riskLevel} + +
+ +
+ Next Action: {monitorData.nextAction} +
+ + {monitorData.orphanedOrderCleanup && ( +
+
+ {monitorData.orphanedOrderCleanup.success ? 'โœ… Cleanup Success' : 'โŒ Cleanup Failed'} +
+
+ {monitorData.orphanedOrderCleanup.message} +
+
+ )} +
+
+ )} + {/* Balance */} {balance && (
-

Account Balance

+

๏ฟฝ Account Balance

Available: - ${balance.availableBalance} + ${parseFloat(balance.availableBalance).toFixed(2)}
Total: - ${balance.totalCollateral} + ${parseFloat(balance.totalCollateral).toFixed(2)}
@@ -445,17 +546,51 @@ export default function AutomationPageV2() { {/* Positions */} {positions.length > 0 && (
-

Open Positions

+

๐Ÿ“ˆ Open Positions

-
+
{positions.map((position, index) => ( -
- {position.symbol} - - {position.side} ${position.size} - +
+
+ {position.symbol} + + {position.side} + +
+ +
+
+ Size: + ${position.size} +
+ + {position.entryPrice && ( +
+ Entry: + ${position.entryPrice} +
+ )} + + {position.markPrice && ( +
+ Mark: + ${position.markPrice} +
+ )} + + {position.pnl !== undefined && ( +
+ PnL: + = 0 ? 'text-green-400' : 'text-red-400' + }`}> + ${position.pnl >= 0 ? '+' : ''}${position.pnl} + +
+ )} +
))}