30 KiB
Build 057 — Исследование REST/WebSocket Trade Feed (Time & Sales)
Engineering Migration Report
Контроль документа
| Свойство | Значение |
|---|---|
| Build | 057 |
| Название | Исследование REST/WebSocket Trade Feed (Time & Sales) |
| Статус | Completed |
| Проект | Dzentra |
| Подсистема | Market Data Acquisition |
| Компонент | Trades Feed |
| Версия | 1.0 |
| Дата | 2026-07-16 |
Цель Build
После завершения полной миграции OHLCV было принято решение перейти к следующему типу рыночных данных — Trade Feed (Time & Sales).
Перед реализацией нового канонического модуля требовалось определить:
- существует ли полноценный REST API;
- существует ли полноценный WebSocket Feed;
- совпадают ли данные REST и WebSocket;
- какие поля действительно предоставляет биржа;
- имеются ли скрытые ограничения;
- какие особенности необходимо учитывать при проектировании новой архитектуры.
Главной целью Build являлось не написание кода, а инженерное исследование поведения API.
Исследуемые интерфейсы
Были исследованы два независимых источника данных.
REST
GET /api/v2/aggTrades
Назначение:
получение последних агрегированных сделок.
Swagger предоставляет параметры:
- symbol
- fromId
- startTime
- endTime
- limit
WebSocket
destination = trades.subscribe
назначение:
подписка на поток новых сделок.
Получаемые события:
destination = internal.trade
Выполненные задачи
В рамках Build были последовательно проверены:
- REST endpoint;
- WebSocket endpoint;
- формат ответа;
- соответствие Swagger документации;
- работа Demo;
- работа Production;
- соответствие REST и WebSocket;
- стабильность подписки;
- возможность дальнейшего использования в Dzentra.
Проверка REST API
Для исследования использовались реальные запросы.
Основная команда:
curl -sS -G \
"https://api-adapter.dzengi.com/api/v2/aggTrades" \
--data-urlencode "symbol=BTC/USD_LEVERAGE" \
--data-urlencode "limit=10" \
| python -m json.tool
Полученный формат REST
Каждая запись имеет вид:
{
"a": 2134857062,
"p": "64497.25",
"q": "0.005",
"T": 1784218066823,
"m": false
}
Расшифровка полей
| Поле | Значение |
|---|---|
| a | идентификатор сделки |
| p | цена исполнения |
| q | объём сделки |
| T | timestamp сделки |
| m | сторона инициатора сделки |
Особенности REST
Во время исследования было установлено:
- данные отсортированы по времени;
- записи идут от новых к старым;
- timestamp имеет миллисекундную точность;
- объём может возвращаться в научной записи (
1.0E-4); - API поддерживает выборку по limit;
- идентификатор сделки монотонно возрастает.
Проверка Demo окружения
Первоначально исследования проводились через Demo API.
Использовался endpoint:
https://demo-api-adapter.dzengi.com/api/v2/aggTrades
Во время длительного наблюдения было обнаружено:
- результаты практически не меняются;
- новые сделки появляются крайне редко;
- иногда ответы полностью совпадают на протяжении нескольких минут.
Из этого был сделан вывод, что Demo нельзя использовать для проверки непрерывного потока сделок.
Проверка Production
После перехода на Production API ситуация изменилась.
Использовался endpoint:
https://api-adapter.dzengi.com/api/v2/aggTrades
Повторные запросы начали возвращать постоянно обновляющийся поток последних сделок.
Это подтвердило:
- REST endpoint является рабочим;
- данные обновляются в реальном времени;
- Production подходит для проверки Time & Sales.
Проверка поддержки символов
Во время тестирования было установлено:
BTC/USD
не поддерживается.
REST возвращает ошибку:
{
"code": -1128,
"msg": "Invalid symbol: BTC/USD"
}
При этом корректным является символ:
BTC/USD_LEVERAGE
Именно его необходимо использовать в дальнейшем при работе с Trade Feed.
Предварительные выводы по REST
На данном этапе исследования было подтверждено:
✔ endpoint полностью работоспособен;
✔ формат данных соответствует Swagger;
✔ поля достаточны для построения Time & Sales;
✔ данные поступают в режиме реального времени;
✔ Demo окружение не подходит для полноценного тестирования активности рынка;
✔ дальнейшие исследования следует проводить исключительно через Production API.
Исследование WebSocket
После завершения анализа REST был исследован поток WebSocket.
Использовался раздел Swagger:
trades.subscribe
Документация описывает механизм подписки на поток сделок.
Формат подписки
Клиент отправляет сообщение:
{
"correlationId": "<uuid>",
"destination": "trades.subscribe",
"payload": {
"symbols": [
"BTC/USD_LEVERAGE"
]
}
}
После успешной обработки сервер отвечает подтверждением.
ACK-сообщение
Первое сообщение всегда имеет вид:
{
"correlationId": "...",
"destination": "trades.subscribe",
"payload": {
"subscriptions": {
"BTC/USD_LEVERAGE": "PROCESSED"
}
},
"status": "OK"
}
Это сообщение не содержит рыночных данных.
Оно означает исключительно успешную регистрацию подписки.
Проверка нескольких символов
Для проверки обработки ошибок была отправлена подписка одновременно на два символа:
{
"symbols": [
"BTC/USD",
"BTC/USD_LEVERAGE"
]
}
Ответ сервера:
{
"subscriptions": {
"BTC/USD": "ERROR: BTC/USD not found",
"BTC/USD_LEVERAGE": "PROCESSED"
}
}
Из этого был сделан важный вывод.
Подписка обрабатывается отдельно для каждого инструмента.
Ошибка одного символа не приводит к отмене остальных подписок.
Первоначальное тестирование Demo
Первые проверки выполнялись на Demo окружении.
Подписка проходила успешно.
Получалось корректное ACK.
Однако далее:
- сообщения сделок не приходили;
- соединение оставалось открытым;
- Ping/Pong работал корректно;
- сервер не разрывал соединение.
Несмотря на пятиминутное ожидание поток оставался пустым.
Вывод по Demo
Полученные результаты полностью совпали с поведением REST.
Demo практически не генерирует торговую активность.
Поэтому отсутствие сообщений не является ошибкой клиента.
Переход на Production
Для окончательной проверки было принято решение перейти на Production API.
Использовались настройки:
EXCHANGE_BASE_URL=https://api-adapter.dzengi.com
EXCHANGE_WS_URL=wss://api-adapter.dzengi.com
После этого поток начал немедленно передавать сделки.
Первый Trade Event
Первое сообщение выглядело следующим образом.
{
"destination": "internal.trade",
"payload": {
"buyer": false,
"id": 2134846831,
"orderId": "00a02503-0079-54c4-0000-000081e62b58",
"price": 64555.55,
"size": 0.002,
"symbol": "BTC/USD_LEVERAGE",
"ts": 1784218012030
},
"status": "OK"
}
Именно это сообщение является каноническим Trade Event.
Структура Trade Event
Сервер публикует сообщения типа
destination = internal.trade
Поля сообщения:
| Поле | Назначение |
|---|---|
| id | уникальный идентификатор сделки |
| price | цена исполнения |
| size | объём |
| ts | время исполнения |
| buyer | направление сделки |
| symbol | торговый инструмент |
| orderId | внутренний идентификатор биржи |
Семантика buyer
Экспериментально подтверждено:
buyer = true
означает
инициатор сделки — покупатель.
Соответственно
buyer = false
означает
инициатор сделки — продавец.
Данное поле полностью соответствует REST-полю:
m
по следующему правилу:
buyer == !m
То есть:
| REST m | WebSocket buyer |
|---|---|
| true | false |
| false | true |
Это полностью соответствует классической Binance-семантике:
m = isBuyerMaker
Частота сообщений
Во время наблюдения было получено множество последовательных сообщений.
Интервал между ними не фиксирован.
Были зарегистрированы:
- менее секунды;
- несколько секунд;
- десятки секунд.
Таким образом поток является событийным.
Сообщения появляются только после фактического исполнения новой сделки.
Стабильность соединения
Во время длительного тестирования:
- соединение не разрывалось;
- Ping/Pong работал корректно;
- подписка сохранялась;
- повторной регистрации не требовалось.
Это подтверждает возможность использования данного WebSocket для постоянной фоновой работы Dzentra.
Диагностический скрипт
Для исследования был разработан отдельный диагностический инструмент:
scripts/check_trades_websocket.py
Назначение:
- открытие WebSocket;
- регистрация подписки;
- вывод ACK;
- вывод всех Trade Event;
- отображение времени получения сообщений;
- автоматический Ping;
- контроль таймаутов;
- длительное наблюдение за потоком.
Скрипт не является частью рабочей архитектуры.
Он предназначен исключительно для инженерной диагностики и проверки API.
Запуск скрипта
Для Demo:
PYTHONPATH=. python scripts/check_trades_websocket.py
Для Production:
EXCHANGE_BASE_URL=https://api-adapter.dzengi.com \
EXCHANGE_WS_URL=wss://api-adapter.dzengi.com \
EXCHANGE_API_KEY="" \
PYTHONPATH=. \
python scripts/check_trades_websocket.py
Проверенные сценарии
Во время исследования были успешно проверены:
- успешная регистрация подписки;
- получение ACK;
- получение Trade Event;
- получение нескольких подряд Trade Event;
- работа Ping/Pong;
- длительное соединение;
- отсутствие ложных сообщений;
- корректная обработка нескольких символов;
- обработка ошибки неизвестного символа.
Сопоставление REST и WebSocket
Главной задачей исследования было доказать, что REST и WebSocket описывают одни и те же биржевые сделки, а не различные представления рынка.
Для этого была выполнена серия сравнительных экспериментов.
Методика проверки
Алгоритм проверки был следующим.
- Запустить подписку
trades.subscribe. - Дождаться получения новой сделки через WebSocket.
- Зафиксировать:
- id;
- цену;
- объём;
- timestamp;
- направление сделки.
- Немедленно запросить последние сделки через REST.
- Найти сделку по идентификатору.
Проверка №1
Через WebSocket была получена сделка:
{
"buyer": false,
"id": 2134846831,
"price": 64555.55,
"size": 0.002,
"symbol": "BTC/USD_LEVERAGE",
"ts": 1784218012030
}
После этого был выполнен REST-запрос:
curl -G \
https://api-adapter.dzengi.com/api/v2/aggTrades \
--data-urlencode "symbol=BTC/USD_LEVERAGE" \
--data-urlencode "limit=100"
Далее был выполнен поиск по ID сделки.
Полученный результат:
{
"a": 2134846831,
"p": "64555.55",
"q": "0.002",
"T": 1784218012030,
"m": true
}
Совпадение полей
Было подтверждено полное совпадение.
| REST | WebSocket |
|---|---|
| a | id |
| p | price |
| q | size |
| T | ts |
| m | !buyer |
Все значения совпали полностью.
Проверка нескольких сообщений
Аналогичная проверка была выполнена ещё для нескольких последовательных событий.
Каждый раз наблюдалось одинаковое соответствие:
- идентификатор сделки совпадает;
- цена совпадает;
- объём совпадает;
- timestamp совпадает;
- направление определяется инверсией поля
buyer.
Таким образом экспериментально доказано, что REST и WebSocket используют единую торговую базу данных.
Итоговая схема соответствия
Exchange Matching Engine
│
┌─────────────┴─────────────┐
│ │
REST aggTrades WebSocket trades.subscribe
│ │
▼ ▼
Aggregated Trade internal.trade
│ │
└─────────────┬─────────────┘
▼
Canonical Trade Model
Это означает, что в архитектуре Dzentra оба источника должны преобразовываться в одну каноническую модель, а не существовать как независимые типы данных.
Архитектурные выводы
По результатам исследования были сформированы следующие инженерные выводы.
1. REST и WebSocket описывают одну сущность
Различается только транспорт доставки.
REST используется для получения истории.
WebSocket используется для получения новых событий.
Модель данных должна быть общей.
2. Канонической сущностью становится Trade
Не существует причин создавать отдельные модели:
- RestTrade;
- WsTrade;
- InternalTrade.
Должна существовать одна модель:
Trade
которая используется всеми компонентами системы.
3. Trade Feed должен быть независимым модулем
Как и Candle Feed, новый модуль должен располагаться в подсистеме Market Data Acquisition.
Предварительная структура:
market_data/acquisition/
models/
trade.py
feeds/
trades_feed.py
handlers/
trades_handler.py
adapters/dzengi/
rest.py
websocket.py
validation/
schema.py
values.py
4. REST и WebSocket должны использовать общий Handler
Различаться должны только источники получения документов.
Схема обработки должна повторять архитектуру Candle Feed:
REST Document
│
▼
Parser
│
▼
Validation
│
▼
Mapper
│
▼
Trade
и
WebSocket Document
│
▼
Parser
│
▼
Validation
│
▼
Mapper
│
▼
Trade
5. Внутри Dzentra не должно существовать транспортных моделей
После обработки все компоненты должны работать исключительно с:
Trade
Они не должны знать:
- откуда пришли данные;
- были ли они получены через REST;
- были ли они получены через WebSocket.
Возможности будущего Trade Feed
После реализации модуль сможет использоваться для решения задач, которые невозможно решить только по свечам.
В частности:
- построение Time & Sales;
- определение текущего торгового потока;
- оценка направления агрессора (Buyer / Seller);
- обнаружение крупных рыночных сделок;
- поиск всплесков активности;
- вычисление Buy/Sell Imbalance;
- вычисление Trade Delta;
- обнаружение поглощений;
- обнаружение кульминаций объёма;
- анализ последовательности исполнения сделок;
- подтверждение истинности пробоев;
- подтверждение ложных пробоев;
- анализ микроструктуры рынка.
Все эти алгоритмы будут использовать одну и ту же каноническую модель Trade.
Итоги Build 057
В рамках Build были полностью исследованы REST и WebSocket интерфейсы Trade Feed.
Подтверждено:
- ✔ REST
GET /api/v2/aggTradesполностью работоспособен. - ✔ WebSocket
trades.subscribeполностью работоспособен. - ✔ Demo окружение практически не генерирует поток сделок и не подходит для тестирования.
- ✔ Production окружение генерирует полноценный поток Trade Event.
- ✔ REST и WebSocket описывают одну и ту же сделку.
- ✔ Идентификаторы, цены, объёмы и временные метки полностью совпадают.
- ✔ Поле
buyerявляется логической инверсией REST-поляm. - ✔ Разработан и протестирован диагностический скрипт
scripts/check_trades_websocket.py. - ✔ Подготовлена архитектурная база для реализации канонического Trade Feed.
Следующий этап
Следующим Build станет Build 058 — Canonical Trade Model Foundation.
В его рамках будет разработан первый полноценный компонент нового Trade Feed:
- каноническая модель
Trade; - REST Parser;
- REST Validation;
- REST Mapper;
- базовые unit-тесты;
- интеграция в существующую архитектуру
Market Data Acquisition.
После этого система Dzentra получит вторую полноценную каноническую рыночную сущность после завершённой миграции OHLCV — поток реальных биржевых сделок (Time & Sales).
Приложение A — Использование диагностического скрипта Trade WebSocket
Назначение скрипта
В рамках Build 057 был разработан отдельный диагностический инструмент
scripts/check_trades_websocket.py
Скрипт предназначен исключительно для инженерной проверки работы биржевого Trade WebSocket API.
Он не является частью рабочего Runtime Dzentra и не используется торговой логикой.
Его задачи:
- проверка открытия WebSocket-соединения;
- проверка успешной регистрации подписки;
- проверка получения ACK;
- проверка получения новых сделок;
- проверка корректности Ping/Pong;
- длительное наблюдение за потоком;
- диагностика работы Demo и Production окружений;
- подтверждение соответствия WebSocket и REST.
Проверяемые возможности
Во время работы скрипт автоматически проверяет:
- возможность подключения к серверу;
- правильность формирования запроса подписки;
- успешность регистрации подписки;
- получение сообщений подтверждения;
- получение сообщений Trade Event;
- сохранение соединения при отсутствии новых сделок;
- работу Ping/Pong;
- получение нескольких последовательных сделок;
- возможность длительной непрерывной работы.
Используемые переменные окружения
Скрипт использует стандартные настройки проекта.
Основные переменные:
EXCHANGE_BASE_URL
EXCHANGE_WS_URL
EXCHANGE_API_KEY
Если переменные не заданы, используются значения из конфигурации Dzentra.
Запуск Demo
PYTHONPATH=. python scripts/check_trades_websocket.py
Используется:
wss://demo-api-adapter.dzengi.com/connect
Ожидаемый результат Demo
При корректной работе вывод должен быть примерно следующим.
Subscription request sent.
Message #1
ACK
После этого возможно длительное отсутствие сообщений Trade Event.
Периодически выводится сообщение:
No trade event yet; connection is alive.
Это означает:
- соединение активно;
- Ping/Pong работает;
- новых сделок пока нет.
Подобное поведение является нормальным для Demo окружения.
Запуск Production
Для проверки реального рынка используется:
EXCHANGE_BASE_URL=https://api-adapter.dzengi.com \
EXCHANGE_WS_URL=wss://api-adapter.dzengi.com \
EXCHANGE_API_KEY="" \
PYTHONPATH=. \
python scripts/check_trades_websocket.py
Ожидаемый результат Production
После ACK начинают поступать реальные сделки.
Типичный вывод:
Message #2
TRADE EVENT
{
"destination": "internal.trade",
"payload": {
"id": 2134846831,
"price": 64555.55,
"size": 0.002,
"buyer": false,
"symbol": "BTC/USD_LEVERAGE",
"ts": 1784218012030
}
}
Затем по мере появления новых сделок будут выводиться:
Message #3
TRADE EVENT
Message #4
TRADE EVENT
и так далее.
Интерпретация сообщений
Во время работы возможны два типа сообщений.
ACK
destination = trades.subscribe
означает успешную регистрацию подписки.
TRADE EVENT
destination = internal.trade
означает получение новой сделки с биржи.
Проверка соответствия REST
После получения Trade Event рекомендуется выполнить запрос:
curl -G \
"https://api-adapter.dzengi.com/api/v2/aggTrades" \
--data-urlencode "symbol=BTC/USD_LEVERAGE" \
--data-urlencode "limit=100"
Затем убедиться, что сделка с полученным ID присутствует в REST.
Пример поиска:
curl -sS -G \
"https://api-adapter.dzengi.com/api/v2/aggTrades" \
--data-urlencode "symbol=BTC/USD_LEVERAGE" \
--data-urlencode "limit=100" \
| python -c '
import json
import sys
target_id = 2134846831
items = json.load(sys.stdin)
matches = [
item
for item in items
if item["a"] == target_id
]
print(json.dumps(matches, indent=2))
'
Если вывод содержит одну запись с тем же идентификатором, значит соответствие REST и WebSocket подтверждено.
Диагностические признаки корректной работы
Работа скрипта считается полностью успешной, если выполняются все условия:
- успешно устанавливается WebSocket-соединение;
- получено ACK;
- подписка имеет статус
PROCESSED; - поступают сообщения
internal.trade; - соединение не разрывается при длительном ожидании;
- Ping/Pong выполняется успешно;
- сделки из WebSocket присутствуют в REST
aggTrades.
Итог
Диагностический скрипт полностью подтвердил работоспособность биржевого интерфейса Trade Feed и стал основным инженерным инструментом проверки перед началом реализации канонического модуля Trade (Time & Sales) в Build 058.