593 lines
13 KiB
Markdown
593 lines
13 KiB
Markdown
# Build 059.3 — WebSocket OHLC Parser
|
||
|
||
**Проект:** Dzentra
|
||
**Подсистема:** Market Data Acquisition
|
||
**Этап:** 059.3
|
||
**Статус:** Completed
|
||
|
||
---
|
||
|
||
# Цель
|
||
|
||
Добавить parser для структурно проверенного события Dzengi WebSocket OHLC.
|
||
|
||
Parser должен преобразовывать:
|
||
|
||
```text
|
||
ValidatedWebSocketOhlcDocument
|
||
```
|
||
|
||
в:
|
||
|
||
```text
|
||
DzengiWebSocketOhlcEvent
|
||
```
|
||
|
||
При этом parser не должен выполнять:
|
||
|
||
- предметную проверку значений;
|
||
- проверку поддерживаемых интервалов;
|
||
- проверку допустимого типа свечи;
|
||
- преобразование timestamp в `datetime`;
|
||
- преобразование цен в `Decimal`;
|
||
- проверку OHLC-инвариантов;
|
||
- mapping во внутреннюю модель Dzentra.
|
||
|
||
---
|
||
|
||
# Причина изменения
|
||
|
||
После Build 059.2 проект умеет проверять структуру сообщения Dzengi WebSocket:
|
||
|
||
```json
|
||
{
|
||
"status": "OK",
|
||
"destination": "ohlc.event",
|
||
"payload": {
|
||
"symbol": "BTC/USD_LEVERAGE",
|
||
"interval": "1m",
|
||
"type": "classic",
|
||
"t": 1784224740000,
|
||
"o": 63992.0,
|
||
"h": 64032.55,
|
||
"l": 63984.0,
|
||
"c": 64032.55
|
||
}
|
||
}
|
||
```
|
||
|
||
Однако структурно проверенный документ ещё не преобразовывался в transport-модель адаптера Dzengi.
|
||
|
||
Build 059.3 добавляет отдельный parsing-слой между schema validation и value validation.
|
||
|
||
---
|
||
|
||
# Реализованные изменения
|
||
|
||
## Parser
|
||
|
||
В файл:
|
||
|
||
```text
|
||
src/market_data/acquisition/adapters/dzengi/parser.py
|
||
```
|
||
|
||
добавлена функция:
|
||
|
||
```text
|
||
parse_dzengi_websocket_ohlc()
|
||
```
|
||
|
||
Она принимает:
|
||
|
||
```text
|
||
ValidatedWebSocketOhlcDocument
|
||
```
|
||
|
||
и возвращает:
|
||
|
||
```text
|
||
DzengiWebSocketOhlcEvent
|
||
```
|
||
|
||
---
|
||
|
||
## Исключение parser-слоя
|
||
|
||
В файл:
|
||
|
||
```text
|
||
src/market_data/acquisition/exceptions.py
|
||
```
|
||
|
||
добавлено исключение:
|
||
|
||
```text
|
||
CandleWebSocketParseError
|
||
```
|
||
|
||
Оно используется только при невозможности преобразовать структурно проверенный WebSocket OHLC-документ в transport-модель адаптера.
|
||
|
||
---
|
||
|
||
## Unit-тесты
|
||
|
||
Создан файл:
|
||
|
||
```text
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_parser.py
|
||
```
|
||
|
||
Он покрывает успешное преобразование и ошибки транспортных типов.
|
||
|
||
---
|
||
|
||
# Соответствие полей
|
||
|
||
Parser выполняет следующее преобразование:
|
||
|
||
| WebSocket payload | Transport model |
|
||
|---|---|
|
||
| `symbol` | `symbol` |
|
||
| `interval` | `interval` |
|
||
| `type` | `candle_type` |
|
||
| `t` | `open_time` |
|
||
| `o` | `open_price` |
|
||
| `h` | `high_price` |
|
||
| `l` | `low_price` |
|
||
| `c` | `close_price` |
|
||
|
||
Parser не переименовывает и не интерпретирует значения предметно. Он только переносит их в явную transport-модель адаптера.
|
||
|
||
---
|
||
|
||
# Проверка базовых транспортных типов
|
||
|
||
## Строковые поля
|
||
|
||
Следующие поля обязаны быть строками:
|
||
|
||
```text
|
||
symbol
|
||
interval
|
||
type
|
||
```
|
||
|
||
Если одно из них не является строкой, выбрасывается:
|
||
|
||
```text
|
||
CandleWebSocketParseError
|
||
```
|
||
|
||
Пример ошибки:
|
||
|
||
```text
|
||
$.payload.symbol должен быть строкой
|
||
```
|
||
|
||
---
|
||
|
||
## Timestamp
|
||
|
||
Поле:
|
||
|
||
```text
|
||
t
|
||
```
|
||
|
||
должно быть целым числом Python:
|
||
|
||
```text
|
||
int
|
||
```
|
||
|
||
Не принимаются:
|
||
|
||
```text
|
||
None
|
||
bool
|
||
float
|
||
str
|
||
list
|
||
```
|
||
|
||
`bool` отклоняется отдельно, несмотря на то что в Python он является подклассом `int`.
|
||
|
||
Parser не проверяет, является ли timestamp положительным или реалистичным. Это относится к Value Validation.
|
||
|
||
---
|
||
|
||
## Поля OHLC
|
||
|
||
Поля:
|
||
|
||
```text
|
||
o
|
||
h
|
||
l
|
||
c
|
||
```
|
||
|
||
могут быть представлены как:
|
||
|
||
```text
|
||
str
|
||
int
|
||
float
|
||
```
|
||
|
||
Это соответствует существующему транспортному типу:
|
||
|
||
```text
|
||
DzengiRawNumeric
|
||
```
|
||
|
||
Такой контракт необходим, поскольку внешнее API может передавать числовые значения как JSON-числа или строки.
|
||
|
||
Не принимаются:
|
||
|
||
```text
|
||
None
|
||
bool
|
||
list
|
||
dict
|
||
object
|
||
```
|
||
|
||
---
|
||
|
||
# Сохранение исходных числовых типов
|
||
|
||
Parser не преобразует цены.
|
||
|
||
Например, документ:
|
||
|
||
```json
|
||
{
|
||
"o": "63992.00",
|
||
"h": 64033,
|
||
"l": 63984.0,
|
||
"c": "64032.55"
|
||
}
|
||
```
|
||
|
||
преобразуется в transport-модель с теми же исходными типами:
|
||
|
||
```text
|
||
open_price = str
|
||
high_price = int
|
||
low_price = float
|
||
close_price = str
|
||
```
|
||
|
||
Преобразование в `Decimal` будет выполняться только на mapping-этапе.
|
||
|
||
---
|
||
|
||
# Поддержка типов свечей
|
||
|
||
Parser сохраняет значение поля:
|
||
|
||
```text
|
||
type
|
||
```
|
||
|
||
в поле transport-модели:
|
||
|
||
```text
|
||
candle_type
|
||
```
|
||
|
||
Поддерживаются на уровне базового транспортного типа любые строки.
|
||
|
||
Например:
|
||
|
||
```text
|
||
classic
|
||
heikin-ashi
|
||
unknown
|
||
```
|
||
|
||
Parser не определяет допустимость значения.
|
||
|
||
Проверка, что значение входит в подтверждённый runtime-набор:
|
||
|
||
```text
|
||
classic
|
||
heikin-ashi
|
||
```
|
||
|
||
будет реализована в Build 059.4 — Value Validation.
|
||
|
||
---
|
||
|
||
# Отсутствие предметной валидации
|
||
|
||
Parser намеренно пропускает значения, которые имеют допустимый транспортный тип, но являются предметно некорректными.
|
||
|
||
Пример:
|
||
|
||
```text
|
||
symbol = ""
|
||
interval = "unsupported"
|
||
candle_type = "unknown"
|
||
open_time = -1
|
||
open_price = "NaN"
|
||
high_price = -10
|
||
low_price = 100
|
||
close_price = 0
|
||
```
|
||
|
||
Такой документ успешно преобразуется в `DzengiWebSocketOhlcEvent`.
|
||
|
||
Это ожидаемое поведение.
|
||
|
||
Следующий слой должен проверить:
|
||
|
||
- непустой символ;
|
||
- допустимый интервал;
|
||
- допустимый тип свечи;
|
||
- положительный timestamp;
|
||
- конечность чисел;
|
||
- положительность цен;
|
||
- OHLC-инварианты.
|
||
|
||
---
|
||
|
||
# Игнорирование metadata envelope
|
||
|
||
Parser использует только:
|
||
|
||
```text
|
||
document.payload
|
||
```
|
||
|
||
Поля внешнего envelope:
|
||
|
||
```text
|
||
status
|
||
destination
|
||
correlation_id
|
||
```
|
||
|
||
не участвуют в создании `DzengiWebSocketOhlcEvent`.
|
||
|
||
Это обеспечивает разделение ответственности:
|
||
|
||
```text
|
||
Schema Validation
|
||
↓
|
||
проверяет структуру envelope и payload
|
||
|
||
Parser
|
||
↓
|
||
преобразует payload в transport-модель
|
||
|
||
Value Validation
|
||
↓
|
||
проверяет предметную допустимость значений
|
||
```
|
||
|
||
---
|
||
|
||
# Новый конвейер
|
||
|
||
После Build 059.3 подготовленная часть конвейера выглядит так:
|
||
|
||
```text
|
||
Raw WebSocket message
|
||
↓
|
||
validate_dzengi_websocket_ohlc_schema()
|
||
↓
|
||
ValidatedWebSocketOhlcDocument
|
||
↓
|
||
parse_dzengi_websocket_ohlc()
|
||
↓
|
||
DzengiWebSocketOhlcEvent
|
||
↓
|
||
Value Validation
|
||
↓
|
||
Internal Close Event
|
||
↓
|
||
REST reconciliation
|
||
↓
|
||
Canonical Candle
|
||
```
|
||
|
||
На текущем этапе реализована часть до `DzengiWebSocketOhlcEvent` включительно.
|
||
|
||
---
|
||
|
||
# Unit Tests
|
||
|
||
Добавлен файл:
|
||
|
||
```text
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_parser.py
|
||
```
|
||
|
||
Проверяются следующие сценарии:
|
||
|
||
- успешное создание transport-модели;
|
||
- корректное соответствие всех полей;
|
||
- сохранение типа `classic`;
|
||
- сохранение типа `heikin-ashi`;
|
||
- сохранение исходных числовых типов;
|
||
- отклонение нестрокового `symbol`;
|
||
- отклонение нестрокового `interval`;
|
||
- отклонение нестрокового `type`;
|
||
- отклонение `None` в timestamp;
|
||
- отклонение `bool` в timestamp;
|
||
- отклонение `float` в timestamp;
|
||
- отклонение строкового timestamp;
|
||
- отклонение коллекции вместо timestamp;
|
||
- отклонение некорректных типов для `o`;
|
||
- отклонение некорректных типов для `h`;
|
||
- отклонение некорректных типов для `l`;
|
||
- отклонение некорректных типов для `c`;
|
||
- отсутствие предметной валидации;
|
||
- игнорирование metadata внешнего envelope.
|
||
|
||
Всего выполнено:
|
||
|
||
```text
|
||
36 tests
|
||
```
|
||
|
||
---
|
||
|
||
# Проверка синтаксиса
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
python -m compileall \
|
||
src/market_data/acquisition/exceptions.py \
|
||
src/market_data/acquisition/adapters/dzengi/parser.py \
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_parser.py
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
успешно
|
||
```
|
||
|
||
---
|
||
|
||
# Целевые тесты
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
python -m pytest -q \
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_parser.py
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
36 passed in 0.03s
|
||
```
|
||
|
||
---
|
||
|
||
# Регрессия parser-слоя
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
python -m pytest -q \
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_parser.py \
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_quote_parser.py \
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_parser.py \
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_candle_parser.py \
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_parser.py
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
80 passed in 0.05s
|
||
```
|
||
|
||
---
|
||
|
||
# Проверка форматирования
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
git diff --check
|
||
```
|
||
|
||
Ошибок форматирования не обнаружено.
|
||
|
||
---
|
||
|
||
# Изменённые файлы
|
||
|
||
```text
|
||
src/market_data/acquisition/adapters/dzengi/parser.py
|
||
src/market_data/acquisition/exceptions.py
|
||
```
|
||
|
||
Создан файл:
|
||
|
||
```text
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_parser.py
|
||
```
|
||
|
||
Документация этапа:
|
||
|
||
```text
|
||
docs/migrations/build_059_3.md
|
||
```
|
||
|
||
---
|
||
|
||
# Совместимость
|
||
|
||
Изменение полностью обратно совместимо.
|
||
|
||
Не изменены:
|
||
|
||
- REST Candles Feed;
|
||
- REST parser свечей;
|
||
- Quotes Feed;
|
||
- WebSocket Quote parser;
|
||
- WebSocket Quote Adapter;
|
||
- ExchangeService;
|
||
- Market Analysis;
|
||
- Trading;
|
||
- runtime.
|
||
|
||
Новый parser пока не подключён к рабочему WebSocket runtime и не влияет на действующий бот.
|
||
|
||
---
|
||
|
||
# Ограничения этапа
|
||
|
||
Build 059.3 не выполняет:
|
||
|
||
- проверку значения `status`;
|
||
- проверку значения `destination`;
|
||
- проверку допустимых интервалов;
|
||
- проверку допустимого типа свечи;
|
||
- проверку timestamp;
|
||
- проверку конечности OHLC;
|
||
- проверку положительности OHLC;
|
||
- проверку OHLC-инвариантов;
|
||
- преобразование в `Decimal`;
|
||
- преобразование времени в UTC `datetime`;
|
||
- создание внутреннего close event;
|
||
- REST reconciliation;
|
||
- выпуск канонической `Candle`.
|
||
|
||
Эти обязанности остаются за последующими подпунктами Build 059.
|
||
|
||
---
|
||
|
||
# Итог
|
||
|
||
Build 059.3 завершает parsing-слой Dzengi WebSocket OHLC.
|
||
|
||
Проект теперь умеет преобразовывать структурно проверенный документ:
|
||
|
||
```text
|
||
ValidatedWebSocketOhlcDocument
|
||
```
|
||
|
||
в transport-модель:
|
||
|
||
```text
|
||
DzengiWebSocketOhlcEvent
|
||
```
|
||
|
||
без смешивания parsing, value validation и domain mapping.
|
||
|
||
Следующий этап:
|
||
|
||
```text
|
||
Build 059.4 — Value Validation
|
||
``` |