# Build 059.6 — WebSocket OHLC Mapper **Проект:** Dzentra **Подсистема:** Market Data Acquisition **Этап:** 059.6 **Статус:** Completed --- # Цель Реализовать преобразование проверенного WebSocket OHLC-события Dzengi во внутреннюю модель Dzentra: ```text CandleCloseEvent ``` Mapper должен стать последней стадией обработки WebSocket-сообщения перед передачей события во внутренний runtime. --- # Причина изменения После завершения Build 059.5 система уже содержит: ```text Raw WebSocket JSON ↓ Schema Validation ↓ ValidatedWebSocketOhlcDocument ↓ Parser ↓ DzengiWebSocketOhlcEvent ↓ Value Validation ``` Однако transport-модель адаптера: ```text DzengiWebSocketOhlcEvent ``` не должна использоваться за пределами слоя интеграции с Dzengi. Для внутренних компонентов требуется полностью source-independent модель: ```text CandleCloseEvent ``` Build 059.6 добавляет именно этот переход. --- # Реализовано Изменены файлы: ```text src/market_data/acquisition/adapters/dzengi/mapper.py src/market_data/acquisition/exceptions.py ``` Создан файл: ```text tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_mapper.py ``` --- # Новый mapper Добавлена функция: ```text map_dzengi_websocket_ohlc_to_candle_close_event() ``` Назначение функции: ```text DzengiWebSocketOhlcEvent ↓ CandleCloseEvent ``` Mapper предполагает, что ранее уже были выполнены: - schema validation; - parsing; - value validation. Повторная проверка предметной корректности данных не выполняется. --- # Выполняемые преобразования Mapper выполняет только преобразования типов. ## Timestamp Поле: ```text t ``` преобразуется из: ```text int (milliseconds) ``` в: ```text datetime (UTC) ``` --- ## OHLC Поля: ```text o h l c ``` преобразуются из: ```text str | int | float ``` в: ```text Decimal ``` Все вычисления внутри runtime далее выполняются только с использованием `Decimal`. --- ## received_at Mapper принимает дополнительный аргумент: ```text received_at ``` Тип: ```text datetime ``` Обязательное требование: ```text timezone-aware datetime ``` Naive datetime считается ошибкой mapper. --- ## source Mapper автоматически устанавливает источник: ```text dzengi_websocket_ohlc ``` Тем самым внутренние модели больше не зависят от транспортного контракта адаптера. --- # Новый тип ошибки Добавлено исключение: ```text CandleWebSocketMappingError ``` Исключение используется исключительно на этапе mapping. Это позволяет разделить ошибки различных стадий обработки. Схема обработки стала следующей: ```text Transport ↓ CandleTransportError Schema ↓ CandleWebSocketSchemaError Parser ↓ CandleWebSocketParseError Value Validation ↓ CandleWebSocketValueError Mapper ↓ CandleWebSocketMappingError ``` Таким образом каждая стадия имеет собственный независимый контракт ошибок. --- # Почему не используется CandleMappingError В проекте уже существует: ```text CandleMappingError ``` Однако он относится исключительно к REST Candles Feed. REST mapper строит: ```text Canonical Candle ``` WebSocket mapper строит: ```text CandleCloseEvent ``` Это разные сущности. Использование общего исключения привело бы к смешиванию двух различных контрактов. Поэтому был введён отдельный тип ошибки. --- # Использование существующих helper-функций В mapper уже присутствуют helper-функции для REST-свечей. Например: ```text _required_candle_decimal() ``` Они намеренно не переиспользуются. Причина: они выбрасывают: ```text CandleMappingError ``` WebSocket mapper обязан выбрасывать: ```text CandleWebSocketMappingError ``` Поэтому были реализованы отдельные helper-функции. --- # Новые helper-функции Добавлены: ```text _required_websocket_ohlc_decimal() _websocket_ohlc_timestamp_ms_to_utc_datetime() _require_websocket_ohlc_aware_datetime() ``` Они полностью изолированы от REST mapper. Это исключает смешивание различных источников данных и их контрактов. --- # Преобразование времени Поле: ```text t ``` содержит Unix timestamp в миллисекундах. Mapper преобразует его через: ```python datetime.fromtimestamp( value / 1000, tz=timezone.utc, ) ``` В результате: ```text open_time ``` становится объектом: ```text datetime UTC ``` Внутренние слои больше не работают с миллисекундными timestamp. --- # Преобразование Decimal Каждое поле: ```text open high low close ``` проходит преобразование: ```text str | int | float ↓ Decimal ``` Дополнительно проверяется: - корректность числа; - конечность значения. --- # Контракт mapper Mapper не выполняет: - schema validation; - parsing; - value validation; - REST reconciliation; - получение объёма; - создание канонической `Candle`. Единственная задача mapper: ```text построить внутреннюю модель CandleCloseEvent из уже проверенного DzengiWebSocketOhlcEvent ``` --- # Что mapper НЕ делает Mapper намеренно не выполняет следующие действия. ## Не создаёт volume Фактический runtime-контракт Dzengi содержит: ```text symbol interval type t o h l c ``` Поля: ```text volume ``` не существует. Поэтому mapper не должен: - подставлять `0`; - использовать `None`; - вычислять объём самостоятельно. Полный объём будет получен позднее через REST reconciliation. --- ## Не создаёт Candle Mapper не строит: ```text Candle ``` Причина проста. Каноническая модель требует: ```text volume ``` которого WebSocket не содержит. Следовательно единственным корректным результатом mapper является: ```text CandleCloseEvent ``` --- ## Не выполняет REST reconciliation Build 059.6 заканчивается на построении внутреннего события. Дальнейший pipeline выглядит следующим образом: ```text CandleCloseEvent ↓ REST reconciliation ↓ Canonical Candle ``` Сам reconciliation будет реализован отдельным этапом. --- # Поддерживаемые типы свечей Mapper сохраняет значение: ```text candle_type ``` без изменения. Поддерживаемые runtime-значения: ```text classic heikin-ashi ``` Mapper не интерпретирует содержимое свечи. Он лишь переносит значение в внутреннюю модель. --- # Обработка received_at Поле: ```text received_at ``` не вычисляется автоматически. Оно передаётся извне. Это решение позволяет: - использовать единый момент получения сообщения; - исключить влияние задержек внутри mapper; - обеспечить единый источник времени для всей системы. Mapper только проверяет: ```text timezone-aware datetime ``` Naive datetime приводит к ошибке: ```text CandleWebSocketMappingError ``` --- # Конвейер после Build 059.6 После завершения этапа полный pipeline выглядит следующим образом: ```text Raw WebSocket message ↓ Schema Validation ↓ ValidatedWebSocketOhlcDocument ↓ Parser ↓ DzengiWebSocketOhlcEvent ↓ Value Validation ↓ Mapper ↓ CandleCloseEvent ``` На этом Build 059.6 завершается. Следующий этап добавит использование новой модели внутри runtime. --- # Unit Tests Создан файл: ```text tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_mapper.py ``` Проверяются: - построение `CandleCloseEvent`; - преобразование всех полей; - преобразование timestamp; - преобразование числовых типов в `Decimal`; - сохранение `received_at`; - поддержка `heikin-ashi`; - обрезка пробелов у symbol; - проверка timezone-aware datetime; - ошибка при невозможности преобразовать timestamp; - ошибка при некорректном Decimal; - ошибка при бесконечных значениях (`NaN`, `Infinity`); - отсутствие создания `volume`. Всего выполнено: ```text 16 tests ``` --- # Проверка синтаксиса Выполнена команда: ```bash python -m compileall \ src/market_data/acquisition/exceptions.py \ src/market_data/acquisition/adapters/dzengi/mapper.py \ tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_mapper.py ``` Результат: ```text успешно ``` --- # Целевые тесты Выполнена команда: ```bash python -m pytest -q \ tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_mapper.py ``` Результат: ```text 16 passed ``` --- # Регрессия mapper Выполнена команда: ```bash python -m pytest -q \ tests/unit/market_data/acquisition/adapters/dzengi/test_mapper.py \ tests/unit/market_data/acquisition/adapters/dzengi/test_quote_mapper.py \ tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_mapper.py \ tests/unit/market_data/acquisition/adapters/dzengi/test_candle_mapper.py \ tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_mapper.py ``` Результат: ```text 52 passed ``` --- # Дополнительная регрессия Выполнена команда: ```bash python -m pytest -q \ tests/unit/market_data/acquisition/models \ tests/unit/market_data/acquisition/validation/test_websocket_ohlc_values.py ``` Результат: ```text 126 passed ``` --- # Проверка форматирования Выполнена команда: ```bash git diff --check ``` Ошибок форматирования не обнаружено. --- # Изменённые файлы ```text src/market_data/acquisition/adapters/dzengi/mapper.py src/market_data/acquisition/exceptions.py ``` Создан файл: ```text tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_mapper.py ``` Документация: ```text docs/migrations/build_059_6.md ``` --- # Совместимость Build 059.6 полностью обратно совместим. Не изменены: - REST Candles Feed; - REST Candle mapper; - Quote mapper; - Instrument mapper; - ExchangeService; - runtime; - Trading; - Market Analysis; - каноническая модель `Candle`. Новый mapper пока не используется существующим runtime и не влияет на работу действующего торгового бота. --- # Архитектурное значение Build 059.6 завершает формирование полного внутреннего конвейера преобразования WebSocket OHLC. После этого этапа система имеет чёткое разделение уровней ответственности: ```text Transport Layer │ ▼ DzengiWebSocketOhlcEvent │ ▼ Validation Layer │ ▼ Mapper Layer │ ▼ CandleCloseEvent ``` Каждый уровень отвечает только за собственную задачу: | Уровень | Ответственность | |---------|-----------------| | Schema Validation | Проверка структуры входящего сообщения | | Parser | Построение transport-модели | | Value Validation | Проверка допустимости значений | | Mapper | Преобразование transport-модели во внутреннюю модель | | Runtime | Использование внутренней модели | Такое разделение соответствует общей архитектуре Dzentra и исключает смешивание обязанностей между слоями. --- # Ограничения этапа Build 059.6 намеренно **не реализует**: - подписку на WebSocket; - публикацию события в runtime; - REST reconciliation; - получение объёма свечи; - создание канонической `Candle`; - проверку соответствия REST и WebSocket OHLC; - восстановление после разрыва соединения; - повторную синхронизацию истории свечей. Все перечисленные задачи относятся к последующим этапам Build 059. --- # Итог Build 059.6 добавляет полноценный mapper: ```text DzengiWebSocketOhlcEvent ↓ CandleCloseEvent ``` В результате система получила: - источник-независимую внутреннюю модель события закрытия свечи; - корректное преобразование timestamp в `datetime (UTC)`; - преобразование цен в `Decimal`; - собственный контракт ошибок (`CandleWebSocketMappingError`); - полную изоляцию WebSocket-контракта Dzengi от внутренних компонентов Dzentra; - набор unit-тестов, подтверждающих корректность преобразования и обработки ошибок. После Build 059.6 цепочка обработки WebSocket OHLC стала полностью завершённой вплоть до внутреннего события Dzentra. --- # Следующий этап ```text Build 059.7 — WebSocket Adapter ``` На этом этапе новый mapper будет встроен в адаптер получения рыночных данных, чтобы при поступлении подтверждённого WebSocket OHLC-сообщения формировался `CandleCloseEvent`, который затем сможет быть передан в механизм REST reconciliation.