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