Реалізація книги ордерів (Order Book) у мобільних біржових додатків
Order book — один з найвимогливіших до продуктивності компонентів біржевого додатка. Дані оновлюються кілька разів на секунду, список може містити сотні рівнів цін, і при цьому інтерфейс повинен залишатися відзивчивим.
Джерело даних: WebSocket
Біржевий стакан оновлюється через WebSocket — HTTP polling тут неприйнятний через затримки. Стандартний підхід: отримати снапшот через REST, потім підписатися на інкрементальні оновлення через WebSocket.
Приклад для Binance API (паттерн, який використовує більшість бирж):
GET https://api.binance.com/api/v3/depth?symbol=BTCUSDT&limit=100
→ снапшот з повними bids та asks
WSS wss://stream.binance.com:9443/ws/btcusdt@depth@100ms
→ інкрементальні оновлення кожні 100ms
Застосування інкрементальних оновлень: якщо рівень ціни вже є в стакані — оновлюємо кількість. Якщо кількість 0 — видаляємо рівень. Якщо ціна нова — додаємо. Це стандартний diff-алгоритм для order book.
Синхронізація снапшоту та стрима
Критичний момент, який часто реалізують неправильно: WebSocket починає слати оновлення до того, як REST-запит за снапшотом вернувся. Потрібно буферизувати WS-події, отримати снапшот з його lastUpdateId, потім застосувати лише буферизовані події де firstUpdateId <= lastUpdateId + 1.
Втратили подію? Рассинхронізувались? Єдиний вихід — переподключитися й отримати новий снапшот. Не пробуйте відновити стан за частковими даними.
Рендеринг: головна проблема
100 рівнів цін × оновлення кожні 100ms = потенційний лаг UI. На мобільному це особливо помітно.
Не оновлюйте список при кожній WebSocket-події. Батчинг: накопичуйте оновлення й застосовуйте до UI раз на 250–500ms. Користувачі не побачать різниці між 100ms та 250ms, але це 2.5x менше роботи для рендерера.
Віртуалізація. Відображайте лише видимі рівні, не рендеріть всі 100. На iOS використовуйте UITableView з reloadRows(at:with:) для змінених рядків, не reloadData(). На Android використовуйте RecyclerView з DiffUtil для обчислення мінімальної кількості змін. У Flutter використовуйте ListView.builder з itemCount.
Кольори та підсвічування змін: коли ціна впала — червоний, вверх — зелений, потім плавно повертається до нейтрального. Реалізуйте через анімацію кольору з CABasicAnimation (iOS) або ValueAnimator (Android). Не утримуйте таймери на кожен рядок — використовуйте один таймер для всього списку.
Глибина стакана: depth chart
Візуалізуйте об'єми через накопичену гістограму (depth chart) — накопичуйте об'єм від найкращої ціни до найгіршої. Рисуйте через CAShapeLayer / Canvas / CustomPainter у Flutter. Оновлюйте не частіше 1 раза на секунду — це візуалізація, не торговий інструмент.
Точність чисел
Ціни й об'єми на біржі — десяткові числа з високою точністю. BTC торгується з 8 знаками після запятої. Жодного Double — лише Decimal (iOS) або BigDecimal (Android). Сумування об'ємів з Double дає помітні помилки округлення при відображенні.
Форматування: різні торгові пари мають різний tick size (мінімальний крок ціни). Для BTC/USDT tick size 0.01, для деяких альткоїнів — 8 знаків. Беріть кількість відображаємих знаків з метаданих пари, не hardcode.
Офлайн і переподключення
При втраті з'єднання — очищуйте стакан й показуйте стан «Нема даних / Переподключення». Не показуйте застарілий стакан як актуальний — це вводить в оману при трейдингу.
Логіка переподключення: exponential backoff від 1s до 30s. Після відновлення — заново отримуйте снапшот й переподписуйтесь.
Період: реалізація order book з WebSocket, коректною синхронізацією та оптимізованим рендерингом — 2–3 тижні. З depth chart, сортуванням, фільтрацією глибини та підтримкою кількох торгових пар — 4–5 тижнів.







