# 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.