Реализация мониторинга торгового бота в реальном времени в мобильном приложении
Бот открыл позицию, рынок двинулся против — пользователь хочет видеть это немедленно, а не через пять минут полинга. Real-time мониторинг торгового бота — это задача про транспорт данных: WebSocket, правильную обработку обрывов и разумное обновление UI без перегрузки main thread.
WebSocket как основной транспорт
REST-полинг каждые 5 секунд создаёт задержку до 5 секунд и бесполезную нагрузку. WebSocket — это постоянное соединение, данные приходят по событию. Бэкенд бота рассылает события: position_opened, position_closed, order_filled, pnl_updated.
На iOS через 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() // рекурсивно ждём следующее сообщение
case .failure(let error):
// reconnect логика
self?.scheduleReconnect()
}
}
}
private func scheduleReconnect() {
Task {
try? await Task.sleep(nanoseconds: 3_000_000_000) // 3 секунды
connect(botId: botId, token: token)
}
}
}
Переподключение и состояния связи
WebSocket рвётся. Мобильная сеть нестабильна. Нужен exponential backoff: первый retry через 1 секунду, потом 2, 4, 8, максимум 30. При успешном переподключении — запрос текущего состояния через REST (GET /bots/{id}/state), потому что за время разрыва события могли потеряться.
Индикатор состояния соединения на UI: зелёная точка (live), серая (reconnecting), красная (offline). Пользователь должен понимать, актуальны ли данные.
На Flutter с Riverpod удобно вынести соединение в 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()); // при ошибке — пересоздать provider
}
Что отображать
Текущие открытые позиции. Пара, сторона (Long/Short), размер, цена входа, текущая цена, unrealized PnL в % и USD. PnL обновляется по каждому pnl_updated событию — не перерисовывать весь список, только изменившуюся строку. На Android через DiffUtil в RecyclerView, на Flutter через ListView.builder с ключами.
Лента событий. Последние N событий в хронологическом порядке: «Открыта позиция BTC/USDT long 0.01 BTC @ 67,430», «Ордер исполнен», «Стоп-лосс сработал». С временными метками.
Метрики за сессию. Количество сделок, суммарный реализованный PnL, win rate. Обновляется при каждом position_closed.
Push-уведомления для критичных событий (стоп-лосс сработал, ошибка бота) — отдельный слой через FCM/APNs. WebSocket для real-time экрана, push — для фоновых уведомлений.
Что входит в работу
- WebSocket-клиент с exponential backoff переподключением
- Индикатор состояния соединения
- Список открытых позиций с live PnL (эффективное обновление без полного ребилда)
- Лента событий с автоскроллом
- REST-синхронизация состояния при переподключении
- Push через FCM/APNs для фоновых алертов
Сроки
5–7 рабочих дней. Если бэкенд уже рассылает WebSocket-события — 4–5 дней на мобильную часть. Стоимость рассчитывается индивидуально после анализа требований.







