From 0b4edca28756b1b9d288818960ce5383403cd4d9 Mon Sep 17 00:00:00 2001 From: Sergey Date: Sun, 19 Jul 2026 13:19:47 +0300 Subject: [PATCH] Build 060.9: add WebSocket Trade Transport Model --- .../acquisition/adapters/dzengi/models.py | 13 + .../adapters/dzengi/test_models.py | 55 + docs/migrations/build_060_9.md | 957 ++++++++++++++++++ 3 files changed, 1025 insertions(+) create mode 100644 docs/migrations/build_060_9.md diff --git a/app/src/market_data/acquisition/adapters/dzengi/models.py b/app/src/market_data/acquisition/adapters/dzengi/models.py index b8af757..33a32b2 100644 --- a/app/src/market_data/acquisition/adapters/dzengi/models.py +++ b/app/src/market_data/acquisition/adapters/dzengi/models.py @@ -161,6 +161,19 @@ class DzengiRestAggTrade: buyer_is_maker: bool +# Транспортное представление одного события +# Dzengi WebSocket internal.trade. +@dataclass(frozen=True, slots=True) +class DzengiWebSocketTradeEvent: + trade_id: int + price: DzengiRawNumeric + size: DzengiRawNumeric + timestamp: int + symbol: str + buyer: bool + order_id: str + + # Транспортное представление одного события Dzengi WebSocket ohlc.event. # # Событие содержит завершённую OHLC-свечу без объёма и поэтому не является diff --git a/app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py b/app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py index f22dc44..331a85b 100644 --- a/app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py +++ b/app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py @@ -15,6 +15,7 @@ from src.market_data.acquisition.adapters.dzengi.models import ( DzengiRateLimit, DzengiRestAggTrade, DzengiUnknownFilter, + DzengiWebSocketTradeEvent, ) @@ -250,3 +251,57 @@ def test_rest_agg_trade_is_immutable_and_uses_slots() -> None: with pytest.raises(FrozenInstanceError): trade.price = "1.00" # type: ignore[misc] + + +def test_websocket_trade_event_stores_complete_transport_contract() -> None: + event = DzengiWebSocketTradeEvent( + trade_id=2134846831, + price=64555.55, + size=0.002, + timestamp=1784218012030, + symbol="BTC/USD_LEVERAGE", + buyer=False, + order_id="00a02503-0079-54c4-0000-000081e62b58", + ) + + assert event.trade_id == 2134846831 + assert event.price == 64555.55 + assert event.size == 0.002 + assert event.timestamp == 1784218012030 + assert event.symbol == "BTC/USD_LEVERAGE" + assert event.buyer is False + assert event.order_id == "00a02503-0079-54c4-0000-000081e62b58" + + +def test_websocket_trade_event_preserves_raw_numeric_values() -> None: + event = DzengiWebSocketTradeEvent( + trade_id=2134846831, + price="64555.55", + size="0.002", + timestamp=1784218012030, + symbol="BTC/USD_LEVERAGE", + buyer=True, + order_id="order-id", + ) + + assert event.price == "64555.55" + assert isinstance(event.price, str) + assert event.size == "0.002" + assert isinstance(event.size, str) + + +def test_websocket_trade_event_is_immutable_and_uses_slots() -> None: + event = DzengiWebSocketTradeEvent( + trade_id=2134846831, + price=64555.55, + size=0.002, + timestamp=1784218012030, + symbol="BTC/USD_LEVERAGE", + buyer=False, + order_id="order-id", + ) + + assert not hasattr(event, "__dict__") + + with pytest.raises(FrozenInstanceError): + event.price = 1.0 # type: ignore[misc] diff --git a/docs/migrations/build_060_9.md b/docs/migrations/build_060_9.md new file mode 100644 index 0000000..1b46f2a --- /dev/null +++ b/docs/migrations/build_060_9.md @@ -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.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 считается полностью завершённым. \ No newline at end of file