555 lines
11 KiB
Markdown
555 lines
11 KiB
Markdown
# 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
|
||
``` |