# Build 060.13 — WebSocket Trade Mapper **Engineering Migration Report** --- # Контроль документа | Свойство | Значение | |----------|----------| | Build | 060.13 | | Название | WebSocket Trade Mapper | | Статус | Completed | | Проект | Dzentra | | Подсистема | Market Data Acquisition | | Компонент | Trades Feed | | Версия | 1.0 | --- # Цель Build После завершения Build 060.12 система получила полностью реализованный уровень **WebSocket Trade Value Validation**, подтверждающий корректность всех значений транспортной модели ```text DzengiWebSocketTradeEvent ``` На этом этапе Pipeline гарантирует: - корректную структуру транспортного документа; - успешное построение transport model; - корректность всех обязательных значений; - отсутствие недопустимых числовых представлений; - корректность строковых идентификаторов. Однако даже после успешного прохождения всех перечисленных этапов транспортная модель ещё не может использоваться остальной частью системы. Несмотря на корректность структуры и значений, объект ```text DzengiWebSocketTradeEvent ``` по-прежнему остаётся транспортной моделью биржи Dzengi. Он содержит особенности конкретного источника данных: - транспортные числовые представления; - транспортное поле `size`; - транспортное поле `buyer`; - транспортное поле `order_id`; - транспортное представление времени в миллисекундах Unix Epoch. Использование подобных моделей за пределами слоя адаптера противоречит базовым архитектурным принципам Dzentra. Внутренние компоненты системы не должны зависеть от особенностей API конкретной биржи. Для решения данной задачи архитектура Dzentra предусматривает следующий обязательный уровень Pipeline — **Mapper**. Именно Mapper выполняет преобразование транспортной модели источника данных в единую каноническую модель предметной области. Build 060.13 реализует данный уровень для WebSocket Trade. Основная задача Build — преобразовать проверенную транспортную модель ```text DzengiWebSocketTradeEvent ``` в каноническую immutable-модель ```text Trade ``` с выполнением всех необходимых преобразований типов данных. При этом Build не затрагивает: - Schema Validation; - Parser; - Value Validation; - WebSocket Runtime; - Routing; - Trades Feed. --- # Предпосылки К началу Build архитектура Market Data Acquisition уже содержала полностью реализованные Mapper для остальных типов рыночных данных. Аналогичный уровень преобразования уже существовал для: - REST Quote; - WebSocket Quote; - WebSocket OHLC; - REST Aggregate Trade. Во всех случаях использовалась одинаковая архитектурная схема обработки. ```text Transport Model │ ▼ Mapper │ ▼ Canonical Model ``` После завершения Build 060.12 аналогичный транспортный конвейер появился и для WebSocket Trade. ```text ValidatedWebSocketTradeDocument │ ▼ Trade Parser │ ▼ DzengiWebSocketTradeEvent │ ▼ Value Validation │ ▼ DzengiWebSocketTradeEvent ``` Однако следующий обязательный уровень — преобразование транспортной модели в каноническую модель системы — ещё отсутствовал. Таким образом WebSocket-конвейер обработки сделок оставался архитектурно незавершённым. --- # Архитектурное основание Одним из базовых принципов архитектуры Dzentra является полная изоляция внутренних компонентов системы от формата данных конкретной биржи. Любые особенности транспортного протокола должны оставаться исключительно внутри слоя адаптера. Общий конвейер обработки рыночных данных имеет следующий вид. ```text Raw Source │ ▼ Schema Validation │ ▼ Transport Model │ ▼ Value Validation │ ▼ Mapper │ ▼ Domain Model ``` Каждый уровень Pipeline отвечает только за одну категорию задач. Для WebSocket Trade это разделение выглядит следующим образом. ```text Schema Validation ``` гарантирует корректность структуры транспортного документа. --- ```text Parser ``` создаёт транспортную модель ```text DzengiWebSocketTradeEvent ``` без изменения типов данных. --- ```text Value Validation ``` подтверждает корректность значений транспортной модели без выполнения каких-либо преобразований. --- Следующий уровень — ```text Mapper ``` выполняет преобразование транспортной модели в каноническую модель предметной области. Именно Mapper отвечает за: - преобразование транспортных числовых представлений в `Decimal`; - преобразование транспортного timestamp в `datetime`; - преобразование транспортных признаков сделки в канонические перечисления; - переименование транспортных полей; - создание immutable-модели `Trade`. При этом Mapper принципиально **не выполняет**: - повторную Validation; - анализ структуры JSON; - Runtime-логику; - бизнес-логику; - маршрутизацию событий. Такое разделение ответственности позволяет каждому уровню Pipeline оставаться независимым, повторно используемым и легко тестируемым. --- # Результаты архитектурного аудита Перед реализацией Build был выполнен аудит существующего слоя Mapper. В ходе анализа подтверждено наличие полностью сформированной архитектуры преобразования транспортных моделей. Для различных типов рыночных данных уже реализованы функции: ```text map_dzengi_quote_to_quote(...) map_dzengi_websocket_quote_to_quote(...) map_dzengi_websocket_ohlc_to_candle_close_event(...) map_dzengi_rest_agg_trades_to_trades(...) ``` Все существующие Mapper используют одинаковые архитектурные принципы. Преобразование транспортных типов выполняется исключительно внутри Mapper. Для этого повторно используются существующие helper-функции проекта. Одновременно аудит подтвердил наличие общего исключения ```text TradeMappingError ``` используемого всеми существующими Mapper при невозможности построения канонической модели. При этом отдельный Mapper для транспортной модели ```text DzengiWebSocketTradeEvent ``` в системе отсутствовал. Таким образом единственным отсутствующим элементом архитектурной цепочки являлся собственный уровень Mapping для WebSocket Trade. Build 060.13 полностью закрывает данный пробел и завершает следующий обязательный уровень Pipeline серии Build 060. --- # Архитектурное решение По результатам проведённого аудита было принято решение полностью повторить архитектурный шаблон, уже используемый Mapper остальных типов рыночных данных. В систему добавлена функция ```text map_dzengi_websocket_trade_to_trade(...) ``` которая принимает транспортную модель ```text DzengiWebSocketTradeEvent ``` и создаёт каноническую immutable-модель ```text Trade ``` Во время преобразования выполняются все необходимые преобразования транспортных типов данных. После успешного завершения Mapping транспортная модель больше не используется последующими уровнями системы. Конвейер WebSocket Trade принимает следующий вид. ```text Raw WebSocket Trade │ ▼ WebSocket Trade Schema Validation │ ▼ ValidatedWebSocketTradeDocument │ ▼ WebSocket Trade Parser │ ▼ DzengiWebSocketTradeEvent │ ▼ WebSocket Trade Value Validation │ ▼ DzengiWebSocketTradeEvent │ ▼ WebSocket Trade Mapper │ ▼ Trade ``` Build 060.13 не изменяет архитектуру ранее реализованных компонентов и завершает следующий обязательный уровень транспортного Pipeline. # Реализованный уровень Mapper В файл ```text src/market_data/acquisition/adapters/dzengi/mapper.py ``` добавлена новая функция ```text map_dzengi_websocket_trade_to_trade(...) ``` Функция получает транспортную модель ```text DzengiWebSocketTradeEvent ``` и выполняет построение канонической модели ```text Trade ``` Во время выполнения Mapping создаётся новый immutable-объект предметной области. Транспортная модель при этом остаётся неизменной. Таким образом следующий уровень Pipeline получает уже не транспортную модель биржи, а полностью независимую внутреннюю модель системы. --- # Почему Mapper создаёт новую модель Во время архитектурного проектирования отдельно рассматривался вопрос о возможности повторного использования объекта ```text DzengiWebSocketTradeEvent ``` на последующих уровнях системы. По результатам анализа было принято решение полностью отказаться от подобного подхода. Основные причины: - транспортная модель отражает структуру конкретной биржи; - транспортная модель содержит поля, отсутствующие в предметной области; - транспортная модель использует транспортные представления числовых данных; - транспортная модель использует транспортные соглашения о наименовании полей; - внутренние компоненты системы не должны зависеть от API биржи. Поэтому Mapper всегда создаёт новый экземпляр ```text Trade ``` который становится единственной моделью, используемой за пределами слоя адаптера. Подобное решение полностью соответствует базовому архитектурному принципу Dzentra — полной изоляции внутренних компонентов от транспортных контрактов внешних источников данных. --- # Преобразуемая транспортная модель Преобразование выполняется над объектом ```python @dataclass(frozen=True, slots=True) class DzengiWebSocketTradeEvent: trade_id: int price: DzengiRawNumeric size: DzengiRawNumeric timestamp: int symbol: str buyer: bool order_id: str ``` После выполнения Mapping создаётся объект ```python @dataclass(frozen=True, slots=True) class Trade: symbol: str trade_id: int price: Decimal quantity: Decimal executed_at: datetime aggressor_side: TradeAggressorSide source: str ``` Таким образом Mapper полностью устраняет зависимость системы от транспортной модели биржи. --- # Преобразование идентификатора сделки Поле ```text trade_id ``` имеет одинаковую семантику в транспортной и канонической модели. Поэтому Mapper переносит его без изменения значения. Дополнительных преобразований не выполняется. --- # Преобразование цены сделки Поле ```text price ``` в транспортной модели может быть представлено в нескольких форматах. Например: ```text "63992.50" 63992 63992.50 ``` Во время Mapping используется существующий helper проекта, преобразующий транспортное представление в ```text Decimal ``` После завершения Mapping каноническая модель всегда содержит внутренний числовой тип системы независимо от исходного представления данных. --- # Преобразование количества сделки Транспортная модель использует поле ```text size ``` которое отражает терминологию API биржи. В канонической модели используется единое наименование ```text quantity ``` Во время Mapping выполняются одновременно два действия: - преобразование транспортного значения в `Decimal`; - переименование транспортного поля в соответствии с внутренней моделью предметной области. После завершения Mapping дальнейшая работа системы полностью абстрагируется от терминологии конкретной биржи. --- # Преобразование временной метки Поле ```text timestamp ``` в транспортной модели представляет собой количество миллисекунд, прошедших с начала эпохи Unix. Подобное представление удобно для передачи данных по сети, однако не является внутренним представлением времени в Dzentra. Во время Mapping используется существующая helper-функция преобразования времени. В результате каноническая модель получает объект ```text datetime ``` в часовом поясе UTC. Таким образом все внутренние компоненты системы используют единый формат представления времени независимо от способа передачи данных биржей. --- # Преобразование стороны агрессора Во время архитектурного аудита отдельно анализировалась семантика транспортного поля ```text buyer ``` В WebSocket API Dzengi данное поле определяет сторону покупателя сделки. Однако внутренняя модель Dzentra использует перечисление ```text TradeAggressorSide ``` Поэтому Mapper выполняет явное преобразование транспортного признака в каноническое перечисление. Используется следующее соответствие. ```text buyer = True │ ▼ TradeAggressorSide.BUY ``` ```text buyer = False │ ▼ TradeAggressorSide.SELL ``` Подобное преобразование делает внутреннюю модель полностью независимой от конкретного транспортного соглашения биржи. --- # Преобразование источника данных Каноническая модель ```text Trade ``` содержит поле ```text source ``` которое используется для идентификации происхождения события. Во время Mapping данное поле получает фиксированное значение ```text dzengi_websocket_trade ``` Использование отдельного идентификатора источника позволяет последующим уровням системы различать происхождение канонических моделей без анализа транспортного Pipeline. --- # Почему order_id отсутствует в канонической модели Транспортная модель WebSocket содержит дополнительное поле ```text order_id ``` которое используется исключительно транспортным контрактом биржи. Во время архитектурного проектирования отдельно анализировался вопрос необходимости переноса данного значения в каноническую модель. По результатам анализа было принято решение отказаться от подобного преобразования. Основные причины: - идентификатор ордера отсутствует в модели предметной области; - последующие уровни системы не используют данное значение; - сохранение транспортного идентификатора нарушило бы независимость канонической модели. В результате поле ```text order_id ``` полностью завершается на уровне адаптера и не попадает в модель ```text Trade ``` --- # Повторное использование существующих helper-функций Build 060.13 не вводит новых механизмов преобразования числовых значений и времени. Вместо этого повторно используются уже существующие helper-функции проекта. Для преобразования числовых представлений применяется существующий механизм преобразования в ```text Decimal ``` Для преобразования временной метки используется существующая функция построения объекта ```text datetime ``` Подобный подход обеспечивает единообразное поведение всех Mapper проекта независимо от типа источника данных. --- # Использование TradeMappingError Все ошибки преобразования используют существующее исключение ```text TradeMappingError ``` Build не вводит новых типов исключений. Это сохраняет единый механизм обработки ошибок Mapping во всей подсистеме Market Data Acquisition. Value Validation продолжает использовать ```text TradeValueError ``` а Mapper использует исключительно ```text TradeMappingError ``` Тем самым достигается чёткое разделение ошибок проверки данных и ошибок преобразования транспортной модели. --- # Изменённые файлы В рамках Build были изменены только два файла. ## Mapper ```text src/market_data/acquisition/adapters/dzengi/mapper.py ``` Добавлена функция ```text map_dzengi_websocket_trade_to_trade(...) ``` а также вспомогательная функция преобразования стороны агрессора WebSocket Trade. Существующая логика Mapping Quote, OHLC и REST Trade не изменялась. --- ## Unit-тесты ```text tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_mapper.py ``` Добавлен полный набор unit-тестов нового уровня Mapping. --- # Добавленные тесты В рамках Build реализовано двадцать два unit-теста, полностью покрывающих новую функциональность. ## Проверка корректного Mapping Подтверждается успешное создание канонической модели ```text Trade ``` из полностью корректной транспортной модели. --- ## Проверка преобразования стороны агрессора Отдельные тесты подтверждают корректное преобразование обоих допустимых значений: - `buyer=True`; - `buyer=False`. --- ## Проверка преобразования числовых представлений Параметризованные тесты подтверждают корректную обработку: - строк; - целых чисел; - чисел с плавающей точкой. для полей: - `price`; - `size`. --- ## Проверка преобразования времени Подтверждается корректное построение объекта ```text datetime ``` в часовом поясе UTC. --- ## Проверка канонических имён полей Отдельные тесты подтверждают: - преобразование `size → quantity`; - удаление пробельных символов из `symbol`; - заполнение поля `source`; - отсутствие поля `order_id` в канонической модели. --- ## Проверка неизменяемости моделей Отдельные тесты подтверждают: - неизменяемость транспортной модели после Mapping; - неизменяемость созданной модели `Trade`. --- ## Проверка ошибок преобразования Реализованы проверки генерации ```text TradeMappingError ``` при невозможности преобразования: - числовых значений; - специальных числовых представлений; - недопустимого значения timestamp. # Результаты тестирования После завершения реализации выполнен целевой запуск нового набора unit-тестов. ```bash python -m pytest \ tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_mapper.py \ -q ``` Результат: ```text 22 passed in 0.04s ``` Все проверки новой функциональности успешно завершены. Новый набор тестов полностью покрывает: - успешное построение канонической модели `Trade`; - преобразование транспортных числовых представлений; - преобразование временной метки в `datetime`; - преобразование стороны агрессора; - переименование транспортных полей; - заполнение источника данных; - генерацию исключений для всех типов ошибок Mapping; - неизменяемость транспортной и канонической моделей. Параметризованные тесты позволили существенно сократить объём тестового кода без уменьшения покрытия и обеспечили единообразную проверку различных вариантов входных данных. --- # Регрессионное тестирование После завершения реализации выполнен полный запуск набора unit-тестов проекта. ```bash python -m pytest -q ``` Результат: ```text 1230 passed in 2.00s ``` Регрессий существующей функциональности не обнаружено. Все ранее реализованные Build продолжают работать без каких-либо изменений. Это подтверждает, что добавленная функциональность полностью изолирована и не влияет на существующие конвейеры обработки Quote, OHLC, REST Trade и остальные подсистемы проекта. --- # Проверка компиляции После завершения реализации выполнена полная проверка компиляции проекта. ```bash python -m compileall src tests ``` Компиляция завершилась успешно. Ошибок синтаксиса не обнаружено. Все изменённые файлы успешно компилируются и не нарушают целостность проекта. --- # Проверка Git diff После завершения реализации выполнена финальная проверка изменений. ```bash git diff --check ``` Результат: ```text без замечаний ``` Проверка подтвердила отсутствие: - trailing whitespace; - ошибок окончания строк; - конфликтов diff; - нарушений форматирования. --- # Scope Build 060.13 В рамках данного Build реализован исключительно уровень ```text WebSocket Trade Mapper ``` Build **не включает**: - WebSocket Trade Schema Validation; - WebSocket Trade Parser; - WebSocket Trade Value Validation; - WebSocket Trade Adapter; - Runtime Integration; - Unified Routing; - Trades Feed. Подобное ограничение полностью соответствует принятому принципу атомарной реализации Build. Каждый этап дорожной карты реализует только один архитектурный уровень Pipeline. --- # Архитектурный результат После завершения Build система содержит полностью реализованный транспортный Pipeline WebSocket Trade вплоть до создания канонической модели предметной области. Конвейер обработки принимает следующий вид. ```text Raw WebSocket Trade │ ▼ WebSocket Trade Schema Validation │ ▼ ValidatedWebSocketTradeDocument │ ▼ WebSocket Trade Parser │ ▼ DzengiWebSocketTradeEvent │ ▼ WebSocket Trade Value Validation │ ▼ DzengiWebSocketTradeEvent │ ▼ WebSocket Trade Mapper │ ▼ Trade ``` Таким образом архитектура WebSocket Trade теперь полностью повторяет ранее реализованные конвейеры обработки Quote, OHLC и REST Trade. После завершения Mapping дальнейшие уровни системы больше не используют транспортную модель биржи. Все последующие компоненты работают исключительно с канонической моделью ```text Trade ``` что полностью соответствует базовым архитектурным принципам Dzentra. --- # Состояние WebSocket Trade Pipeline После завершения Build 060.13 конвейер имеет следующий вид. ```text Raw WebSocket Trade │ ▼ Schema Validation │ ▼ ValidatedWebSocketTradeDocument │ ▼ Parser │ ▼ DzengiWebSocketTradeEvent │ ▼ Value Validation │ ▼ DzengiWebSocketTradeEvent │ ▼ 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 | Pending | | Unified WebSocket Routing | 060.15 | Pending | --- # Соблюдение архитектурных принципов В рамках Build полностью сохранены архитектурные инварианты Dzentra. ## Локальность изменений Изменены только: - `adapters/dzengi/mapper.py`; - unit-тесты нового уровня Mapping. Существующая логика Quote, OHLC и REST Trade не изменялась. --- ## Повторное использование архитектуры Новая реализация полностью повторяет существующий архитектурный шаблон Mapper. Новая архитектура не проектировалась. Использован уже существующий подход, применяемый для остальных типов рыночных данных. --- ## Повторное использование инфраструктуры Для преобразования числовых значений, временных меток и канонических перечислений повторно использованы существующие helper-функции проекта. Build не вводит новых механизмов преобразования и не дублирует уже реализованную функциональность. Это обеспечивает единообразное поведение всех Mapper проекта. --- ## Разделение ответственности Mapper отвечает исключительно за преобразование транспортной модели в каноническую модель предметной области. Build не выполняет: - Schema Validation; - Value Validation; - Runtime Integration; - бизнес-логику; - маршрутизацию событий. Все перечисленные задачи остаются ответственностью предыдущих либо последующих уровней Pipeline. --- ## Обратная совместимость Существующая обработка: - Quote; - OHLC; - REST Trade; не изменилась. Добавленная функциональность полностью изолирована и не оказывает влияния на ранее реализованные компоненты системы. --- # Архитектурные решения Build (ADR) ## ADR-060.13-001 **Mapper всегда создаёт новую каноническую модель.** После завершения преобразования транспортная модель больше не используется последующими уровнями системы. Это обеспечивает полную изоляцию внутренней архитектуры от API конкретной биржи. --- ## ADR-060.13-002 **Все преобразования транспортных типов выполняются исключительно внутри Mapper.** Преобразование транспортных числовых представлений в `Decimal`, временной метки в `datetime` и транспортных признаков сделки в канонические перечисления не допускается ни на одном другом уровне Pipeline. --- ## ADR-060.13-003 **Транспортные поля, отсутствующие в предметной области, не переносятся в каноническую модель.** Поле ```text order_id ``` является частью транспортного контракта биржи и не включается в модель ```text Trade ``` --- ## ADR-060.13-004 **Mapper использует существующее исключение TradeMappingError.** Build не вводит новых типов исключений. Все ошибки преобразования продолжают использовать единый механизм обработки ошибок Mapping. --- # Критерии завершения Build Build 060.13 считается завершённым, поскольку выполнены все поставленные задачи. - ✔ реализована функция `map_dzengi_websocket_trade_to_trade()`; - ✔ реализовано преобразование транспортной модели в каноническую модель `Trade`; - ✔ реализовано преобразование транспортных числовых представлений в `Decimal`; - ✔ реализовано преобразование временной метки в `datetime`; - ✔ реализовано преобразование стороны агрессора; - ✔ реализовано заполнение поля `source`; - ✔ исключено транспортное поле `order_id`; - ✔ повторно использованы существующие helper-функции; - ✔ используется существующее исключение `TradeMappingError`; - ✔ транспортная модель остаётся неизменяемой; - ✔ реализовано 22 unit-теста; - ✔ целевой набор тестов успешно проходит; - ✔ полное регрессионное тестирование успешно завершено; - ✔ проект успешно компилируется; - ✔ `git diff --check` не выявил замечаний; - ✔ изменения полностью укладываются в согласованный scope Build. --- # Следующий этап Следующим этапом дорожной карты является ```text Build 060.14 — WebSocket Trade Adapter ``` Цель следующего Build: - объединить Schema Validation, Parser, Value Validation и Mapper в единый адаптер; - реализовать единую точку обработки WebSocket Trade; - завершить адаптер получения канонической модели `Trade` из сырого WebSocket-сообщения; - подготовить основу для последующей интеграции в Unified WebSocket Routing. --- # Итог Build 060.13 завершил реализацию уровня **WebSocket Trade Mapper** и сделал транспортный Pipeline WebSocket Trade полностью независимым от формата данных биржи Dzengi. Новая реализация основана на уже существующих архитектурных принципах Dzentra, повторно использует существующую инфраструктуру преобразования данных, создаёт каноническую модель предметной области и сохраняет строгое разделение ответственности между уровнями Pipeline. Build ограничен согласованным scope, успешно прошёл целевое и полное регрессионное тестирование, подтвердил отсутствие регрессий и завершил построение транспортного конвейера WebSocket Trade до уровня канонической модели. Следующим этапом развития серии является **Build 060.14 — WebSocket Trade Adapter**, который объединит все реализованные уровни Pipeline в единый компонент обработки входящих WebSocket-сообщений.