# 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.9–060.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 считается полностью завершённым.