diff --git a/app/src/market_data/acquisition/adapters/dzengi/websocket.py b/app/src/market_data/acquisition/adapters/dzengi/websocket.py index 11856d6..3ab6b23 100644 --- a/app/src/market_data/acquisition/adapters/dzengi/websocket.py +++ b/app/src/market_data/acquisition/adapters/dzengi/websocket.py @@ -12,14 +12,19 @@ from src.market_data.acquisition.adapters.dzengi.parser import ( parse_dzengi_websocket_ohlc, parse_dzengi_websocket_quote, ) +from src.market_data.acquisition.adapters.dzengi.websocket_trade_adapter import ( + adapt_websocket_trade_document, +) from src.market_data.acquisition.exceptions import ( WebSocketMessageRoutingError, ) from src.market_data.acquisition.models.candle_close import CandleCloseEvent from src.market_data.acquisition.models.quote import Quote +from src.market_data.acquisition.models.trade import Trade from src.market_data.acquisition.validation.schema import ( validate_dzengi_websocket_ohlc_schema, validate_dzengi_websocket_quote_schema, + validate_dzengi_websocket_trade_schema, ) from src.market_data.acquisition.validation.values import ( validate_dzengi_websocket_ohlc_values, @@ -64,6 +69,22 @@ class DzengiWebSocketOhlcAdapter: ) +# Преобразует одно декодированное сообщение Dzengi WebSocket +# internal.trade в каноническую модель Trade. +class DzengiWebSocketTradeAdapter: + def map_message( + self, + document: object, + ) -> Trade: + validated = validate_dzengi_websocket_trade_schema( + document, + ) + + return adapt_websocket_trade_document( + validated, + ) + + # Определяет тип декодированного сообщения Dzengi WebSocket # и передаёт его соответствующему специализированному адаптеру. class DzengiUnifiedWebSocketAdapter: @@ -72,27 +93,47 @@ class DzengiUnifiedWebSocketAdapter: *, quote_adapter: DzengiWebSocketQuoteAdapter | None = None, ohlc_adapter: DzengiWebSocketOhlcAdapter | None = None, + trade_adapter: DzengiWebSocketTradeAdapter | None = None, ) -> None: - self._quote_adapter = quote_adapter or DzengiWebSocketQuoteAdapter() - self._ohlc_adapter = ohlc_adapter or DzengiWebSocketOhlcAdapter() + self._quote_adapter = ( + quote_adapter + or DzengiWebSocketQuoteAdapter() + ) + + self._ohlc_adapter = ( + ohlc_adapter + or DzengiWebSocketOhlcAdapter() + ) + + self._trade_adapter = ( + trade_adapter + or DzengiWebSocketTradeAdapter() + ) def map_message( self, document: object, *, received_at: datetime | None = None, - ) -> Quote | CandleCloseEvent: + ) -> Quote | CandleCloseEvent | Trade: if not isinstance(document, dict): raise WebSocketMessageRoutingError( "Сообщение Dzengi WebSocket должно быть объектом." ) - if document.get("destination") == "ohlc.event": + destination = document.get("destination") + + if destination == "ohlc.event": return self._ohlc_adapter.map_message( document, received_at=received_at, ) + if destination == "internal.trade": + return self._trade_adapter.map_message( + document, + ) + if "Payload" in document: return self._quote_adapter.map_message( document, diff --git a/app/tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_unified_adapter.py b/app/tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_unified_adapter.py index 091990d..f6ca3d0 100644 --- a/app/tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_unified_adapter.py +++ b/app/tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_unified_adapter.py @@ -14,6 +14,7 @@ from src.market_data.acquisition.exceptions import ( ) from src.market_data.acquisition.models.candle_close import CandleCloseEvent from src.market_data.acquisition.models.quote import Quote +from src.market_data.acquisition.models.trade import Trade def test_adapter_routes_quote_message() -> None: @@ -57,6 +58,30 @@ def test_adapter_routes_ohlc_message() -> None: assert str(result.close_price) == "103" +def test_adapter_routes_trade_message() -> None: + result = DzengiUnifiedWebSocketAdapter().map_message( + { + "status": "OK", + "destination": "internal.trade", + "payload": { + "id": 123456, + "price": "105.50", + "size": "0.25", + "ts": 1752364800123, + "symbol": "BTC/USD", + "buyer": True, + "orderId": "order-123", + }, + } + ) + + assert isinstance(result, Trade) + assert result.symbol == "BTC/USD" + assert result.trade_id == 123456 + assert str(result.price) == "105.50" + assert str(result.quantity) == "0.25" + + def test_adapter_passes_received_at_to_quote_adapter() -> None: received_at = datetime(2026, 7, 17, tzinfo=timezone.utc) diff --git a/docs/migrations/build_060_15.md b/docs/migrations/build_060_15.md new file mode 100644 index 0000000..8ed8be1 --- /dev/null +++ b/docs/migrations/build_060_15.md @@ -0,0 +1,926 @@ +# Build 060.15 — Unified WebSocket Routing + +**Engineering Migration Report** + +--- + +# Контроль документа + +| Свойство | Значение | +|----------|----------| +| Build | 060.15 | +| Название | Unified WebSocket Routing | +| Статус | Completed | +| Проект | Dzentra | +| Подсистема | Market Data Acquisition | +| Компонент | Trades Feed | +| Версия | 1.0 | + +--- + +# Цель Build + +После завершения Build 060.14 система получила полностью реализованный уровень **WebSocket Trade Adapter**. + +К этому моменту Pipeline обработки WebSocket Trade уже гарантировал: + +- корректность структуры транспортного документа; +- успешное построение транспортной модели; +- корректность всех обязательных значений; +- успешное преобразование транспортной модели в каноническую модель предметной области; +- наличие единой точки входа транспортного Pipeline. + +Однако полученный Adapter ещё не был встроен в существующую инфраструктуру получения WebSocket-сообщений. + +Архитектура Dzentra уже содержала единый компонент маршрутизации входящих сообщений — + +```text +DzengiUnifiedWebSocketAdapter +``` + +который определял тип входящего сообщения и направлял его в соответствующий специализированный Adapter. + +До начала Build поддерживались только два типа сообщений: + +```text +Quote +``` + +и + +```text +OHLC +``` + +Поддержка сообщений + +```text +Trade +``` + +отсутствовала. + +В результате Trade Pipeline существовал как полностью реализованный, но оставался недоступным для общего механизма маршрутизации WebSocket. + +Основная задача Build заключается в расширении существующего Unified WebSocket Routing без изменения ранее реализованной архитектуры. + +После завершения Build компонент + +```text +DzengiUnifiedWebSocketAdapter +``` + +должен уметь автоматически определять сообщения + +```text +internal.trade +``` + +и передавать их в специализированный Adapter обработки Trade. + +При этом Build не затрагивает: + +- WebSocket Runtime; +- транспортный протокол; +- Schema Validation; +- Parser; +- Value Validation; +- Mapper; +- внутреннюю архитектуру Trade Adapter; +- Trades Feed. + +--- + +# Предпосылки + +К началу Build архитектура WebSocket уже содержала единый механизм маршрутизации сообщений. + +Он обеспечивал разделение различных типов транспортных событий между специализированными Adapter. + +Конвейер обработки выглядел следующим образом. + +```text +Raw WebSocket Message + │ + ▼ +DzengiUnifiedWebSocketAdapter + │ + ├────────► Quote Adapter + │ + └────────► OHLC Adapter +``` + +Каждый специализированный Adapter полностью инкапсулировал собственный транспортный Pipeline. + +После завершения Build 060.14 аналогичный Pipeline уже существовал и для Trade. + +```text +ValidatedWebSocketTradeDocument + │ + ▼ +WebSocket Trade Adapter + │ + ├────────► Parser + │ + ├────────► Value Validation + │ + ├────────► Mapper + │ + ▼ +Trade +``` + +Однако данный Adapter не был подключён к общему механизму маршрутизации. + +В результате система не могла автоматически обрабатывать входящие сообщения о сделках, несмотря на полностью реализованный транспортный Pipeline. + +Таким образом архитектура Unified WebSocket Routing оставалась незавершённой. + +--- + +# Архитектурное основание + +Одним из фундаментальных принципов архитектуры Dzentra является централизованная маршрутизация транспортных сообщений. + +Определение типа входящего сообщения должно происходить исключительно в одном месте системы. + +Именно эту задачу выполняет + +```text +DzengiUnifiedWebSocketAdapter +``` + +После определения типа события дальнейшая обработка полностью передаётся специализированному Adapter соответствующего типа данных. + +Полная схема обработки WebSocket-сообщений принимает следующий вид. + +```text +Raw WebSocket Message + │ + ▼ +Unified Routing + │ + ├────────► Quote Adapter + │ + ├────────► OHLC Adapter + │ + └────────► Trade Adapter +``` + +Каждый специализированный Adapter полностью инкапсулирует собственный транспортный Pipeline. + +Unified Router принципиально **не выполняет**: + +- Schema Validation; +- Parser; +- Value Validation; +- Mapper; +- бизнес-логику; +- принятие торговых решений; +- обработку Runtime. + +Его единственная ответственность — определить тип входящего сообщения и передать его соответствующему Adapter. + +Подобное разделение ответственности обеспечивает: + +- единое место маршрутизации всех WebSocket-событий; +- независимое развитие отдельных Pipeline; +- повторное использование существующих Adapter; +- отсутствие дублирования логики определения типа сообщений. + +--- + +# Результаты архитектурного аудита + +Перед реализацией Build был выполнен аудит существующей архитектуры WebSocket Routing. + +В ходе анализа подтверждено наличие полностью реализованного компонента + +```text +DzengiUnifiedWebSocketAdapter +``` + +который уже обеспечивал маршрутизацию сообщений Quote и OHLC. + +Также был выполнен аудит транспортного уровня Runtime. + +Анализ подтвердил, что + +```text +runtime/websocket_protocol.py +``` + +не содержит логики определения типов сообщений и не должен выполнять: + +- Schema Validation; +- Parser; +- Value Validation; +- Mapper; +- транспортную маршрутизацию. + +Данный принцип уже закреплён архитектурой подсистемы Market Data Acquisition. + +Кроме того, аудит подтвердил полную готовность WebSocket Trade Adapter, реализованного в Build 060.14. + +Таким образом единственным отсутствующим элементом архитектуры являлось подключение нового Adapter к существующему механизму Unified Routing. + +--- + +# Архитектурное решение + +По результатам проведённого аудита было принято решение сохранить существующую архитектуру маршрутизации без изменения её принципов. + +В компонент + +```text +DzengiUnifiedWebSocketAdapter +``` + +добавлена поддержка нового специализированного Adapter. + +После определения значения + +```text +destination = "internal.trade" +``` + +маршрутизатор передаёт документ в + +```text +DzengiWebSocketTradeAdapter +``` + +который выполняет полный транспортный Pipeline обработки и возвращает готовую каноническую модель + +```text +Trade +``` + +После завершения обработки вызывающая сторона получает результат в виде одной из трёх канонических моделей предметной области. + +```text +Quote + +CandleCloseEvent + +Trade +``` + +Конвейер обработки WebSocket принимает следующий вид. + +```text +Raw WebSocket Message + │ + ▼ +DzengiUnifiedWebSocketAdapter + │ + ├────────► Quote Adapter + │ │ + │ ▼ + │ Quote + │ + ├────────► OHLC Adapter + │ │ + │ ▼ + │ CandleCloseEvent + │ + └────────► Trade Adapter + │ + ▼ + Trade +``` + +Build 060.15 не изменяет архитектуру Runtime, не затрагивает ранее реализованные Pipeline и не изменяет внутреннюю структуру специализированных Adapter. + +Он исключительно расширяет существующий механизм Unified Routing новым типом сообщений. + +# Реализованный уровень Unified Routing + +В файл + +```text +src/market_data/acquisition/adapters/dzengi/websocket.py +``` + +добавлен специализированный Adapter + +```text +DzengiWebSocketTradeAdapter +``` + +Новый Adapter становится частью существующей архитектуры маршрутизации WebSocket-сообщений и отвечает исключительно за подключение уже реализованного транспортного Pipeline Trade к Unified Router. + +Во время обработки сообщения Adapter последовательно выполняет: + +1. Schema Validation; +2. WebSocket Trade Adapter. + +При этом полный транспортный Pipeline продолжает оставаться инкапсулированным внутри ранее реализованного Adapter Build 060.14. + +После успешного завершения обработки вызывающая сторона получает готовую каноническую модель + +```text +Trade +``` + +Промежуточные транспортные модели по-прежнему не покидают слой адаптеров. + +--- + +# Почему маршрутизация выполняется в Unified Adapter + +Во время проектирования отдельно рассматривался вопрос о переносе логики определения типа сообщения в Runtime. + +По результатам анализа данный вариант был отклонён. + +Основные причины: + +- Runtime не должен знать типы рыночных сообщений; +- Runtime не должен зависеть от конкретной биржи; +- Runtime не должен содержать транспортную маршрутизацию; +- вся логика определения типа сообщения уже сосредоточена в Unified Adapter. + +Таким образом архитектурная ответственность остаётся неизменной. + +Runtime отвечает исключительно за получение транспортных сообщений. + +Unified Adapter отвечает исключительно за определение их типа. + +Специализированные Adapter отвечают исключительно за преобразование данных. + +Подобное разделение полностью соответствует базовым архитектурным принципам Dzentra. + +--- + +# Последовательность обработки + +После завершения Build полный конвейер обработки WebSocket-сообщений принимает следующий вид. + +```text +Raw WebSocket Message + │ + ▼ +DzengiUnifiedWebSocketAdapter + │ + ├────────► Quote Adapter + │ │ + │ ▼ + │ Quote + │ + ├────────► OHLC Adapter + │ │ + │ ▼ + │ CandleCloseEvent + │ + └────────► Trade Adapter + │ + ▼ + Schema Validation + │ + ▼ + WebSocket Trade Adapter + │ + ├────────► Parser + ├────────► Value Validation + ├────────► Mapper + │ + ▼ + Trade +``` + +Каждый уровень Pipeline сохраняет собственную область ответственности. + +Ни один из существующих компонентов не изменяет своё назначение. + +--- + +# Использование существующих компонентов + +Build 060.15 не вводит новых механизмов обработки транспортных данных. + +Unified Router повторно использует уже существующую инфраструктуру проекта. + +Для обработки Quote используются: + +```text +DzengiWebSocketQuoteAdapter +``` + +Для обработки OHLC используются: + +```text +DzengiWebSocketOhlcAdapter +``` + +Для обработки Trade используются: + +```text +DzengiWebSocketTradeAdapter +``` + +Новый специализированный Adapter, в свою очередь, повторно использует ранее реализованный + +```text +adapt_websocket_trade_document(...) +``` + +из Build 060.14. + +Таким образом Build не дублирует существующую функциональность и полностью основан на уже реализованных компонентах проекта. + +--- + +# Проброс исключений + +Unified Router не выполняет собственной обработки ошибок. + +Все исключения специализированных Adapter пробрасываются вызывающей стороне без изменения. + +При обработке Quote сохраняется существующий механизм обработки ошибок. + +При обработке OHLC сохраняется существующий механизм обработки ошибок. + +При обработке Trade сохраняются исключения: + +```text +TradeSchemaError +``` + +```text +TradeParseError +``` + +```text +TradeValueError +``` + +```text +TradeMappingError +``` + +Подобный подход сохраняет единообразную архитектуру обработки ошибок во всей подсистеме Market Data Acquisition. + +--- + +# Изменённые файлы + +В рамках Build были изменены только два файла. + +## Unified Adapter + +```text +src/market_data/acquisition/adapters/dzengi/websocket.py +``` + +Добавлен специализированный + +```text +DzengiWebSocketTradeAdapter +``` + +и расширена маршрутизация сообщений. + +Существующая логика обработки Quote и OHLC не изменялась. + +--- + +## Unit-тесты + +```text +tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_unified_adapter.py +``` + +Добавлен unit-тест маршрутизации сообщений + +```text +internal.trade +``` + +Все существующие тесты сохранены без изменений. + +--- + +# Добавленные тесты + +В рамках Build реализован дополнительный unit-тест, подтверждающий корректную маршрутизацию сообщений + +```text +internal.trade +``` + +через Unified Router. + +--- + +## Проверка успешной маршрутизации Trade + +Подтверждается, что сообщение + +```text +destination = "internal.trade" +``` + +автоматически направляется в специализированный + +```text +DzengiWebSocketTradeAdapter +``` + +и после завершения полного транспортного Pipeline возвращается каноническая модель + +```text +Trade +``` + +--- + +# Результаты тестирования + +После завершения реализации выполнен запуск целевого набора unit-тестов нового Adapter. + +```bash +PYTHONPATH="$PWD" python -m pytest \ +tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_adapter.py -v +``` + +Результат: + +```text +4 passed +``` + +--- + +После этого выполнено тестирование Unified Router. + +```bash +PYTHONPATH="$PWD" python -m pytest \ +tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_unified_adapter.py -v +``` + +Результат: + +```text +10 passed +``` + +Подтверждена корректная маршрутизация: + +- Quote; +- OHLC; +- Trade. + +Также подтверждено отсутствие изменений существующей функциональности. + +--- + +# Регрессионное тестирование Adapter + +После завершения реализации выполнен полный запуск тестов адаптеров Dzengi. + +```bash +PYTHONPATH="$PWD" python -m pytest \ +tests/unit/market_data/acquisition/adapters/dzengi +``` + +Результат: + +```text +302 passed +``` + +Регрессий существующих Adapter, Parser, Mapper и Validation не обнаружено. + +--- + +# Регрессионное тестирование Market Data + +После завершения реализации выполнен запуск полного набора unit-тестов подсистемы Market Data. + +```bash +PYTHONPATH="$PWD" python -m pytest \ +tests/unit/market_data +``` + +Результат: + +```text +914 passed +``` + +Подтверждено отсутствие регрессий во всех ранее реализованных компонентах подсистемы. + +--- + +# Полное регрессионное тестирование + +После завершения реализации выполнен полный запуск unit-тестов проекта. + +```bash +PYTHONPATH="$PWD" python -m pytest +``` + +Результат: + +```text +1235 passed +``` + +Регрессий существующей функциональности не обнаружено. + +Добавление поддержки Trade в Unified Routing не повлияло на существующие механизмы обработки Quote, OHLC, REST Trade и остальные подсистемы проекта. + +# Проверка компиляции + +После завершения реализации выполнена полная проверка компиляции проекта. + +```bash +python -m compileall src tests +``` + +Компиляция завершилась успешно. + +Ошибок синтаксиса не обнаружено. + +Все изменённые файлы успешно компилируются и не нарушают целостность проекта. + +--- + +# Проверка Git diff + +После завершения реализации выполнена финальная проверка изменений. + +```bash +git diff --check +``` + +Результат: + +```text +без замечаний +``` + +Проверка подтвердила отсутствие: + +- trailing whitespace; +- ошибок окончания строк; +- конфликтов diff; +- нарушений форматирования. + +--- + +# Scope Build 060.15 + +В рамках данного Build реализован исключительно уровень + +```text +Unified WebSocket Routing +``` + +Build **не включает**: + +- WebSocket Runtime; +- WebSocket Protocol; +- Trades Feed; +- Dispatcher; +- Event Bus; +- Runtime Integration; +- обработку торговых решений. + +Подобное ограничение полностью соответствует принятому принципу атомарной реализации Build. + +Каждый этап дорожной карты реализует только один архитектурный уровень системы. + +--- + +# Архитектурный результат + +После завершения Build система содержит полностью реализованный механизм Unified WebSocket Routing. + +Конвейер обработки принимает следующий вид. + +```text +Raw WebSocket Message + │ + ▼ +DzengiUnifiedWebSocketAdapter + │ + ├────────► Quote Adapter + │ │ + │ ▼ + │ Quote + │ + ├────────► OHLC Adapter + │ │ + │ ▼ + │ CandleCloseEvent + │ + └────────► Trade Adapter + │ + ▼ + Trade +``` + +Unified Router полностью изолирует вызывающий код от деталей транспортного Pipeline. + +Все специализированные Adapter продолжают самостоятельно выполнять полный цикл преобразования транспортных данных в канонические модели предметной области. + +--- + +# Состояние WebSocket Trade Pipeline + +После завершения Build 060.15 транспортный конвейер принимает следующий вид. + +```text +Raw WebSocket Trade + │ + ▼ +Unified WebSocket Routing + │ + ▼ +Schema Validation + │ + ▼ +ValidatedWebSocketTradeDocument + │ + ▼ +Adapter + │ + ├────────► Parser + │ + ├────────► Value Validation + │ + ├────────► Mapper + │ + ▼ +Trade +``` + +Статус реализации компонентов: + +| Компонент | Build | Статус | +|-----------|-------|--------| +| Canonical Trade Model | 060.1 | ✔ Completed | +| WebSocket Trade Transport Model | 060.9 | ✔ Completed | +| WebSocket Trade Schema Validation | 060.10 | ✔ Completed | +| WebSocket Trade Parser | 060.11 | ✔ Completed | +| WebSocket Trade Value Validation | 060.12 | ✔ Completed | +| WebSocket Trade Mapper | 060.13 | ✔ Completed | +| WebSocket Trade Adapter | 060.14 | ✔ Completed | +| Unified WebSocket Routing | 060.15 | ✔ Completed | +| Trades Feed | 060.16 | Pending | + +--- + +# Соблюдение архитектурных принципов + +В рамках Build полностью сохранены архитектурные инварианты Dzentra. + +## Локальность изменений + +Изменены только: + +- `src/market_data/acquisition/adapters/dzengi/websocket.py`; +- `tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_unified_adapter.py`. + +Все остальные компоненты проекта остались без изменений. + +--- + +## Повторное использование архитектуры + +Build полностью использует существующую архитектуру Unified Router. + +Новый механизм маршрутизации не проектировался. + +Расширен уже существующий механизм обработки сообщений. + +--- + +## Повторное использование инфраструктуры + +Build полностью использует ранее реализованные компоненты проекта: + +- Quote Adapter; +- OHLC Adapter; +- WebSocket Trade Adapter. + +Новые Parser, Mapper или Validation не создавались. + +Вся существующая инфраструктура используется повторно. + +--- + +## Разделение ответственности + +Unified Router отвечает исключительно за определение типа входящего сообщения. + +Специализированные Adapter отвечают исключительно за преобразование транспортных данных. + +Runtime продолжает отвечать исключительно за получение транспортных сообщений. + +Подобное разделение полностью соответствует архитектурным принципам Dzentra. + +--- + +## Обратная совместимость + +Существующая обработка: + +- Quote; +- OHLC; +- REST Trade; + +не изменилась. + +Добавленная поддержка Trade полностью совместима с существующей архитектурой и не оказывает влияния на ранее реализованные компоненты проекта. + +--- + +# Архитектурные решения Build (ADR) + +## ADR-060.15-001 + +**Определение типа WebSocket-сообщения выполняется исключительно Unified Router.** + +Runtime не должен содержать транспортную маршрутизацию. + +--- + +## ADR-060.15-002 + +**Каждый тип рыночных данных обрабатывается собственным специализированным Adapter.** + +Unified Router не выполняет преобразование транспортных данных. + +Он только определяет тип сообщения и передаёт его соответствующему Adapter. + +--- + +## ADR-060.15-003 + +**Trade Pipeline повторно использует ранее реализованный Adapter Build 060.14.** + +Unified Router не вызывает Parser, Value Validation и Mapper напрямую. + +Полный транспортный Pipeline остаётся полностью инкапсулированным внутри специализированного Adapter. + +--- + +## ADR-060.15-004 + +**Подключение нового типа сообщений не изменяет существующую архитектуру Runtime.** + +Поддержка Trade реализуется исключительно расширением Unified Router. + +Это сохраняет независимость Runtime от особенностей конкретных транспортных сообщений. + +--- + +# Критерии завершения Build + +Build 060.15 считается завершённым, поскольку выполнены все поставленные задачи. + +- ✔ реализован `DzengiWebSocketTradeAdapter`; +- ✔ Trade подключён к Unified Router; +- ✔ добавлена маршрутизация сообщений `internal.trade`; +- ✔ Runtime не изменён; +- ✔ транспортный Pipeline Trade повторно использован без дублирования; +- ✔ сохранено разделение ответственности между Runtime, Router и Adapter; +- ✔ добавлен unit-тест маршрутизации Trade; +- ✔ успешно пройдены целевые тесты; +- ✔ успешно пройдена регрессия Adapter; +- ✔ успешно пройдена регрессия Market Data; +- ✔ успешно пройдена полная регрессия проекта (`1235 passed`); +- ✔ проект успешно компилируется; +- ✔ `git diff --check` не выявил замечаний; +- ✔ изменения полностью укладываются в согласованный scope Build. + +--- + +# Следующий этап + +Следующим этапом дорожной карты является + +```text +Build 060.16 — Trades Feed +``` + +Цель следующего Build: + +- реализовать специализированный Feed обработки сделок; +- подключить поток Trade к инфраструктуре Market Data Acquisition; +- обеспечить публикацию канонических моделей `Trade`; +- подготовить основу для дальнейшего использования сделок подсистемами анализа рынка и торговых стратегий. + +--- + +# Итог + +Build 060.15 завершил интеграцию **WebSocket Trade Pipeline** в существующий механизм Unified WebSocket Routing. + +Новая реализация полностью повторно использует архитектуру Dzentra, не изменяет Runtime, не дублирует транспортный Pipeline и сохраняет строгое разделение ответственности между компонентами системы. + +После завершения Build все три типа рыночных данных — **Quote**, **CandleCloseEvent** и **Trade** — обрабатываются единым механизмом маршрутизации и возвращаются в виде канонических моделей предметной области. + +Build ограничен согласованным scope, успешно прошёл целевое и полное регрессионное тестирование, подтвердил отсутствие регрессий и завершил интеграцию WebSocket Trade в общий механизм маршрутизации Market Data Acquisition. \ No newline at end of file