# Build 059.5 — Internal Candle Close Event Model **Проект:** Dzentra **Подсистема:** Market Data Acquisition **Этап:** 059.5 **Статус:** Completed --- # Цель Добавить внутреннюю immutable-модель уведомления о закрытии OHLC-свечи. Модель должна представлять уже проверенное WebSocket-событие внутри Dzentra, но не должна заменять каноническую модель: ```text Candle ``` Причина разделения заключается в том, что фактическое событие Dzengi WebSocket: ```text ohlc.event ``` содержит: ```text symbol interval type t o h l c ``` но не содержит: ```text volume ``` Поэтому WebSocket OHLC-событие не является полноценной OHLCV-свечой. --- # Причина изменения После Build 059.4 проект умеет: 1. структурно проверять входящее WebSocket-сообщение; 2. преобразовывать его в transport-модель; 3. проверять предметную допустимость значений. Подготовленная цепочка выглядит так: ```text Raw WebSocket message ↓ ValidatedWebSocketOhlcDocument ↓ DzengiWebSocketOhlcEvent ↓ Value Validation ``` Однако transport-модель: ```text DzengiWebSocketOhlcEvent ``` принадлежит адаптеру конкретного внешнего источника Dzengi. Перед дальнейшим использованием в runtime требуется источник-независимая внутренняя модель Dzentra. --- # Реализовано Создан файл: ```text src/market_data/acquisition/models/candle_close.py ``` Добавлена модель: ```text CandleCloseEvent ``` --- # Структура модели Модель содержит следующие поля: ```text symbol interval candle_type open_time received_at open_price high_price low_price close_price source ``` Полное назначение модели: ```text внутреннее уведомление Dzentra о завершении OHLC-свечи, требующее последующего REST reconciliation для получения полной OHLCV-свечи ``` --- # Контракт модели Модель объявлена как: ```text frozen=True slots=True ``` Это обеспечивает: - неизменяемость; - отсутствие динамического `__dict__`; - уменьшение вероятности случайного изменения события; - компактное представление в памяти; - предсказуемый runtime-контракт. --- # Отличие от transport-модели Transport-модель: ```text DzengiWebSocketOhlcEvent ``` отражает внешний контракт Dzengi. Она содержит: ```text open_time: int OHLC: str | int | float ``` Внутренняя модель: ```text CandleCloseEvent ``` содержит: ```text open_time: datetime received_at: datetime OHLC: Decimal source: str ``` Таким образом transport-особенности внешнего API не распространяются на внутренние слои Dzentra. --- # Отличие от канонической Candle Каноническая модель: ```text Candle ``` представляет полноценную OHLCV-свечу. Она содержит: ```text volume: Decimal ``` Модель: ```text CandleCloseEvent ``` не содержит `volume`. Это преднамеренное архитектурное решение. WebSocket-событие не должно: - подставлять фиктивный нулевой объём; - использовать nullable volume; - притворяться полной OHLCV-свечой; - наследоваться от `Candle`; - передаваться потребителям, ожидающим каноническую свечу. Полная `Candle` будет получена только после REST reconciliation. --- # Поля времени ## open_time Поле: ```text open_time ``` представляет время открытия завершённой свечи. Для минутной свечи это начало минутного интервала, к которому относится событие. Тип: ```text datetime ``` Ожидаемая timezone: ```text UTC ``` --- ## received_at Поле: ```text received_at ``` представляет момент получения события системой Dzentra. Оно не равно `open_time`. Разделение двух времён необходимо для: - измерения задержки WebSocket; - диагностики запоздавших сообщений; - определения порядка фактической обработки; - runtime-метрик; - журналирования; - анализа качества источника данных. --- # Поля OHLC Внутренняя модель использует: ```text Decimal ``` для: ```text open_price high_price low_price close_price ``` Это исключает дальнейшее распространение внешних типов: ```text str int float ``` Преобразование transport-значений в `Decimal` будет реализовано в следующем этапе: ```text Build 059.6 — Mapper ``` --- # Поле candle_type Поле: ```text candle_type ``` сохраняет тип серии свечей. Подтверждённые runtime-значения: ```text classic heikin-ashi ``` Для production-интеграции Candles Feed основной интерес представляет: ```text classic ``` Поскольку именно классические свечи будут использоваться для REST reconciliation с каноническими OHLCV-свечами. `heikin-ashi` остаётся поддерживаемым внутренним типом события, но не должен автоматически преобразовываться в обычную каноническую `Candle`. --- # Поле source Поле: ```text source ``` фиксирует происхождение внутреннего события. Пример: ```text dzengi_websocket_ohlc ``` Это позволит в дальнейшем: - различать источники; - поддерживать несколько адаптеров; - вести диагностику; - применять источник-зависимую политику reconciliation; - не встраивать имя Dzengi в общую доменную модель. --- # Новый внутренний конвейер После Build 059.5 архитектура выглядит так: ```text Raw WebSocket message ↓ Schema Validation ↓ ValidatedWebSocketOhlcDocument ↓ Parser ↓ DzengiWebSocketOhlcEvent ↓ Value Validation ↓ Mapper ↓ CandleCloseEvent ↓ REST reconciliation ↓ Canonical Candle ``` На текущем этапе модель `CandleCloseEvent` уже существует, но mapper ещё не реализован. --- # Unit Tests Создан файл: ```text tests/unit/market_data/acquisition/models/test_candle_close.py ``` Проверяются: - сохранение всех полей; - неизменяемость модели; - использование `slots`; - отсутствие `__dict__`; - отсутствие наследования от `Candle`; - отсутствие поля `volume`; - поддержка типа `heikin-ashi`; - раздельное хранение `open_time` и `received_at`. Всего выполнено: ```text 7 tests ``` --- # Проверка синтаксиса Выполнена команда: ```bash python -m compileall \ src/market_data/acquisition/models/candle_close.py \ tests/unit/market_data/acquisition/models/test_candle_close.py ``` Результат: ```text успешно ``` --- # Целевые тесты Выполнена команда: ```bash python -m pytest -q \ tests/unit/market_data/acquisition/models/test_candle_close.py ``` Результат: ```text 7 passed in 0.01s ``` --- # Регрессия слоя моделей Выполнена команда: ```bash python -m pytest -q \ tests/unit/market_data/acquisition/models ``` Результат: ```text 42 passed in 0.02s ``` --- # Проверка форматирования Выполнена команда: ```bash git diff --check ``` Ошибок форматирования не обнаружено. --- # Созданные файлы ```text src/market_data/acquisition/models/candle_close.py tests/unit/market_data/acquisition/models/test_candle_close.py docs/migrations/build_059_5.md ``` --- # Совместимость Изменение полностью обратно совместимо. Не изменены: - каноническая модель `Candle`; - transport-модель Dzengi; - REST Candles Feed; - parser; - value validation; - WebSocket Adapter; - ExchangeService; - runtime; - Market Analysis; - Trading. Новая модель пока не подключена к runtime и не влияет на работающий бот. --- # Ограничения этапа Build 059.5 не выполняет: - преобразование transport-модели; - преобразование timestamp; - преобразование OHLC в `Decimal`; - установку реального `received_at`; - mapping; - WebSocket Adapter; - REST reconciliation; - проверку соответствия REST и WebSocket OHLC; - создание канонической `Candle`; - публикацию события в runtime. --- # Итог Build 059.5 добавляет источник-независимую внутреннюю модель: ```text CandleCloseEvent ``` Она представляет подтверждённое уведомление о завершении OHLC-свечи без фиктивного объёма. Модель создаёт архитектурную границу между: ```text внешним Dzengi WebSocket контрактом ``` и: ```text внутренним runtime Dzentra ``` Следующий этап: ```text Build 059.6 — Mapper ```