Real-time Trading Bot Monitoring in Mobile App
The bot opened a position, the market moved against it—the user wants to see this immediately, not in five minutes of polling. Real-time trading bot monitoring is about data transport: WebSocket, proper handling of disconnections, and smart UI updates without overloading the main thread.
WebSocket as Primary Transport
REST polling every 5 seconds creates up to 5-second latency and unnecessary load. WebSocket is a persistent connection; data arrives on event. The bot backend broadcasts events: position_opened, position_closed, order_filled, pnl_updated.
On iOS via URLSessionWebSocketTask:
actor BotMonitorConnection {
private var webSocketTask: URLSessionWebSocketTask?
private let session = URLSession.shared
var onEvent: ((BotEvent) -> Void)?
func connect(botId: String, token: String) {
let url = URL(string: "wss://api.example.com/bots/\(botId)/stream?token=\(token)")!
webSocketTask = session.webSocketTask(with: url)
webSocketTask?.resume()
startListening()
}
private func startListening() {
webSocketTask?.receive { [weak self] result in
switch result {
case .success(let message):
if case .string(let text) = message,
let data = text.data(using: .utf8),
let event = try? JSONDecoder().decode(BotEvent.self, from: data) {
self?.onEvent?(event)
}
self?.startListening() // recursively wait for next message
case .failure(let error):
// reconnect logic
self?.scheduleReconnect()
}
}
}
private func scheduleReconnect() {
Task {
try? await Task.sleep(nanoseconds: 3_000_000_000) // 3 seconds
connect(botId: botId, token: token)
}
}
}
Reconnection and Connection States
WebSocket breaks. Mobile networks are unstable. Use exponential backoff: first retry after 1 second, then 2, 4, 8, maximum 30. On successful reconnection—fetch current state via REST (GET /bots/{id}/state), because events could be lost during the break.
Connection state indicator on UI: green dot (live), gray (reconnecting), red (offline). Users should understand if data is current.
On Flutter with Riverpod, it's convenient to isolate the connection in a StreamProvider:
@riverpod
Stream<BotEvent> botEventStream(BotEventStreamRef ref, String botId) {
final channel = WebSocketChannel.connect(
Uri.parse('wss://api.example.com/bots/$botId/stream'),
);
ref.onDispose(channel.sink.close);
return channel.stream
.map((data) => BotEvent.fromJson(jsonDecode(data as String)))
.handleError((e) => ref.invalidateSelf()); // on error — recreate provider
}
What to Display
Currently open positions. Pair, side (Long/Short), size, entry price, current price, unrealized PnL in % and USD. PnL updates on each pnl_updated event—don't redraw the whole list, just the changed row. On Android use DiffUtil in RecyclerView, on Flutter use ListView.builder with keys.
Event stream. Last N events in chronological order: "Opened BTC/USDT long 0.01 BTC @ 67,430", "Order filled", "Stop-loss triggered". With timestamps.
Session metrics. Number of trades, total realized PnL, win rate. Updated on each position_closed.
Push notifications for critical events (stop-loss triggered, bot error)—separate layer via FCM/APNs. WebSocket for real-time screen, push—for background alerts.
What's Included
- WebSocket client with exponential backoff reconnection
- Connection state indicator
- Open positions list with live PnL (efficient update without full rebuild)
- Event stream with autoscroll
- REST state sync on reconnection
- Push via FCM/APNs for background alerts
Timeline
5–7 business days. If backend already broadcasts WebSocket events—4–5 days for the mobile part. Cost is calculated individually after requirements analysis.







