24 KiB
24 KiB
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 <token>
│ 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