Files
dzentra_bot/docs/migrations/build_060_13.md

40 KiB
Raw Blame History

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, подтверждающий корректность всех значений транспортной модели

DzengiWebSocketTradeEvent

На этом этапе Pipeline гарантирует:

  • корректную структуру транспортного документа;
  • успешное построение transport model;
  • корректность всех обязательных значений;
  • отсутствие недопустимых числовых представлений;
  • корректность строковых идентификаторов.

Однако даже после успешного прохождения всех перечисленных этапов транспортная модель ещё не может использоваться остальной частью системы.

Несмотря на корректность структуры и значений, объект

DzengiWebSocketTradeEvent

по-прежнему остаётся транспортной моделью биржи Dzengi.

Он содержит особенности конкретного источника данных:

  • транспортные числовые представления;
  • транспортное поле size;
  • транспортное поле buyer;
  • транспортное поле order_id;
  • транспортное представление времени в миллисекундах Unix Epoch.

Использование подобных моделей за пределами слоя адаптера противоречит базовым архитектурным принципам Dzentra.

Внутренние компоненты системы не должны зависеть от особенностей API конкретной биржи.

Для решения данной задачи архитектура Dzentra предусматривает следующий обязательный уровень Pipeline —

Mapper.

Именно Mapper выполняет преобразование транспортной модели источника данных в единую каноническую модель предметной области.

Build 060.13 реализует данный уровень для WebSocket Trade.

Основная задача Build — преобразовать проверенную транспортную модель

DzengiWebSocketTradeEvent

в каноническую immutable-модель

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.

Во всех случаях использовалась одинаковая архитектурная схема обработки.

Transport Model
        │
        ▼
Mapper
        │
        ▼
Canonical Model

После завершения Build 060.12 аналогичный транспортный конвейер появился и для WebSocket Trade.

ValidatedWebSocketTradeDocument
        │
        ▼
Trade Parser
        │
        ▼
DzengiWebSocketTradeEvent
        │
        ▼
Value Validation
        │
        ▼
DzengiWebSocketTradeEvent

Однако следующий обязательный уровень — преобразование транспортной модели в каноническую модель системы — ещё отсутствовал.

Таким образом WebSocket-конвейер обработки сделок оставался архитектурно незавершённым.


Архитектурное основание

Одним из базовых принципов архитектуры Dzentra является полная изоляция внутренних компонентов системы от формата данных конкретной биржи.

Любые особенности транспортного протокола должны оставаться исключительно внутри слоя адаптера.

Общий конвейер обработки рыночных данных имеет следующий вид.

Raw Source
        │
        ▼
Schema Validation
        │
        ▼
Transport Model
        │
        ▼
Value Validation
        │
        ▼
Mapper
        │
        ▼
Domain Model

Каждый уровень Pipeline отвечает только за одну категорию задач.

Для WebSocket Trade это разделение выглядит следующим образом.

Schema Validation

гарантирует корректность структуры транспортного документа.


Parser

создаёт транспортную модель

DzengiWebSocketTradeEvent

без изменения типов данных.


Value Validation

подтверждает корректность значений транспортной модели без выполнения каких-либо преобразований.


Следующий уровень —

Mapper

выполняет преобразование транспортной модели в каноническую модель предметной области.

Именно Mapper отвечает за:

  • преобразование транспортных числовых представлений в Decimal;
  • преобразование транспортного timestamp в datetime;
  • преобразование транспортных признаков сделки в канонические перечисления;
  • переименование транспортных полей;
  • создание immutable-модели Trade.

При этом Mapper принципиально не выполняет:

  • повторную Validation;
  • анализ структуры JSON;
  • Runtime-логику;
  • бизнес-логику;
  • маршрутизацию событий.

Такое разделение ответственности позволяет каждому уровню Pipeline оставаться независимым, повторно используемым и легко тестируемым.


Результаты архитектурного аудита

Перед реализацией Build был выполнен аудит существующего слоя Mapper.

В ходе анализа подтверждено наличие полностью сформированной архитектуры преобразования транспортных моделей.

Для различных типов рыночных данных уже реализованы функции:

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-функции проекта.

Одновременно аудит подтвердил наличие общего исключения

TradeMappingError

используемого всеми существующими Mapper при невозможности построения канонической модели.

При этом отдельный Mapper для транспортной модели

DzengiWebSocketTradeEvent

в системе отсутствовал.

Таким образом единственным отсутствующим элементом архитектурной цепочки являлся собственный уровень Mapping для WebSocket Trade.

Build 060.13 полностью закрывает данный пробел и завершает следующий обязательный уровень Pipeline серии Build 060.


Архитектурное решение

По результатам проведённого аудита было принято решение полностью повторить архитектурный шаблон, уже используемый Mapper остальных типов рыночных данных.

В систему добавлена функция

map_dzengi_websocket_trade_to_trade(...)

которая принимает транспортную модель

DzengiWebSocketTradeEvent

и создаёт каноническую immutable-модель

Trade

Во время преобразования выполняются все необходимые преобразования транспортных типов данных.

После успешного завершения Mapping транспортная модель больше не используется последующими уровнями системы.

Конвейер WebSocket Trade принимает следующий вид.

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

В файл

src/market_data/acquisition/adapters/dzengi/mapper.py

добавлена новая функция

map_dzengi_websocket_trade_to_trade(...)

Функция получает транспортную модель

DzengiWebSocketTradeEvent

и выполняет построение канонической модели

Trade

Во время выполнения Mapping создаётся новый immutable-объект предметной области.

Транспортная модель при этом остаётся неизменной.

Таким образом следующий уровень Pipeline получает уже не транспортную модель биржи, а полностью независимую внутреннюю модель системы.


Почему Mapper создаёт новую модель

Во время архитектурного проектирования отдельно рассматривался вопрос о возможности повторного использования объекта

DzengiWebSocketTradeEvent

на последующих уровнях системы.

По результатам анализа было принято решение полностью отказаться от подобного подхода.

Основные причины:

  • транспортная модель отражает структуру конкретной биржи;
  • транспортная модель содержит поля, отсутствующие в предметной области;
  • транспортная модель использует транспортные представления числовых данных;
  • транспортная модель использует транспортные соглашения о наименовании полей;
  • внутренние компоненты системы не должны зависеть от API биржи.

Поэтому Mapper всегда создаёт новый экземпляр

Trade

который становится единственной моделью, используемой за пределами слоя адаптера.

Подобное решение полностью соответствует базовому архитектурному принципу Dzentra — полной изоляции внутренних компонентов от транспортных контрактов внешних источников данных.


Преобразуемая транспортная модель

Преобразование выполняется над объектом

@dataclass(frozen=True, slots=True)
class DzengiWebSocketTradeEvent:
    trade_id: int
    price: DzengiRawNumeric
    size: DzengiRawNumeric
    timestamp: int
    symbol: str
    buyer: bool
    order_id: str

После выполнения Mapping создаётся объект

@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 полностью устраняет зависимость системы от транспортной модели биржи.


Преобразование идентификатора сделки

Поле

trade_id

имеет одинаковую семантику в транспортной и канонической модели.

Поэтому Mapper переносит его без изменения значения.

Дополнительных преобразований не выполняется.


Преобразование цены сделки

Поле

price

в транспортной модели может быть представлено в нескольких форматах.

Например:

"63992.50"

63992

63992.50

Во время Mapping используется существующий helper проекта, преобразующий транспортное представление в

Decimal

После завершения Mapping каноническая модель всегда содержит внутренний числовой тип системы независимо от исходного представления данных.


Преобразование количества сделки

Транспортная модель использует поле

size

которое отражает терминологию API биржи.

В канонической модели используется единое наименование

quantity

Во время Mapping выполняются одновременно два действия:

  • преобразование транспортного значения в Decimal;
  • переименование транспортного поля в соответствии с внутренней моделью предметной области.

После завершения Mapping дальнейшая работа системы полностью абстрагируется от терминологии конкретной биржи.


Преобразование временной метки

Поле

timestamp

в транспортной модели представляет собой количество миллисекунд, прошедших с начала эпохи Unix.

Подобное представление удобно для передачи данных по сети, однако не является внутренним представлением времени в Dzentra.

Во время Mapping используется существующая helper-функция преобразования времени.

В результате каноническая модель получает объект

datetime

в часовом поясе UTC.

Таким образом все внутренние компоненты системы используют единый формат представления времени независимо от способа передачи данных биржей.


Преобразование стороны агрессора

Во время архитектурного аудита отдельно анализировалась семантика транспортного поля

buyer

В WebSocket API Dzengi данное поле определяет сторону покупателя сделки.

Однако внутренняя модель Dzentra использует перечисление

TradeAggressorSide

Поэтому Mapper выполняет явное преобразование транспортного признака в каноническое перечисление.

Используется следующее соответствие.

buyer = True
        │
        ▼
TradeAggressorSide.BUY
buyer = False
        │
        ▼
TradeAggressorSide.SELL

Подобное преобразование делает внутреннюю модель полностью независимой от конкретного транспортного соглашения биржи.


Преобразование источника данных

Каноническая модель

Trade

содержит поле

source

которое используется для идентификации происхождения события.

Во время Mapping данное поле получает фиксированное значение

dzengi_websocket_trade

Использование отдельного идентификатора источника позволяет последующим уровням системы различать происхождение канонических моделей без анализа транспортного Pipeline.


Почему order_id отсутствует в канонической модели

Транспортная модель WebSocket содержит дополнительное поле

order_id

которое используется исключительно транспортным контрактом биржи.

Во время архитектурного проектирования отдельно анализировался вопрос необходимости переноса данного значения в каноническую модель.

По результатам анализа было принято решение отказаться от подобного преобразования.

Основные причины:

  • идентификатор ордера отсутствует в модели предметной области;
  • последующие уровни системы не используют данное значение;
  • сохранение транспортного идентификатора нарушило бы независимость канонической модели.

В результате поле

order_id

полностью завершается на уровне адаптера и не попадает в модель

Trade

Повторное использование существующих helper-функций

Build 060.13 не вводит новых механизмов преобразования числовых значений и времени.

Вместо этого повторно используются уже существующие helper-функции проекта.

Для преобразования числовых представлений применяется существующий механизм преобразования в

Decimal

Для преобразования временной метки используется существующая функция построения объекта

datetime

Подобный подход обеспечивает единообразное поведение всех Mapper проекта независимо от типа источника данных.


Использование TradeMappingError

Все ошибки преобразования используют существующее исключение

TradeMappingError

Build не вводит новых типов исключений.

Это сохраняет единый механизм обработки ошибок Mapping во всей подсистеме Market Data Acquisition.

Value Validation продолжает использовать

TradeValueError

а Mapper использует исключительно

TradeMappingError

Тем самым достигается чёткое разделение ошибок проверки данных и ошибок преобразования транспортной модели.


Изменённые файлы

В рамках Build были изменены только два файла.

Mapper

src/market_data/acquisition/adapters/dzengi/mapper.py

Добавлена функция

map_dzengi_websocket_trade_to_trade(...)

а также вспомогательная функция преобразования стороны агрессора WebSocket Trade.

Существующая логика Mapping Quote, OHLC и REST Trade не изменялась.


Unit-тесты

tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_mapper.py

Добавлен полный набор unit-тестов нового уровня Mapping.


Добавленные тесты

В рамках Build реализовано двадцать два unit-теста, полностью покрывающих новую функциональность.

Проверка корректного Mapping

Подтверждается успешное создание канонической модели

Trade

из полностью корректной транспортной модели.


Проверка преобразования стороны агрессора

Отдельные тесты подтверждают корректное преобразование обоих допустимых значений:

  • buyer=True;
  • buyer=False.

Проверка преобразования числовых представлений

Параметризованные тесты подтверждают корректную обработку:

  • строк;
  • целых чисел;
  • чисел с плавающей точкой.

для полей:

  • price;
  • size.

Проверка преобразования времени

Подтверждается корректное построение объекта

datetime

в часовом поясе UTC.


Проверка канонических имён полей

Отдельные тесты подтверждают:

  • преобразование size → quantity;
  • удаление пробельных символов из symbol;
  • заполнение поля source;
  • отсутствие поля order_id в канонической модели.

Проверка неизменяемости моделей

Отдельные тесты подтверждают:

  • неизменяемость транспортной модели после Mapping;
  • неизменяемость созданной модели Trade.

Проверка ошибок преобразования

Реализованы проверки генерации

TradeMappingError

при невозможности преобразования:

  • числовых значений;
  • специальных числовых представлений;
  • недопустимого значения timestamp.

Результаты тестирования

После завершения реализации выполнен целевой запуск нового набора unit-тестов.

python -m pytest \
  tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_mapper.py \
  -q

Результат:

22 passed in 0.04s

Все проверки новой функциональности успешно завершены.

Новый набор тестов полностью покрывает:

  • успешное построение канонической модели Trade;
  • преобразование транспортных числовых представлений;
  • преобразование временной метки в datetime;
  • преобразование стороны агрессора;
  • переименование транспортных полей;
  • заполнение источника данных;
  • генерацию исключений для всех типов ошибок Mapping;
  • неизменяемость транспортной и канонической моделей.

Параметризованные тесты позволили существенно сократить объём тестового кода без уменьшения покрытия и обеспечили единообразную проверку различных вариантов входных данных.


Регрессионное тестирование

После завершения реализации выполнен полный запуск набора unit-тестов проекта.

python -m pytest -q

Результат:

1230 passed in 2.00s

Регрессий существующей функциональности не обнаружено.

Все ранее реализованные Build продолжают работать без каких-либо изменений.

Это подтверждает, что добавленная функциональность полностью изолирована и не влияет на существующие конвейеры обработки Quote, OHLC, REST Trade и остальные подсистемы проекта.


Проверка компиляции

После завершения реализации выполнена полная проверка компиляции проекта.

python -m compileall src tests

Компиляция завершилась успешно.

Ошибок синтаксиса не обнаружено.

Все изменённые файлы успешно компилируются и не нарушают целостность проекта.


Проверка Git diff

После завершения реализации выполнена финальная проверка изменений.

git diff --check

Результат:

без замечаний

Проверка подтвердила отсутствие:

  • trailing whitespace;
  • ошибок окончания строк;
  • конфликтов diff;
  • нарушений форматирования.

Scope Build 060.13

В рамках данного Build реализован исключительно уровень

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 вплоть до создания канонической модели предметной области.

Конвейер обработки принимает следующий вид.

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 дальнейшие уровни системы больше не используют транспортную модель биржи.

Все последующие компоненты работают исключительно с канонической моделью

Trade

что полностью соответствует базовым архитектурным принципам Dzentra.


Состояние WebSocket Trade Pipeline

После завершения Build 060.13 конвейер имеет следующий вид.

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

Транспортные поля, отсутствующие в предметной области, не переносятся в каноническую модель.

Поле

order_id

является частью транспортного контракта биржи и не включается в модель

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.

Следующий этап

Следующим этапом дорожной карты является

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-сообщений.