# Admin Trade Control - Architecture Diagram ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ ADMIN TRADE CONTROL SYSTEM │ │ │ │ ┌───────────────────────────────────────────────────────────────────────┐ │ │ │ FRONTEND (Dashboard) │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ Header (All Pages) │ │ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ Trading Status Badge │ │ │ │ │ │ │ │ ⏸️ Trading Paused OR ▶️ Trading Active │ │ │ │ │ │ │ │ (Orange) (Green) │ │ │ │ │ │ │ │ Tooltip: "Paused by admin@example.com" │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ Admin Tab (Admin Users Only) │ │ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ Trading Control Panel │ │ │ │ │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ │ │ Status Banner │ │ │ │ │ │ │ │ │ │ AUTO-TRADING: PAUSED / RUNNING │ │ │ │ │ │ │ │ │ │ "No new positions will be opened..." │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌────────────────────┐ ┌────────────────────────────┐ │ │ │ │ │ │ │ │ │ ⏸️ Pause Auto │ │ ▶️ Resume Auto Trading │ │ │ │ │ │ │ │ │ │ Trading │ │ │ │ │ │ │ │ │ │ │ │ (disabled if │ │ (disabled if running) │ │ │ │ │ │ │ │ │ │ already paused) │ │ │ │ │ │ │ │ │ │ │ └────────────────────┘ └────────────────────────────┘ │ │ │ │ │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ │ │ Safety Notice │ │ │ │ │ │ │ │ │ │ "Pausing blocks new entries only. Existing │ │ │ │ │ │ │ │ │ │ positions continue to be managed." │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ WebSocket Connection │ │ │ │ │ │ • Receives 'health_update' events │ │ │ │ │ │ • Updates botState.health.tradingControl │ │ │ │ │ │ • UI reflects backend state in real-time │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────────────────┘ │ │ │ │ ▲ │ │ │ │ │ WebSocket │ HTTP API │ │ health_update POST /internal/trading/pause │ │ │ POST /internal/trading/resume │ │ │ GET /internal/trading/status │ │ │ │ │ ┌───────────────────────────────────────────────────────────────────────┐ │ │ │ BACKEND (Bot Service) │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ API Server (apiServer.ts) │ │ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ Admin Control Endpoints │ │ │ │ │ │ │ │ • requireAuth middleware │ │ │ │ │ │ │ │ • requireAdmin middleware │ │ │ │ │ │ │ │ • Audit logging │ │ │ │ │ │ │ │ • Idempotent operations │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ healthTracker.recordTradingControl() │ │ │ │ │ │ │ │ • Updates in-memory state │ │ │ │ │ │ │ │ • Broadcasts health_update via WebSocket │ │ │ │ │ │ │ │ • Persists to disk (bot_state.json) │ │ │ │ │ │ │ │ • Persists to Supabase │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ Health Tracker (healthTracker.ts) │ │ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ Trading Control State │ │ │ │ │ │ │ │ { │ │ │ │ │ │ │ │ mode: 'RUNNING' | 'PAUSED', │ │ │ │ │ │ │ │ lastChangedBy: string, │ │ │ │ │ │ │ │ lastChangedAt: number, │ │ │ │ │ │ │ │ reason?: string │ │ │ │ │ │ │ │ } │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ isPaused(): boolean │ │ │ │ │ │ │ │ • Returns true if mode === 'PAUSED' │ │ │ │ │ │ │ │ • Called by enforcement points │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ ENFORCEMENT POINTS │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ AutoTrader.handleSignal() (line 106-109) │ │ │ │ │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ │ │ if (healthTracker.isPaused()) { │ │ │ │ │ │ │ │ │ │ logger.info("Entry BLOCKED: Bot is PAUSED"); │ │ │ │ │ │ │ │ │ │ return; // ❌ Block new entry │ │ │ │ │ │ │ │ │ │ } │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ TradeExecutor.openPosition() (line 531-534) │ │ │ │ │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ │ │ if (healthTracker.isPaused()) { │ │ │ │ │ │ │ │ │ │ logger.info("Entry BLOCKED: Bot is PAUSED"); │ │ │ │ │ │ │ │ │ │ return { success: false, error: '...' }; │ │ │ │ │ │ │ │ │ │ } │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ ✅ CONTINUES WHEN PAUSED: │ │ │ │ │ │ • closePosition() - Exit orders │ │ │ │ │ │ • monitorStopLoss() - SL monitoring │ │ │ │ │ │ • monitorTakeProfit() - TP monitoring │ │ │ │ │ │ • reconcilePositions() - Position sync │ │ │ │ │ │ • syncOrderStatus() - Order status updates │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ Persistence Layer │ │ │ │ │ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │ │ │ │ │ │ │ In-Memory │ │ Disk │ │ Supabase │ │ │ │ │ │ │ │ (HealthTracker) │→ │ (bot_state.json)│→ │ (Database) │ │ │ │ │ │ │ │ Singleton │ │ Local file │ │ Remote DB │ │ │ │ │ │ │ └──────────────────┘ └──────────────────┘ └──────────────┘ │ │ │ │ │ │ • State restored on bot restart │ │ │ │ │ │ • Supabase preferred, disk fallback │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────┐ │ CONTROL FLOW │ └─────────────────────────────────────────────────────────────────────────────┘ PAUSE FLOW: 1. Admin clicks "Pause Auto Trading" button 2. Frontend calls POST /internal/trading/pause with auth token 3. API Server validates: requireAuth + requireAdmin 4. API Server calls healthTracker.recordTradingControl({ mode: 'PAUSED' }) 5. HealthTracker updates in-memory state 6. API Server broadcasts health_update via WebSocket 7. API Server persists to disk and Supabase 8. Frontend receives health_update, updates UI 9. Header badge shows "⏸️ Trading Paused" 10. AutoTrader/TradeExecutor check isPaused() before new entries 11. New entries blocked, existing positions continue RESUME FLOW: 1. Admin clicks "Resume Auto Trading" button 2. Frontend calls POST /internal/trading/resume with auth token 3. API Server validates: requireAuth + requireAdmin 4. API Server calls healthTracker.recordTradingControl({ mode: 'RUNNING' }) 5. HealthTracker updates in-memory state 6. API Server broadcasts health_update via WebSocket 7. API Server persists to disk and Supabase 8. Frontend receives health_update, updates UI 9. Header badge shows "▶️ Trading Active" 10. AutoTrader/TradeExecutor allow new entries 11. Normal trading resumes RESTART RECOVERY: 1. Bot starts up 2. API Server calls loadState() 3. Reads bot_state.json from disk 4. Restores tradingControl state 5. Calls healthTracker.recordTradingControl() with restored state 6. Trading resumes in last known mode (PAUSED or RUNNING) ┌─────────────────────────────────────────────────────────────────────────────┐ │ SECURITY LAYERS │ └─────────────────────────────────────────────────────────────────────────────┘ Layer 1: Authentication ├─ All endpoints require valid JWT token ├─ Token verified via Supabase auth └─ Unauthorized requests → 401 Layer 2: Authorization ├─ Pause/Resume require role = 'admin' ├─ Role checked in user profile └─ Non-admin requests → 403 Layer 3: UI Guards ├─ Trading Control Panel hidden for non-admin ├─ Header badge visible to all (read-only) └─ Buttons disabled when already in target state Layer 4: Audit Trail ├─ All pause/resume actions logged ├─ Includes: userId, timestamp, reason └─ Logs written to console and observability ┌─────────────────────────────────────────────────────────────────────────────┐ │ DATA FLOW DIAGRAM │ └─────────────────────────────────────────────────────────────────────────────┘ Admin Action │ ▼ Frontend (AdminTab.tsx) │ │ HTTP POST /internal/trading/pause │ Authorization: Bearer │ Body: { reason: "..." } ▼ API Server (apiServer.ts) │ ├─ requireAuth middleware → Verify JWT ├─ requireAdmin middleware → Check role │ ▼ healthTracker.recordTradingControl() │ ├─ Update in-memory state ├─ Broadcast WebSocket health_update ├─ Persist to bot_state.json └─ Persist to Supabase │ ▼ WebSocket Broadcast │ ▼ Frontend (useWebSocket.ts) │ ├─ Receive health_update event ├─ Update botState.health.tradingControl │ ▼ UI Updates │ ├─ Header badge: "⏸️ Trading Paused" ├─ Admin panel: Status banner updates └─ Buttons: Pause disabled, Resume enabled Trading Loop │ ▼ AutoTrader.handleSignal() │ ├─ Check: healthTracker.isPaused() │ ├─ if true → Block entry, return │ └─ if false → Continue to entry logic │ ▼ TradeExecutor.openPosition() │ ├─ Check: healthTracker.isPaused() │ ├─ if true → Return { success: false, error: '...' } │ └─ if false → Place order on exchange │ ▼ Exchange Order Placement ```