Files
dzentra_bot/docs/migrations/build_057.md

1111 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
Для исследования использовались реальные запросы.
Основная команда:
```bash
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
Каждая запись имеет вид:
```json
{
"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:
```text
https://demo-api-adapter.dzengi.com/api/v2/aggTrades
```
Во время длительного наблюдения было обнаружено:
- результаты практически не меняются;
- новые сделки появляются крайне редко;
- иногда ответы полностью совпадают на протяжении нескольких минут.
Из этого был сделан вывод, что Demo нельзя использовать для проверки непрерывного потока сделок.
---
# Проверка Production
После перехода на Production API ситуация изменилась.
Использовался endpoint:
```text
https://api-adapter.dzengi.com/api/v2/aggTrades
```
Повторные запросы начали возвращать постоянно обновляющийся поток последних сделок.
Это подтвердило:
- REST endpoint является рабочим;
- данные обновляются в реальном времени;
- Production подходит для проверки Time & Sales.
---
# Проверка поддержки символов
Во время тестирования было установлено:
```
BTC/USD
```
не поддерживается.
REST возвращает ошибку:
```json
{
"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
```
Документация описывает механизм подписки на поток сделок.
---
# Формат подписки
Клиент отправляет сообщение:
```json
{
"correlationId": "<uuid>",
"destination": "trades.subscribe",
"payload": {
"symbols": [
"BTC/USD_LEVERAGE"
]
}
}
```
После успешной обработки сервер отвечает подтверждением.
---
# ACK-сообщение
Первое сообщение всегда имеет вид:
```json
{
"correlationId": "...",
"destination": "trades.subscribe",
"payload": {
"subscriptions": {
"BTC/USD_LEVERAGE": "PROCESSED"
}
},
"status": "OK"
}
```
Это сообщение не содержит рыночных данных.
Оно означает исключительно успешную регистрацию подписки.
---
# Проверка нескольких символов
Для проверки обработки ошибок была отправлена подписка одновременно на два символа:
```json
{
"symbols": [
"BTC/USD",
"BTC/USD_LEVERAGE"
]
}
```
Ответ сервера:
```json
{
"subscriptions": {
"BTC/USD": "ERROR: BTC/USD not found",
"BTC/USD_LEVERAGE": "PROCESSED"
}
}
```
Из этого был сделан важный вывод.
Подписка обрабатывается отдельно для каждого инструмента.
Ошибка одного символа не приводит к отмене остальных подписок.
---
# Первоначальное тестирование Demo
Первые проверки выполнялись на Demo окружении.
Подписка проходила успешно.
Получалось корректное ACK.
Однако далее:
- сообщения сделок не приходили;
- соединение оставалось открытым;
- Ping/Pong работал корректно;
- сервер не разрывал соединение.
Несмотря на пятиминутное ожидание поток оставался пустым.
---
# Вывод по Demo
Полученные результаты полностью совпали с поведением REST.
Demo практически не генерирует торговую активность.
Поэтому отсутствие сообщений не является ошибкой клиента.
---
# Переход на Production
Для окончательной проверки было принято решение перейти на Production API.
Использовались настройки:
```text
EXCHANGE_BASE_URL=https://api-adapter.dzengi.com
EXCHANGE_WS_URL=wss://api-adapter.dzengi.com
```
После этого поток начал немедленно передавать сделки.
---
# Первый Trade Event
Первое сообщение выглядело следующим образом.
```json
{
"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:
```bash
PYTHONPATH=. python scripts/check_trades_websocket.py
```
Для Production:
```bash
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 описывают **одни и те же биржевые сделки**, а не различные представления рынка.
Для этого была выполнена серия сравнительных экспериментов.
---
# Методика проверки
Алгоритм проверки был следующим.
1. Запустить подписку `trades.subscribe`.
2. Дождаться получения новой сделки через WebSocket.
3. Зафиксировать:
- id;
- цену;
- объём;
- timestamp;
- направление сделки.
4. Немедленно запросить последние сделки через REST.
5. Найти сделку по идентификатору.
---
# Проверка №1
Через WebSocket была получена сделка:
```json
{
"buyer": false,
"id": 2134846831,
"price": 64555.55,
"size": 0.002,
"symbol": "BTC/USD_LEVERAGE",
"ts": 1784218012030
}
```
После этого был выполнен REST-запрос:
```bash
curl -G \
https://api-adapter.dzengi.com/api/v2/aggTrades \
--data-urlencode "symbol=BTC/USD_LEVERAGE" \
--data-urlencode "limit=100"
```
Далее был выполнен поиск по ID сделки.
Полученный результат:
```json
{
"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 используют единую торговую базу данных.
---
# Итоговая схема соответствия
```text
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.
Должна существовать одна модель:
```text
Trade
```
которая используется всеми компонентами системы.
---
## 3. Trade Feed должен быть независимым модулем
Как и Candle Feed, новый модуль должен располагаться в подсистеме Market Data Acquisition.
Предварительная структура:
```text
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:
```text
REST Document
Parser
Validation
Mapper
Trade
```
и
```text
WebSocket Document
Parser
Validation
Mapper
Trade
```
---
## 5. Внутри Dzentra не должно существовать транспортных моделей
После обработки все компоненты должны работать исключительно с:
```text
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
```bash
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
Для проверки реального рынка используется:
```bash
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
```
```json
{
"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
```text
destination = trades.subscribe
```
означает успешную регистрацию подписки.
---
## TRADE EVENT
```text
destination = internal.trade
```
означает получение новой сделки с биржи.
---
# Проверка соответствия REST
После получения Trade Event рекомендуется выполнить запрос:
```bash
curl -G \
"https://api-adapter.dzengi.com/api/v2/aggTrades" \
--data-urlencode "symbol=BTC/USD_LEVERAGE" \
--data-urlencode "limit=100"
```
Затем убедиться, что сделка с полученным ID присутствует в REST.
Пример поиска:
```bash
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.