Разработка системы агрегации данных с нескольких бирж
Торговые системы, работающие одновременно на нескольких биржах, сталкиваются с фундаментальной проблемой: каждая биржа имеет собственный WebSocket API, собственный формат данных, собственные ограничения по частоте запросов и собственные причуды в поведении. Агрегатор превращает этот зоопарк в единый нормализованный поток.
Архитектура агрегатора
Система строится по принципу fan-in: множество источников данных собираются в единый нормализованный поток.
Exchange Connectors — отдельный модуль для каждой биржи. Отвечает за установку WebSocket-соединения, подписку на нужные каналы, handling reconnects и ошибок, парсинг raw-формата биржи в нормализованный.
Normalization Layer — преобразует биржеспецифичные форматы в единую схему. Binance называет поле b (best bid), Kraken — b тоже, но с другой семантикой. OKX использует наносекунды для timestamp, Bitfinex — миллисекунды.
Distribution Layer — публикует нормализованные события в шину (Redis Streams, Kafka) для downstream-потребителей.
Нормализованный формат
Универсальная схема ticker-события:
{
"exchange": "binance",
"symbol": "BTC/USDT",
"timestamp": 1704067200000,
"received_at": 1704067200045,
"bid": 43250.50,
"ask": 43251.00,
"last": 43250.75,
"volume_24h": 28450.123,
"open_24h": 42800.00
}
Поле received_at — время получения данных агрегатором, отличное от биржевого timestamp. Разница между ними — network latency до биржи, полезная метрика для мониторинга.
Работа с rate limits
Каждая биржа ограничивает количество запросов. WebSocket-подключения обычно не лимитированы по сообщениям, но есть лимиты на количество подписок в одном соединении (Binance: 1024 потока на соединение) и скорость отправки команд подписки.
Правильный коннектор управляет очередью подписок с учётом этих ограничений:
class ExchangeConnector:
MAX_SUBSCRIPTIONS_PER_CONN = 1000
SUBSCRIPTION_RATE_LIMIT = 10 # per second
async def subscribe_symbols(self, symbols: list[str]):
# Разбиваем на чанки по размеру соединения
for chunk in chunks(symbols, self.MAX_SUBSCRIPTIONS_PER_CONN):
conn = await self.create_connection()
# Rate-limit подписки
async with self.rate_limiter:
await conn.subscribe(chunk)
Reconnection и data gaps
WebSocket-соединения разрываются. Биржи иногда посылают "ping" и ожидают "pong" в течение строго определённого времени (Binance: 10 минут без pong = disconnect). Правильный коннектор:
- Автоматически отвечает на ping-frames
- Отслеживает время последнего сообщения (heartbeat check)
- При разрыве — exponential backoff reconnect с jitter
- При восстановлении — переподписывается на все символы
- Публикует событие
GAP_DETECTEDс временным диапазоном отсутствующих данных
Downstream-потребители должны обрабатывать GAP-события корректно, особенно если используют скользящие агрегаты.
Latency и синхронизация времени
При сравнении цен на разных биржах критически важна синхронизация времени. Системное время сервера должно синхронизироваться через NTP с точностью до 1–5ms. Большинство клауд-провайдеров предоставляют точный NTP, но это нужно проверять.
Биржи имеют разный network latency — от 1ms (co-location) до 50–100ms для обычного сервера. При арбитражных стратегиях важно учитывать эту задержку.
Мониторинг качества данных
| Метрика | Описание |
|---|---|
| Message rate | Сообщений в секунду на биржу/символ |
| Latency (p50/p99) | Задержка от биржи до агрегатора |
| Gap rate | Количество разрывов в данных в час |
| Reconnect count | Частота переподключений |
| Stale data alerts | Символы без обновлений > X секунд |
Prometheus + Grafana — стандартный стек для этого мониторинга.
Библиотеки и готовые решения
CCXT Pro — WebSocket расширение CCXT с поддержкой 50+ бирж. Хорошая отправная точка для прототипа, но для production часто нужны кастомные коннекторы из-за производительности и специфических требований.
cryptofeed (Python) — специализированная библиотека для криптовалютных feeds с поддержкой 30+ бирж, нормализацией данных и бэкендами для Kafka, Redis, RabbitMQ, PostgreSQL.
Для high-performance систем (< 1ms latency) пишем коннекторы на Rust или Go с нуля.







