Files
dzentra_bot/docs/migrations/build_059_6.md

797 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.