Ми розробляємо модуль історії торгів для біржових додатків, який обробляє до 50 000 ордерів у реальному часі з WebSocket-оновленнями та віртуалізацією списку. Без продуманої архітектури користувач стикається з лагами та некоректним P&L. Наше рішення реалізовано для Binance і OKX, використовуючи Swift, Kotlin та Flutter з оптимізованим завантаженням даних.
Типовий користувач має 2 000+ угод — без правильної архітектури екран історії стає вузьким місцем. Ми вирішуємо три ключові проблеми: завантаження великих обсягів даних, розрахунок P&L та плавний скрол.
Як влаштована історія торгів для мобільного біржового додатку?
Які типи записів потрібно підтримувати? — архітектура історії торгів
Біржова історія включає ордери, угоди (fills) та P&L. Ордери бувають лімітні, ринкові, стоп; статуси: open, filled, partially_filled, cancelled. Угоди — це фактичне виконання, один ордер може дробитися на кілька fills. Для кожного ордера зберігаються side (buy/sell), symbol (наприклад, "BTC/USDT"), ціна, кількість, комісія та asset комісії (USDT або BNB при дисконті). P&L для spot розраховується за FIFO: (sell_price - avg_buy_price) * quantity - fees. Для ф'ючерсів — з урахуванням маржі та комісій. Якщо біржа не надає P&L через API, обчислюємо локально, що вимагає зберігання історії угод. Ігнорування комісій може спотворити прибуток на 5–10%.
Як завантажувати дані без втрати швидкості?
Історичні дані завантажуються через REST API біржі. Більшість бірж (Binance, OKX, Bybit) мають ендпоінт /api/v3/myTrades з cursor-based пагінацією за fromId або startTime. Згідно з документацією Binance, курсор забезпечує стабільний час відповіді, на відміну від offset-based, який на 10 000 записах сповільнюється в 3 рази.
Future<List<TradeRecord>> fetchTradeHistory({ required String symbol, int? fromId, DateTime? startTime, int limit = 50, }) async { final response = await dio.get('/api/v3/myTrades', queryParameters: { 'symbol': symbol.replaceAll('/', ''), if (fromId != null) 'fromId': fromId, if (startTime != null) 'startTime': startTime.millisecondsSinceEpoch, 'limit': limit, 'timestamp': DateTime.now().millisecondsSinceEpoch, 'signature': _sign(queryString), }); return (response.data as List).map(TradeRecord.fromJson).toList(); } Порівняйте cursor-based пагінацію з offset-based: cursor-based у 3 рази швидший при обсязі понад 1 000 записів. Ми рекомендуємо cursor-based для всіх біржових історій.
Real-time оновлення надходять через WebSocket: канал executionReport (Binance) або orders channel. При отриманні події додаємо угоду на початок списку та оновлюємо статус ордера без повного перезавантаження. Затримка становить близько 50 мс.
| Канал | Призначення | Частота оновлення |
|---|---|---|
| REST | Первинне завантаження + пагінація | При відкритті екрана / підвантаженні |
| WebSocket | Нові угоди, статуси | Миттєво |
Приклад обробки WebSocket-повідомлення (Binance)
StreamSubscription<ExecutionReport> _subscribeExecutionReport() { return websocket.stream('executionReport').listen((event) { final report = ExecutionReport.fromJson(event); if (report.executionType == 'TRADE') { _trades.insert(0, report.toTradeRecord()); _updateOrderStatus(report.orderId, report.orderStatus); _updatePnl(report.symbol); _notifyListeners(); } }); } Плавна прокрутка при тисячах ордерів
ListView.builder з пагінацією — єдиний правильний підхід. Звичайний ListView з 5 000 віджетів викликає jank та OOM на слабких пристроях. Віртуалізація через ListView.builder у 10 разів ефективніша за невіртуалізований список. Ми також використовуємо NotificationListener для lazy-завантаження наступних сторінок при прокрутці до кінця:
NotificationListener<ScrollNotification>( onNotification: (notification) { if (notification is ScrollEndNotification && notification.metrics.pixels >= notification.metrics.maxScrollExtent - 200) { _loadNextPage(); } return false; }, child: ListView.builder( itemCount: _trades.length + (_hasMore ? 1 : 0), itemBuilder: (context, index) { if (index == _trades.length) { return const Center(child: CircularProgressIndicator()); } return TradeRow(trade: _trades[index]); }, ), ) Фільтрацію за торговою парою, стороною, типом ордера та датою застосовуємо на сервері — інакше при великій історії доведеться скачати все та фільтрувати локально. DateTimeRange picker з пресетами «сьогодні / 7 днів / 30 днів» прискорює вибір. Фільтрація скорочує обсяг даних у 5 разів для типового користувача.
| Тип фільтра | Параметри | Приклад |
|---|---|---|
| За парою | symbol | BTC/USDT, ETH/USDT |
| За стороною | side | buy, sell |
| За типом ордера | orderType | market, limit, stop_limit |
| За статусом | status | filled, cancelled, partially_filled |
| За датою | startTime, endTime | діапазон 7 днів |
Колірна індикація та читабельність
Стандарт: buy — зелений, sell — червоний. Статуси: filled — основний колір, cancelled — сірий, partially_filled — помаранчевий. P&L: зелений з + для прибутку, червоний з - для збитку. Ціна виконання трохи більша за інші поля. Це відповідає UX-конвенціям біржових додатків і знижує кількість помилок користувача.
Експорт у CSV
Кнопка експорту — стандартна вимога для податкової. Формуємо CSV з BOM, поля: Date, Pair, Side, Price, Quantity, Fee, Fee Asset, Total. Фільтрація за датою дозволяє вивантажити лише потрібний період, що скорочує обсяг даних у 5 разів для типового користувача. Економія часу на підготовку звітів — до 2 тижнів на рік.
Що входить в роботу
- REST API інтеграція з пагінацією та підписом запитів (HMAC-SHA256)
- WebSocket для real-time оновлень
- Віртуалізований список з лінивим завантаженням
- Фільтри за парою, стороною, датою
- Відображення P&L (з API або розрахунок за FIFO)
- Експорт у CSV
Процес інтеграції
- Аналітика: вивчаємо API біржі, визначаємо ендпоінти та ліміти.
- Підключення API: налаштовуємо HMAC-SHA256 підпис та WebSocket.
- Реалізація UI: створюємо віртуалізований список з фільтрами. Зв'яжіться з нами для обговорення деталей.
- Тестування: перевіряємо на 10 000 записів, вимірюємо продуктивність.
- Деплой: публікуємо в App Store / Google Play.
Типові помилки при інтеграції
- Неправильна обробка cursor-пагінації (пропуск записів при паралельних запитах).
- Ігнорування rate limits — блокування API.
- Неврахування комісій у розрахунку P&L — спотворення прибутку на 5–10%.
- Відсутність обробки стану часткового виконання ордера.
Строки
Базова історія з пагінацією та фільтрами — від 3 до 5 днів. З real-time, P&L та експортом — 1–2 тижні. Вартість розраховується індивідуально. Наша команда має більше 7 років досвіду в fintech та 50+ реалізованих проектів. Ми гарантуємо стабільну роботу модуля на 99,9% часу. Замовте інтеграцію для вашої біржі — наші інженери мають понад 50 реалізованих fintech-проектів. Отримайте консультацію з архітектури через форму зв'язку.







