# Build 060.11 — WebSocket Trade Parser **Engineering Migration Report** --- # Контроль документа | Свойство | Значение | |----------|----------| | Build | 060.11 | | Название | WebSocket Trade Parser | | Статус | Completed | | Проект | Dzentra | | Подсистема | Market Data Acquisition | | Компонент | Trades Feed | | Версия | 1.0 | --- # Цель Build После завершения Build 060.10 система получила завершённый уровень **Schema Validation** для WebSocket-событий канала `internal.trade`. Структурная корректность входящего сообщения теперь гарантируется объектом ```text ValidatedWebSocketTradeDocument ``` Следующим обязательным этапом развития WebSocket-конвейера является реализация слоя **Parser**, отвечающего за преобразование структурно корректного документа в транспортную модель адаптера Dzengi. Build 060.11 вводит механизм **WebSocket Trade Parser**. Основная задача Build — изолировать знания о транспортном формате WebSocket-сообщения внутри Parser и предоставить последующим уровням Pipeline готовую transport-модель. Данный Build ограничивается исключительно транспортным преобразованием документа и не затрагивает: - Schema Validation; - Value Validation; - Mapper; - Adapter; - Runtime; - Routing; - Trades Feed. --- # Предпосылки К моменту начала Build архитектура Dzentra уже содержала завершённые Parser для остальных типов WebSocket-событий. ## WebSocket Quote ```text Raw WebSocket Quote │ ▼ Schema Validation │ ▼ ValidatedWebSocketQuoteDocument │ ▼ Quote Parser │ ▼ DzengiWebSocketQuoteResponse ``` ## WebSocket OHLC ```text Raw WebSocket OHLC │ ▼ Schema Validation │ ▼ ValidatedWebSocketOhlcDocument │ ▼ OHLC Parser │ ▼ DzengiWebSocketOhlcEvent ``` После завершения Build 060.10 появился документ ```text ValidatedWebSocketTradeDocument ``` однако между ним и транспортной моделью ```text DzengiWebSocketTradeEvent ``` отсутствовал собственный слой Parsing. В результате WebSocket-конвейер обработки сделок оставался незавершённым и отличался от уже реализованных конвейеров Quote и OHLC. --- # Архитектурное основание В архитектуре Dzentra каждый уровень Pipeline имеет строго определённую область ответственности. Parser располагается между Schema Validation и Value Validation и отвечает исключительно за транспортное преобразование документа. На данном уровне выполняется: - извлечение обязательных полей из `payload`; - минимальная проверка типов, необходимая для построения транспортной модели; - переименование транспортных полей; - создание immutable transport object. Parser принципиально **не выполняет**: - проверку диапазонов значений; - проверку корректности timestamp; - проверку корректности цены; - проверку корректности объёма; - бизнес-валидацию; - Mapping; - создание канонической модели `Trade`. Такое разделение позволяет каждому уровню Pipeline выполнять одну строго определённую задачу и исключает смешивание ответственности между компонентами. --- # Результаты архитектурного аудита Перед началом реализации был выполнен аудит существующей подсистемы Parsing. Подтверждено наличие полностью реализованных компонентов: - `parse_dzengi_websocket_quote()`; - `parse_dzengi_websocket_ohlc()`; - `DzengiWebSocketQuoteResponse`; - `DzengiWebSocketOhlcEvent`. Также подтверждено существование транспортной модели: ```text DzengiWebSocketTradeEvent ``` и завершённого уровня Schema Validation: ```text ValidatedWebSocketTradeDocument ``` Одновременно подтверждено отсутствие собственного Parser для WebSocket Trade. Таким образом Build 060.11 полностью соответствует утверждённой дорожной карте серии 060 и закрывает третий этап WebSocket-ветки обработки сделок. --- # Архитектурное решение По итогам аудита было принято решение не проектировать отдельную архитектуру для Trade. Вместо этого реализован третий экземпляр уже существующего архитектурного шаблона. В систему добавлена функция ```text parse_dzengi_websocket_trade(...) ``` которая преобразует ```text ValidatedWebSocketTradeDocument ``` в ```text DzengiWebSocketTradeEvent ``` Архитектура всех WebSocket-конвейеров стала полностью симметричной. ```text Quote ValidatedWebSocketQuoteDocument │ ▼ Quote Parser │ ▼ DzengiWebSocketQuoteResponse OHLC ValidatedWebSocketOhlcDocument │ ▼ OHLC Parser │ ▼ DzengiWebSocketOhlcEvent Trade ValidatedWebSocketTradeDocument │ ▼ Trade Parser │ ▼ DzengiWebSocketTradeEvent ``` Build 060.11 не изменяет существующее поведение Quote и OHLC, а лишь расширяет существующую архитектуру новым типом рыночных данных. --- # Реализованный Parser В файл ```text src/market_data/acquisition/adapters/dzengi/parser.py ``` добавлена новая функция транспортного преобразования: ```text parse_dzengi_websocket_trade(...) ``` Parser получает результат успешной структурной проверки ```text ValidatedWebSocketTradeDocument ``` и создаёт транспортную модель ```text DzengiWebSocketTradeEvent ``` Parser является единственным компонентом системы, который знает транспортный формат WebSocket-сообщения Dzengi. Все последующие уровни Pipeline работают исключительно с транспортной моделью и полностью изолированы от структуры исходного JSON-документа. --- # Почему используется отдельный Parser `DzengiWebSocketTradeEvent` не создаётся непосредственно во время Schema Validation. Подобное разделение соответствует архитектурному принципу Dzentra, согласно которому каждый уровень Pipeline отвечает только за одну задачу. Schema Validation гарантирует корректность структуры документа. Parser преобразует структуру документа в транспортную модель. Value Validation анализирует корректность самих значений. Mapper преобразует транспортную модель в каноническую модель предметной области. Такое разделение ответственности уже используется для Quote и OHLC и полностью повторено для Trade. --- # Входной документ Parser Parser принимает единственный аргумент ```text ValidatedWebSocketTradeDocument ``` Документ гарантирует: - корректную структуру транспортной оболочки; - наличие объекта `payload`; - наличие обязательных транспортных полей; - неизменяемость содержимого `payload`. Благодаря этому Parser не выполняет повторную структурную проверку сообщения и не анализирует наличие обязательных полей. Эти гарантии уже обеспечены предыдущим уровнем Pipeline. --- # Формируемая транспортная модель Результатом успешной работы Parser является объект ```python @dataclass(frozen=True, slots=True) class DzengiWebSocketTradeEvent: trade_id: int price: DzengiRawNumeric size: DzengiRawNumeric timestamp: int symbol: str buyer: bool order_id: str ``` Экземпляр представляет собой неизменяемую транспортную модель одного события биржи. Объект полностью соответствует транспортному контракту WebSocket Trade и используется последующими этапами обработки без дополнительного обращения к исходному JSON-документу. --- # Выполняемое преобразование полей Parser извлекает значения исключительно из объекта ```text payload ``` При создании транспортной модели выполняется переименование отдельных транспортных полей. | Поле WebSocket | Поле модели | |---------------|-------------| | `id` | `trade_id` | | `price` | `price` | | `size` | `size` | | `symbol` | `symbol` | | `ts` | `timestamp` | | `buyer` | `buyer` | | `orderId` | `order_id` | Все остальные значения сохраняются без изменения. Подобное переименование позволяет транспортной модели использовать единый стиль именования, принятый во всём проекте Dzentra. --- # Сохранение исходных значений Build 060.11 принципиально не преобразует содержимое транспортных полей. В частности Parser: - не преобразует цену в `Decimal`; - не преобразует объём в `Decimal`; - не преобразует timestamp в `datetime`; - не изменяет строковое представление символа; - не интерпретирует направление сделки. Все значения сохраняются в том виде, в котором они были получены от биржи. Это позволяет полностью отделить транспортный уровень от уровня предметной валидации. --- # Почему Parser не переносит транспортную оболочку В ходе архитектурного аудита отдельно рассматривался вопрос о необходимости переноса полей транспортной оболочки ```text status destination correlationId ``` в объект ```text DzengiWebSocketTradeEvent ``` По итогам анализа принято решение отказаться от подобного переноса. После успешного завершения Schema Validation транспортная оболочка полностью выполняет свою задачу и больше не участвует в обработке рыночного события. Последующие уровни Pipeline работают исключительно с содержимым объекта `payload`. Таким образом транспортная модель содержит только данные, непосредственно описывающие совершённую сделку. Подобный подход уже используется в существующих Parser для Quote и OHLC и полностью сохраняет архитектурную симметрию подсистемы Market Data Acquisition. --- # Использование TradeParseError Для всех ошибок, возникающих на этапе Parsing, используется существующее исключение ```text TradeParseError ``` Build не вводит новых типов исключений. Это сохраняет единую иерархию обработки ошибок Trade и полностью соответствует архитектуре Dzentra. Parser использует `TradeParseError` исключительно для ошибок транспортного преобразования и проверки типов, необходимых для построения транспортной модели. Ошибки предметной корректности значений остаются областью ответственности Build 060.12. --- # Целевой конвейер обработки WebSocket Trade После завершения Build 060.11 конвейер обработки принимает следующий вид. ```text Raw WebSocket Object │ ▼ WebSocket Trade Schema Validation │ ▼ ValidatedWebSocketTradeDocument │ ▼ WebSocket Trade Parser │ ▼ DzengiWebSocketTradeEvent │ ▼ Build 060.12 — WebSocket Trade Value Validation │ ▼ Validated Trade Transport Event │ ▼ Build 060.13 — WebSocket Trade Mapper │ ▼ Trade ``` Таким образом Build 060.11 завершает третий архитектурный уровень WebSocket-конвейера обработки сделок. --- # Соотношение с предыдущим Build Build 060.10 и Build 060.11 реализуют два различных архитектурных уровня. ```text Build 060.10 ``` вводит документ ```text ValidatedWebSocketTradeDocument ``` который гарантирует структурную корректность входящего WebSocket-сообщения. ```text Build 060.11 ``` вводит Parser ```text parse_dzengi_websocket_trade(...) ``` который преобразует проверенный документ в транспортную модель ```text DzengiWebSocketTradeEvent ``` Таким образом последовательность обработки становится следующей. ```text Raw JSON │ ▼ ValidatedWebSocketTradeDocument │ ▼ DzengiWebSocketTradeEvent │ ▼ Trade ``` Каждый компонент относится к собственному архитектурному уровню и не дублирует ответственность другого. --- # Изменённые файлы В рамках Build были изменены только два файла. ## Parser ```text src/market_data/acquisition/adapters/dzengi/parser.py ``` Добавлены: ```text parse_dzengi_websocket_trade(...) _websocket_trade_required_string(...) _websocket_trade_required_int(...) _websocket_trade_required_bool(...) _websocket_trade_required_raw_numeric(...) ``` При этом существующие Parser для Quote и OHLC, импорты и поведение файла не изменялись. --- ## Unit-тесты ```text tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_parser.py ``` Добавлен полный набор unit-тестов нового Parser. --- # Добавленные тесты В рамках Build реализовано двадцать девять unit-тестов, полностью покрывающих функциональность нового Parser. ## Проверка успешного Parsing Тест ```text test_parse_websocket_trade_returns_transport_event ``` проверяет: - успешное создание `DzengiWebSocketTradeEvent`; - корректное заполнение всех полей; - создание неизменяемой транспортной модели. --- ## Проверка переименования полей Тест ```text test_parse_websocket_trade_renames_transport_fields ``` подтверждает корректное преобразование: - `id → trade_id`; - `ts → timestamp`; - `orderId → order_id`. --- ## Проверка сохранения raw-значений Тест ```text test_parse_websocket_trade_preserves_raw_numeric_values ``` подтверждает, что Parser не изменяет: - цену; - объём. Значения сохраняются в исходном виде без каких-либо преобразований. --- ## Проверка сохранения buyer Тест ```text test_parse_websocket_trade_preserves_buyer_flag ``` подтверждает корректную передачу направления сделки в транспортную модель. --- ## Игнорирование транспортной оболочки Тест ```text test_parse_websocket_trade_ignores_transport_envelope ``` подтверждает, что поля - `status`; - `destination`; - `correlationId`; не входят в состав транспортной модели и не используются Parser после успешного прохождения Schema Validation. --- ## Дополнительные поля payload Тест ```text test_parse_websocket_trade_ignores_additional_payload_fields ``` подтверждает, что дополнительные поля WebSocket-сообщения не влияют на результат Parsing. Parser использует только обязательные поля транспортного контракта. --- ## Проверка обязательных типов Реализована серия параметризованных тестов, проверяющих корректность типов каждого обязательного поля транспортной модели. При несоответствии ожидаемому типу Parser генерирует ```text TradeParseError ``` с указанием пути к некорректному элементу. --- ## Проверка отсутствующих значений Реализована серия тестов, подтверждающих генерацию ```text TradeParseError ``` при невозможности построить транспортную модель из-за отсутствия требуемого значения. --- ## Проверка отсутствия Value Validation Отдельные тесты подтверждают архитектурный принцип Build. Parser выполняет исключительно транспортное преобразование и не анализирует корректность самих значений. Проверка диапазонов, семантики и бизнес-ограничений полностью переносится на следующий этап дорожной карты. --- # Результаты тестирования Выполнен целевой запуск нового набора unit-тестов. ```bash python -m pytest \ tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_parser.py \ -q ``` Результат: ```text 29 passed in 0.03s ``` Все проверки новой функциональности успешно завершены. Следует отметить, что в процессе разработки была обнаружена неточность в первоначальной версии unit-тестов. При проверке сообщений исключений использовался параметр ```python pytest.raises(..., match=expected_path) ``` где в качестве шаблона передавался JSONPath, например ```text $.payload.id ``` Поскольку параметр `match` интерпретирует строку как регулярное выражение, символы `$` и `.` требовали экранирования. После замены ```python match=expected_path ``` на ```python match=re.escape(expected_path) ``` тесты стали корректно проверять текст сообщений исключений. Данное изменение затронуло исключительно тестовый код и не потребовало каких-либо изменений реализации Parser. --- # Регрессионное тестирование После завершения реализации выполнен полный запуск набора unit-тестов проекта. ```bash python -m pytest -q ``` Результат: ```text 1161 passed in 2.63s ``` Регрессий существующей функциональности не обнаружено. Все ранее реализованные Build продолжают работать без изменений. --- # Проверка компиляции Выполнена проверка компиляции изменённых файлов. ```bash python -m compileall \ src/market_data/acquisition/adapters/dzengi/parser.py \ tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_parser.py ``` Компиляция завершилась успешно. Синтаксические ошибки отсутствуют. --- # Проверка Git diff Выполнена финальная проверка изменений. ```bash git diff --check ``` Ошибок не обнаружено. Это подтверждает отсутствие: - trailing whitespace; - конфликтов окончания строк; - ошибок форматирования diff. --- # Scope Build 060.11 В рамках данного Build реализовано только ```text WebSocket Trade Parser ``` Build **не включает**: - WebSocket Trade Schema Validation; - WebSocket Trade Value Validation; - WebSocket Trade Mapper; - WebSocket Trade Adapter; - Runtime Integration; - Unified Routing; - Trades Feed. Это полностью соответствует принципу атомарной реализации Build. --- # Архитектурный результат После завершения Build система содержит завершённый уровень Parser для всех поддерживаемых WebSocket-событий. ```text Quote ValidatedWebSocketQuoteDocument │ ▼ Quote Parser │ ▼ DzengiWebSocketQuoteResponse OHLC ValidatedWebSocketOhlcDocument │ ▼ OHLC Parser │ ▼ DzengiWebSocketOhlcEvent Trade ValidatedWebSocketTradeDocument │ ▼ Trade Parser │ ▼ DzengiWebSocketTradeEvent ``` Архитектура Parsing стала полностью симметричной. --- # Состояние WebSocket Trade Pipeline После завершения Build 060.11 конвейер имеет следующий вид. ```text Raw WebSocket Trade Document │ ▼ ValidatedWebSocketTradeDocument │ ▼ 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 | ✔ Completed | | WebSocket Trade Parser | 060.11 | ✔ Completed | | 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. ## Локальность изменений Изменены только: - Parser; - unit-тесты нового Parser. --- ## Повторное использование архитектуры Новая реализация полностью повторяет существующий шаблон Quote и OHLC. Новая архитектура не проектировалась. --- ## Разделение ответственности Parser отвечает исключительно за транспортное преобразование документа. Schema Validation, Value Validation, Mapper и Runtime остаются полностью независимыми уровнями Pipeline. --- ## Отсутствие бизнес-логики Build не выполняет: - Value Validation; - преобразование числовых значений; - проверку диапазонов; - Mapping; - Runtime Integration. Это полностью соответствует архитектуре Dzentra. --- ## Обратная совместимость Существующая обработка Quote, OHLC и REST Trade не изменилась. Новая функциональность добавлена изолированно и не влияет на ранее реализованные Build. --- # Критерии завершения Build Build 060.11 считается завершённым, поскольку выполнены все поставленные задачи. - ✔ реализована функция `parse_dzengi_websocket_trade()`; - ✔ реализовано преобразование `ValidatedWebSocketTradeDocument` в `DzengiWebSocketTradeEvent`; - ✔ реализовано переименование транспортных полей: - `id → trade_id`; - `ts → timestamp`; - `orderId → order_id`; - ✔ реализована минимальная проверка типов, необходимая для построения транспортной модели; - ✔ используются существующие исключения `TradeParseError`; - ✔ транспортная модель остаётся неизменяемой (`frozen=True`); - ✔ реализовано двадцать девять unit-тестов; - ✔ все целевые тесты успешно проходят; - ✔ полное регрессионное тестирование успешно завершено; - ✔ компиляция выполнена без ошибок; - ✔ `git diff --check` не выявил замечаний; - ✔ изменения не выходят за пределы согласованного scope. --- # Следующий этап Следующим этапом дорожной карты является ```text Build 060.12 — WebSocket Trade Value Validation ``` Цель Build: - проверка корректности значений транспортной модели; - проверка цены сделки; - проверка объёма сделки; - проверка временной метки; - проверка символа; - проверка идентификатора сделки; - проверка идентификатора ордера; - создание валидированной транспортной модели. После завершения Build 060.12 конвейер примет следующий вид. ```text Raw WebSocket Object │ ▼ Schema Validation │ ▼ ValidatedWebSocketTradeDocument │ ▼ WebSocket Trade Parser │ ▼ DzengiWebSocketTradeEvent │ ▼ WebSocket Trade Value Validation │ ▼ ValidatedTradeTransportEvent ``` Build 060.12 по-прежнему не будет выполнять: - Mapping в каноническую модель `Trade`; - Runtime Integration; - Routing; - обработку Trade Feed. Все перечисленные задачи будут реализованы на последующих этапах дорожной карты серии 060. --- # Итог Build 060.11 завершил формирование уровня **Parser** для WebSocket Trade и сделал архитектуру транспортного преобразования всех поддерживаемых WebSocket-событий Dzentra полностью симметричной. Новая реализация основана на существующем шаблоне Quote и OHLC, использует единый подход к транспортному преобразованию сообщений, повторно применяет существующую иерархию исключений и не изменяет ранее реализованное поведение системы. Parser изолирует знания о транспортном формате WebSocket-сообщений Dzengi, выполняет минимально необходимую проверку типов для построения транспортной модели и передаёт дальнейшую обработку следующему архитектурному уровню — **Value Validation**. Build ограничен согласованным scope, успешно прошёл целевое и полное регрессионное тестирование и создаёт необходимый фундамент для следующего этапа — **Build 060.12 — WebSocket Trade Value Validation**.