Build 060.9: add WebSocket Trade Transport Model

This commit is contained in:
2026-07-19 13:19:47 +03:00
parent 2523d715e7
commit 0b4edca287
3 changed files with 1025 additions and 0 deletions

View File

@@ -0,0 +1,957 @@
# Build 060.9 — WebSocket Trade Transport Model
**Engineering Migration Report**
---
# Контроль документа
| Свойство | Значение |
|----------|----------|
| Build | 060.9 |
| Название | WebSocket Trade Transport Model |
| Статус | Completed |
| Проект | Dzentra |
| Подсистема | Market Data Acquisition |
| Компонент | Trades Feed |
| Версия | 1.0 |
---
# Цель Build
После завершения Build 060.8 система получила источник получения сырых REST-документов Trade (`DzengiTradesDocumentSource`).
Следующим этапом развития является построение полного конвейера обработки сделок, поступающих по WebSocket.
Build 060.9 открывает вторую ветку реализации Trades Feed и вводит транспортную модель, описывающую одно WebSocket-событие биржи.
Данный Build ограничивается исключительно транспортным уровнем (Transport Layer) и не затрагивает Parser, Schema Validation, Value Validation, Mapper, Adapter, Routing и Runtime.
---
# Предпосылки
К моменту начала Build архитектура Dzentra уже содержала транспортные модели для остальных типов рыночных данных.
## REST Trades
```text
REST Document
DzengiRestAggTrade
Trade
```
## WebSocket Quote
```text
WebSocket Quote Document
DzengiWebSocketQuoteResponse
Quote
```
## WebSocket OHLC
```text
WebSocket OHLC Document
DzengiWebSocketOhlcEvent
Candle
```
Для WebSocket Trade аналогичная транспортная модель отсутствовала.
В результате архитектура обработки сделок оставалась неполной и асимметричной относительно остальных типов рыночных данных.
---
# Архитектурное основание
Build 060.9 не проектирует транспортный контракт самостоятельно.
В качестве первичного источника использованы результаты инженерного исследования, выполненного в рамках Build 057.
Во время исследования были изучены:
- REST `aggTrades`;
- WebSocket `trades.subscribe`;
- реальные Production-сообщения;
- соответствие REST и WebSocket контрактов.
Исследование подтвердило фактический формат события:
```json
{
"status": "OK",
"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
}
}
```
Именно этот контракт принят за основу реализации Build 060.9.
---
# Результаты архитектурного аудита
Перед началом реализации был выполнен аудит существующего адаптера Dzengi.
Подтверждено наличие транспортных моделей:
- `DzengiRestAggTrade`;
- `DzengiWebSocketQuoteResponse`;
- `DzengiWebSocketOhlcEvent`.
Также подтверждено существование полной иерархии ошибок обработки Trade:
- `TradeTransportError`;
- `TradeSchemaError`;
- `TradeParseError`;
- `TradeValueError`;
- `TradeMappingError`.
Одновременно подтверждено отсутствие следующих компонентов WebSocket Trade Pipeline:
- Transport Model;
- Schema Validation;
- Parser;
- Value Validation;
- Mapper;
- Adapter.
Таким образом Build 060.9 полностью соответствует утверждённой дорожной карте серии 060 и закрывает первый этап WebSocket-ветки обработки сделок.
---
# Архитектурное решение
В транспортный слой адаптера введена новая модель
```text
DzengiWebSocketTradeEvent
```
Она представляет собой типизированное описание одного события
```text
destination = internal.trade
```
и относится исключительно к транспортному уровню адаптера.
Модель не является:
- внутренней моделью Dzentra;
- бизнес-сущностью;
- канонической моделью `Trade`.
Её единственная ответственность — хранение уже разобранных транспортных данных WebSocket-сообщения.
---
# Реализованная транспортная модель
В файл
```text
src/market_data/acquisition/adapters/dzengi/models.py
```
добавлена новая транспортная модель:
```python
@dataclass(frozen=True, slots=True)
class DzengiWebSocketTradeEvent:
trade_id: int
price: DzengiRawNumeric
size: DzengiRawNumeric
timestamp: int
symbol: str
buyer: bool
order_id: str
```
Модель размещена рядом с существующими транспортными моделями адаптера.
Итоговая последовательность моделей выглядит следующим образом:
```text
DzengiRestAggTrade
DzengiWebSocketTradeEvent
DzengiWebSocketOhlcEvent
```
Такое расположение сохраняет логическую группировку транспортных сущностей по типам рыночных данных.
---
# Почему используется dataclass
`DzengiWebSocketTradeEvent` представляет собой неизменяемый контейнер транспортных данных.
Модель:
- не содержит бизнес-логики;
- не выполняет преобразование типов;
- не выполняет структурную проверку;
- не выполняет Value Validation;
- не выполняет Mapping;
- не взаимодействует с сетью;
- не содержит изменяемого состояния.
Поэтому используется конструкция
```python
@dataclass(frozen=True, slots=True)
```
что полностью соответствует архитектурному стилю остальных транспортных моделей проекта.
---
# Назначение полей
В модель включены только поля, подтверждённые реальными Production-сообщениями.
| Поле | Назначение |
|------|------------|
| `trade_id` | идентификатор сделки |
| `price` | цена сделки |
| `size` | объём сделки |
| `timestamp` | время исполнения |
| `symbol` | торговый инструмент |
| `buyer` | сторона инициатора |
| `order_id` | идентификатор ордера |
Каждое поле соответствует данным, полученным в ходе исследования Build 057.
---
# Нормализация имён полей
Во входящем WebSocket JSON используются ключи
```text
id
ts
orderId
```
Во внутренней транспортной модели используются нормализованные имена
```text
trade_id
timestamp
order_id
```
Подобная нормализация уже используется в остальных транспортных моделях Dzentra и обеспечивает единый стиль внутренних контрактов.
Преобразование JSON-ключей выполняется Parser.
Транспортная модель не зависит от формата сериализации входящего сообщения.
---
# Использование DzengiRawNumeric
Поля
```python
price
size
```
имеют тип
```python
DzengiRawNumeric
```
Такое решение уже применяется в REST Trade и других транспортных моделях адаптера.
Использование `DzengiRawNumeric` означает, что транспортная модель сохраняет числовые значения в исходном виде без выполнения каких-либо преобразований.
Ответственность за интерпретацию числовых значений относится к последующим этапам конвейера.
Это позволяет полностью разделить:
- транспортное представление данных;
- их синтаксическую корректность;
- семантическую валидацию;
- преобразование в каноническую модель.
# Неизменяемость модели
Параметр
```python
frozen=True
```
гарантирует, что после создания экземпляра его поля не могут быть изменены.
Это важно для транспортного слоя, поскольку модель представляет собой результат разбора одного входящего сообщения и после создания должна оставаться неизменной.
После формирования экземпляр проходит через последующие этапы конвейера:
```text
Validated WebSocket Document
DzengiWebSocketTradeEvent
Value Validation
Mapper
Trade
```
Неизменяемость транспортной модели обеспечивает воспроизводимость обработки сообщения и исключает случайную модификацию данных на последующих этапах.
---
# Использование slots
Параметр
```python
slots=True
```
используется для:
- фиксации структуры модели;
- предотвращения динамического добавления атрибутов;
- уменьшения накладных расходов на экземпляр;
- сохранения единого архитектурного стиля транспортных моделей Dzentra.
Экземпляр `DzengiWebSocketTradeEvent` не содержит `__dict__`, что дополнительно подтверждает его роль как лёгкого транспортного контейнера.
---
# Граница ответственности модели
`DzengiWebSocketTradeEvent` отвечает исключительно за хранение уже разобранных транспортных данных.
В обязанности модели **не входит**:
- проверка структуры исходного JSON;
- проверка значения `status`;
- проверка `destination`;
- проверка наличия обязательных полей;
- проверка корректности цены;
- проверка корректности объёма;
- проверка корректности timestamp;
- определение бизнес-семантики сделки;
- преобразование в каноническую модель `Trade`.
Каждая из перечисленных задач относится к отдельному уровню архитектуры и будет реализована в соответствующих Build.
---
# Целевой конвейер обработки WebSocket Trade
После завершения Builds 060.9060.14 полный конвейер обработки будет иметь следующий вид:
```text
Raw WebSocket Object
WebSocket Trade Schema Validation
ValidatedWebSocketTradeDocument
WebSocket Trade Parser
DzengiWebSocketTradeEvent
WebSocket Trade Value Validation
WebSocket Trade Mapper
Trade
```
Build 060.9 реализует только один компонент этого конвейера:
```text
DzengiWebSocketTradeEvent
```
Все остальные этапы будут реализованы последовательно в следующих Build.
---
# Соотношение с канонической моделью Trade
В Build 060.1 была реализована каноническая модель
```text
Trade
```
Она используется внутренними компонентами Dzentra и полностью независима от способа получения рыночных данных.
`DzengiWebSocketTradeEvent` не заменяет `Trade`.
Эти модели относятся к различным архитектурным уровням.
```text
DzengiWebSocketTradeEvent
```
— транспортное представление WebSocket-события конкретной биржи.
```text
Trade
```
— единая внутренняя модель сделки, используемая всеми компонентами Dzentra.
После реализации Mapper транспортная модель будет преобразовываться в каноническую.
---
# Соотношение REST и WebSocket транспортных моделей
REST и WebSocket описывают одну и ту же биржевую сделку, но используют различные транспортные контракты.
REST:
```json
{
"a": 2134846831,
"p": "64555.55",
"q": "0.002",
"T": 1784218012030,
"m": true
}
```
WebSocket:
```json
{
"id": 2134846831,
"price": 64555.55,
"size": 0.002,
"symbol": "BTC/USD_LEVERAGE",
"ts": 1784218012030,
"buyer": false,
"orderId": "00a02503-0079-54c4-0000-000081e62b58"
}
```
Несмотря на различие транспортных контрактов, обе модели описывают одну и ту же биржевую сущность и далее преобразуются в единую каноническую модель `Trade`.
---
# Сопоставление полей REST и WebSocket
| Каноническая семантика | REST | WebSocket |
|------------------------|------|-----------|
| идентификатор сделки | `a` | `id` |
| цена | `p` | `price` |
| объём | `q` | `size` |
| timestamp | `T` | `ts` |
| сторона инициатора | `m` | `buyer` |
| торговый инструмент | отсутствует | `symbol` |
| идентификатор ордера | отсутствует | `orderId` |
Согласно результатам исследования Build 057 выполняется соответствие:
```text
buyer == !m
```
Данная нормализация будет реализована WebSocket Trade Mapper в Build 060.13.
---
# Обработка транспортной оболочки сообщения
Фактическое WebSocket-сообщение имеет следующую структуру:
```json
{
"status": "OK",
"destination": "internal.trade",
"payload": {
"...": "..."
}
}
```
`DzengiWebSocketTradeEvent` представляет исключительно содержимое объекта `payload`.
Поля
```text
status
destination
```
не входят в транспортную модель, поскольку относятся к внешней оболочке WebSocket-документа.
Их обработка будет реализована в Build 060.10 — WebSocket Trade Schema Validation.
---
# Решение по полю clientOrderId
Во время исследования было обнаружено, что Swagger содержит дополнительное поле
```text
clientOrderId
```
Однако анализ реальных Production-сообщений подтвердил обязательное наличие только поля
```text
orderId
```
Поэтому `clientOrderId` не включён в обязательный транспортный контракт Build 060.9.
Это исключает зависимость Parser от поля, которое отсутствует в фактических сообщениях биржи.
При реализации Schema Validation будут использоваться следующие принципы:
- обязательными считаются только поля, подтверждённые Production;
- наличие дополнительных полей не должно приводить к ошибке;
- неподтверждённые поля не становятся обязательными автоматически.
---
# Изменённые файлы
В рамках Build были изменены только два файла.
## Транспортная модель
```text
src/market_data/acquisition/adapters/dzengi/models.py
```
Добавлена новая модель
```text
DzengiWebSocketTradeEvent
```
При этом существующие модели, импорты и поведение файла не изменялись.
---
## Unit-тесты
```text
tests/unit/market_data/acquisition/adapters/dzengi/test_models.py
```
Добавлены три новых unit-теста транспортной модели.
---
# Добавленные тесты
## Проверка полного транспортного контракта
Тест
```text
test_websocket_trade_event_stores_complete_transport_contract
```
проверяет сохранение всех подтверждённых полей транспортной модели.
---
## Проверка сохранения исходных числовых значений
Тест
```text
test_websocket_trade_event_preserves_raw_numeric_values
```
подтверждает, что модель сохраняет цену и объём без преобразования типов.
---
## Проверка неизменяемости и slots
Тест
```text
test_websocket_trade_event_is_immutable_and_uses_slots
```
подтверждает:
- отсутствие `__dict__`;
- невозможность изменения экземпляра после создания.
# Результаты тестирования
Выполнен целевой запуск unit-тестов транспортных моделей:
```bash
python -m pytest \
tests/unit/market_data/acquisition/adapters/dzengi/test_models.py \
-q
```
Результат выполнения:
```text
12 passed in 0.03s
```
До реализации Build 060.9 файл содержал девять тестов.
После добавления транспортной модели количество тестов увеличилось:
```text
9 → 12
```
что соответствует трём новым проверкам.
---
# Регрессионное тестирование
После завершения реализации был выполнен полный запуск тестов адаптера Dzengi.
```bash
python -m pytest \
tests/unit/market_data/acquisition/adapters/dzengi \
-q
```
Результат:
```text
246 passed in 0.11s
```
До реализации Build 060.9 набор содержал:
```text
243 passed
```
После добавления новой транспортной модели:
```text
246 passed
```
Регрессий не обнаружено.
---
# Проверка компиляции
Выполнена проверка компиляции изменённых файлов:
```bash
python -m compileall \
src/market_data/acquisition/adapters/dzengi/models.py \
tests/unit/market_data/acquisition/adapters/dzengi/test_models.py
```
Компиляция завершилась успешно.
Синтаксические ошибки отсутствуют.
---
# Проверка Git diff
Финальный анализ изменений подтвердил, что Build затронул только согласованный scope.
Добавлены:
- одна транспортная модель;
- три unit-теста.
Не изменялись:
```text
src/market_data/acquisition/adapters/dzengi/parser.py
src/market_data/acquisition/adapters/dzengi/mapper.py
src/market_data/acquisition/adapters/dzengi/websocket.py
src/market_data/acquisition/validation/schema.py
src/market_data/acquisition/validation/values.py
src/market_data/acquisition/exceptions.py
src/market_data/acquisition/runtime/
src/market_data/acquisition/feeds/
src/market_data/acquisition/handlers/
```
Таким образом Build полностью соответствует принципу локальности изменений.
---
# Scope Build 060.9
В рамках данного Build реализовано только:
```text
WebSocket Trade Transport Model
```
В Build **не входят**:
- WebSocket Trade Schema Validation;
- WebSocket Trade Parser;
- WebSocket Trade Value Validation;
- WebSocket Trade Mapper;
- WebSocket Trade Adapter;
- Unified WebSocket Routing;
- Trade Subscription Layer;
- Trades Feed Core;
- Runtime Integration.
Такое разделение обеспечивает атомарность миграции и позволяет независимо проверять каждый архитектурный уровень.
---
# Архитектурный результат
После завершения Build система содержит транспортные представления обеих форм получения сделок.
REST:
```text
REST Trade Document
DzengiRestAggTrade
Trade
```
WebSocket:
```text
WebSocket Trade Document
DzengiWebSocketTradeEvent
Trade
```
На текущем этапе WebSocket-ветка содержит только транспортную модель.
Остальные уровни конвейера будут добавлены последовательно в следующих Build.
---
# Состояние WebSocket Trade Pipeline
После завершения Build 060.9 архитектура конвейера выглядит следующим образом:
```text
Raw WebSocket Trade Document
Schema Validation
Parser
DzengiWebSocketTradeEvent
Value Validation
Mapper
Trade
```
Статус реализации компонентов:
| Компонент | Build | Статус |
|-----------|-------|--------|
| Canonical Trade Model | 060.1 | ✔ Completed |
| WebSocket Trade Transport Model | 060.9 | ✔ Completed |
| WebSocket Trade Schema Validation | 060.10 | Pending |
| WebSocket Trade Parser | 060.11 | Pending |
| WebSocket Trade Value Validation | 060.12 | Pending |
| WebSocket Trade Mapper | 060.13 | Pending |
| WebSocket Trade Adapter | 060.14 | Pending |
| Unified WebSocket Routing | 060.15 | Pending |
---
# Соблюдение архитектурных принципов
В рамках реализации Build сохранены все архитектурные инварианты проекта Dzentra.
## Локальность изменений
Изменены только транспортная модель и её unit-тесты.
---
## Отсутствие преждевременной интеграции
Новая модель не подключена к Adapter, Runtime и Trades Feed до появления всех промежуточных этапов обработки.
---
## Разделение транспортной и канонической моделей
`DzengiWebSocketTradeEvent` используется только внутри транспортного слоя адаптера.
Внутренние компоненты системы продолжают работать исключительно с канонической моделью `Trade`.
---
## Отсутствие бизнес-логики
Транспортная модель не выполняет:
- Parsing;
- Schema Validation;
- Value Validation;
- Mapping;
- Routing;
- Subscription;
- Network I/O.
Она представляет собой исключительно неизменяемый контейнер транспортных данных.
---
## Неизменность существующего поведения
Рабочая логика обработки Quote, OHLC и REST Trade не изменялась.
Build является полностью обратимо-совместимым с существующей архитектурой.
---
# Критерии завершения Build
Build 060.9 считается завершённым, поскольку выполнены все поставленные задачи.
- ✔ исследован Production-контракт WebSocket Trade;
- ✔ использованы результаты Build 057;
- ✔ реализована транспортная модель `DzengiWebSocketTradeEvent`;
- ✔ модель содержит все подтверждённые поля события;
- ✔ цена и объём сохраняются как `DzengiRawNumeric`;
- ✔ модель является неизменяемой;
- ✔ используется `slots`;
- ✔ добавлены три unit-теста;
- ✔ все целевые тесты успешно проходят;
- ✔ регрессионное тестирование успешно завершено;
- ✔ компиляция выполнена без ошибок;
- ✔ изменения не выходят за пределы согласованного scope.
---
# Следующий этап
Следующим этапом дорожной карты является
```text
Build 060.10 — WebSocket Trade Schema Validation
```
Цель Build:
- реализовать структурную проверку WebSocket Trade-документа;
- проверить транспортную оболочку сообщения;
- проверить `status`;
- проверить `destination = internal.trade`;
- проверить наличие объекта `payload`;
- проверить наличие обязательных ключей транспортного события;
- сформировать `ValidatedWebSocketTradeDocument`;
- использовать `TradeSchemaError` для ошибок структуры.
После завершения Build 060.10 конвейер примет следующий вид:
```text
Raw WebSocket Object
validate_dzengi_websocket_trade_schema(...)
ValidatedWebSocketTradeDocument
Build 060.11 — WebSocket Trade Parser
```
Build 060.10 по-прежнему не будет выполнять:
- преобразование значений;
- семантическую валидацию;
- Mapping в `Trade`;
- WebSocket Routing;
- Runtime Integration.
---
# Итог
Build 060.9 завершил формирование транспортного уровня обработки WebSocket Trade, добавив отсутствующую транспортную модель `DzengiWebSocketTradeEvent`.
Новая модель представляет собой типизированное описание события
```text
destination = internal.trade
```
и основана на фактическом Production-контракте, подтверждённом в ходе исследования Build 057.
Реализация полностью соответствует архитектурным принципам Dzentra:
- транспортный слой отделён от канонической модели;
- Build ограничен согласованным scope;
- существующее поведение системы не изменено;
- создан фундамент для последующей реализации WebSocket Trade Schema Validation, Parser, Mapper и полного конвейера Trades Feed.
На этом Build 060.9 считается полностью завершённым.