Files
dzentra_bot/docs/migrations/build_058.md

1268 lines
21 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 058 — Runtime исследование Dzengi WebSocket OHLC Market Data
---
# Контроль документа
| Свойство | Значение |
|----------|-----------|
| Build | 058 |
| Название | Runtime исследование Dzengi WebSocket OHLC Market Data |
| Статус | Completed |
| Проект | Dzentra |
| Подсистема | Market Data Acquisition |
| Источник | Dzengi Exchange |
| Дата | 2026-07-16 |
---
# Цель Build
Провести полное runtime-исследование контракта WebSocket
```
OHLCMarketData.subscribe
```
и определить возможность его использования в качестве
канонического источника Candles Feed.
Необходимо было подтвердить:
- соответствие Swagger реальному API;
- формат подписки;
- формат ACK;
- формат событий;
- поддерживаемые параметры;
- поддержку нескольких символов;
- поддержку нескольких интервалов;
- наличие или отсутствие Volume;
- соответствие REST API;
- возможность использования в Build 059.
---
# Исходные материалы
Были исследованы:
REST
```
GET /api/v1/klines
```
WebSocket
```
OHLCMarketData.subscribe
```
Swagger WebSocket
```
wss://.../connect
```
Также были выполнены реальные подключения к:
```
demo-api-adapter.dzengi.com
```
и
```
api-adapter.dzengi.com
```
---
# Вывод по Demo
Во время исследования обнаружено:
Demo WebSocket корректно принимает подписку:
```
OHLCMarketData.subscribe
```
но поток данных практически отсутствует.
Поэтому дальнейшее исследование проводилось исключительно
на Production API.
---
# Production WebSocket
Используемый URL
```
wss://api-adapter.dzengi.com/connect
```
Подписка выполняется сообщением
```json
{
"correlationId": "<uuid>",
"destination": "OHLCMarketData.subscribe",
"payload": {
"intervals": [
"1m"
],
"symbols": [
"BTC/USD_LEVERAGE"
],
"type": "classic"
}
}
```
После отправки сервер возвращает ACK.
---
# ACK
Фактический ответ сервера
```json
{
"correlationId": "...",
"destination": "OHLCMarketData.subscribe",
"payload": {
"subscriptions": {
"BTC/USD_LEVERAGE:1m": "PROCESSED"
}
},
"status": "OK"
}
```
Подтверждено:
- destination совпадает со Swagger;
- status = OK;
- подписки перечислены внутри payload.subscriptions;
- ключ имеет вид
```
symbol:interval
```
например
```
BTC/USD_LEVERAGE:1m
```
---
# Поддержка нескольких интервалов
Проверена подписка
```json
{
"payload": {
"symbols":[
"BTC/USD_LEVERAGE"
],
"intervals":[
"1m",
"5m"
],
"type":"classic"
}
}
```
Полученный ACK
```json
{
"subscriptions": {
"BTC/USD_LEVERAGE:1m":"PROCESSED",
"BTC/USD_LEVERAGE:5m":"PROCESSED"
}
}
```
Таким образом подтверждено,
что одна подписка может одновременно обслуживать
несколько таймфреймов.
Каждая комбинация
```
symbol + interval
```
имеет собственный статус.
---
# Формат события
После закрытия свечи сервер публикует
```json
{
"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
},
"status":"OK"
}
```
Полученные поля
| Поле | Значение |
|------|----------|
| symbol | инструмент |
| interval | таймфрейм |
| type | тип свечи |
| t | open time |
| o | Open |
| h | High |
| l | Low |
| c | Close |
Volume отсутствует.
Это подтверждено всеми полученными событиями.
---
# Исследование типа свечей
Swagger указывает,
что поле
```
type
```
используется для выбора разновидности свечей.
Во время runtime-исследования были проверены все возможные значения.
---
# Попытка использовать bid
Подписка
```json
{
"payload":{
"symbols":[
"BTC/USD_LEVERAGE"
],
"intervals":[
"1m"
],
"type":"bid"
}
}
```
Ответ сервера
```json
{
"subscriptions":{
"bid":"ERROR: Invalid type. Valid: classic, heikin-ashi"
}
}
```
Таким образом подтверждено,
что WebSocket не использует параметр
```
priceType
```
аналогичный REST.
---
# Поддерживаемые значения type
Во время исследования подтверждены два допустимых значения
```
classic
```
и
```
heikin-ashi
```
Других поддерживаемых вариантов обнаружено не было.
---
# Исследование classic
Подписка
```json
{
"payload":{
"symbols":[
"BTC/USD_LEVERAGE"
],
"intervals":[
"1m"
],
"type":"classic"
}
}
```
События поступают регулярно
после закрытия каждой минутной свечи.
Пример
```json
{
"destination":"ohlc.event",
"payload":{
"symbol":"BTC/USD_LEVERAGE",
"interval":"1m",
"type":"classic",
"t":1784224860000,
"o":64047.45,
"h":64099.10,
"l":64042.25,
"c":64098.00
}
}
```
---
# Сравнение с REST
Для той же свечи был выполнен запрос
```
GET /api/v1/klines
```
```
priceType=bid
```
Получен ответ
```json
[
[
1784224860000,
"64047.45",
"64099.10",
"64042.25",
"64098.00",
165
]
]
```
Полное сравнение
| Поле | REST | WebSocket |
|------|------|-----------|
| Open Time | ✔ | ✔ |
| Open | ✔ | ✔ |
| High | ✔ | ✔ |
| Low | ✔ | ✔ |
| Close | ✔ | ✔ |
| Volume | ✔ | ✖ |
OHLC совпадают полностью.
---
# Вывод
Runtime подтвердил,
что
```
classic
```
представляет собой классические рыночные свечи,
идентичные
```
REST /klines
priceType=bid
```
за исключением отсутствующего объёма.
---
# Исследование Heikin-Ashi
Подписка
```json
{
"payload":{
"symbols":[
"BTC/USD_LEVERAGE"
],
"intervals":[
"1m"
],
"type":"heikin-ashi"
}
}
```
Получен ACK
```json
{
"subscriptions":{
"BTC/USD_LEVERAGE:1m":"PROCESSED"
}
}
```
После этого сервер начал публиковать события
```json
{
"destination":"ohlc.event",
"payload":{
"symbol":"BTC/USD_LEVERAGE",
"interval":"1m",
"type":"heikin-ashi",
"t":1784225280000,
"o":64068.18,
"h":64128.80,
"l":64068.18,
"c":64100.85
}
}
```
Получены несколько последовательных свечей.
---
# Вывод по Heikin-Ashi
Контракт полностью совпадает
с форматом
```
classic
```
Изменяется только способ расчёта OHLC.
Следовательно
```
classic
```
и
```
heikin-ashi
```
представляют две различные серии свечей,
имеющие одинаковый транспортный контракт.
---
# Исследование времени публикации
Во время наблюдения было подтверждено,
что событие публикуется только после завершения интервала.
Например
```
t = 1784224860000
```
соответствует времени открытия свечи.
Само событие было получено примерно через одну минуту,
после её закрытия.
Таким образом
```
t
```
является временем открытия свечи,
а не временем публикации события.
---
# Исследование нескольких интервалов
Одновременно были подписаны
```
1m
```
и
```
5m
```
После закрытия пятиминутного интервала
сервер практически одновременно прислал
```
1m
```
и
```
5m
```
события.
Полученное пятиминутное событие
```json
{
"payload":{
"interval":"5m",
"type":"classic",
"t":1784225400000,
"o":64130.80,
"h":64184.70,
"l":64118.20,
"c":64151.00
}
}
```
Тем самым подтверждено,
что сервер самостоятельно агрегирует свечи
для всех запрошенных интервалов.
Клиенту не требуется самостоятельно строить
5m, 15m или другие свечи
из минутных данных.
---
# Сравнение REST и WebSocket
Во время исследования одновременно выполнялись:
- периодические запросы REST;
- постоянная подписка WebSocket.
Это позволило определить различия поведения.
---
# Поведение REST
Запрос
```
GET /api/v1/klines
```
возвращает последние свечи.
Последняя свеча является текущей,
поэтому её значения изменяются
по мере поступления новых сделок.
Например
21:01:41
```text
Open 64047.45
High 64074.90
Low 64042.25
Close 64074.90
Volume 111
```
Через некоторое время
21:01:56
```text
High 64095.45
Close 64095.45
Volume 153
```
Ещё позже
21:02:11
```text
High 64099.10
Close 64098.00
Volume 165
```
Таким образом REST показывает
формирующуюся свечу.
---
# Поведение WebSocket
Для этой же свечи был получен
```json
{
"t":1784224860000,
"o":64047.45,
"h":64099.10,
"l":64042.25,
"c":64098.00
}
```
WebSocket опубликовал событие
только после окончания интервала.
Таким образом опубликована
уже окончательная свеча.
---
# Главное различие
REST
```
может вернуть
ещё незавершённую свечу
```
WebSocket
```
публикует только
закрытую свечу
```
Именно это делает WebSocket
очень полезным источником событий.
---
# Исследование Volume
Во всех событиях WebSocket отсутствует
```
volume
```
Например
```json
{
"payload":{
"o":64096.20,
"h":64110.60,
"l":64045.45,
"c":64052.50
}
}
```
Поле
```
volume
```
не передаётся.
Однако REST возвращает
```text
389
```
для этой же свечи.
Следовательно
WebSocket публикует
```
OHLC
```
но не
```
OHLCV
```
---
# Ограничения использования
Текущая каноническая модель проекта
```
Candle
```
содержит
```python
volume: Decimal
```
Следовательно WebSocket-событие
не может напрямую быть преобразовано
в канонический Candle.
Заполнение
```
volume = 0
```
является архитектурно неверным,
поскольку означает
"объём равен нулю",
хотя на самом деле
объём неизвестен.
Поэтому WebSocket не должен
непосредственно создавать
канонический Candle.
---
# Архитектурное решение
На основании проведённого исследования
для Build 059 принято решение
использовать гибридную схему.
```
OHLCMarketData.subscribe
получено событие закрытия свечи
REST GET /klines
(symbol + interval + open_time)
получена окончательная OHLCV свеча
Validation
Parser
Mapper
Canonical Candle
```
Таким образом
WebSocket становится
```
источником события
о закрытии свечи
```
а REST
остаётся
```
авторитетным источником
полной OHLCV модели.
```
---
# Новая транспортная модель
Для Build 059 рекомендуется
создать отдельную transport-модель
```
DzengiWebSocketOhlcEvent
```
например
```python
@dataclass(frozen=True, slots=True)
class DzengiWebSocketOhlcEvent:
symbol: str
interval: str
candle_type: str
open_time: int
open_price: DzengiRawNumeric
high_price: DzengiRawNumeric
low_price: DzengiRawNumeric
close_price: DzengiRawNumeric
```
Она не должна заменять
каноническую модель Candle.
Её назначение —
представление транспортного
WebSocket-события.
---
# Итоги исследования
Подтверждено
✓ WebSocket полностью работоспособен.
✓ Поддерживает несколько инструментов.
✓ Поддерживает несколько интервалов.
✓ Поддерживает
```
classic
```
и
```
heikin-ashi
```
✓ Публикует только завершённые свечи.
✓ Формат classic совпадает
с REST bid OHLC.
✓ Передаёт только OHLC.
Не содержит Volume.
✓ Может использоваться
как триггер появления
новой закрытой свечи.
---
# Вывод Build 058
Исследование показало,
что
```
OHLCMarketData.subscribe
```
не является заменой REST Candles Feed.
Однако данный поток является
идеальным механизмом
для своевременного обнаружения
момента закрытия новой свечи.
Именно такая схема
будет реализована
в следующем этапе проекта.
---
# Следующий Build
```
Build 059
Интеграция WebSocket OHLC
в Candles Feed
```
Основные задачи Build 059
- создать транспортную модель
DzengiWebSocketOhlcEvent;
- реализовать schema validation;
- реализовать parser;
- реализовать value validation;
- реализовать mapper;
- добавить WebSocket Adapter;
- интегрировать новый источник
в Candles Feed;
- использовать WebSocket
как источник события
закрытия свечи;
- использовать REST
для получения окончательной
OHLCV свечи;
- сохранить существующую
каноническую модель Candle
без изменений.
---
# Работа с диагностическим скриптом
В рамках Build 058 был разработан отдельный диагностический инструмент
```
scripts/check_ohlc_websocket.py
```
Назначение скрипта:
- проверка доступности WebSocket;
- проверка подписки OHLCMarketData.subscribe;
- получение ACK;
- получение реальных OHLC-событий;
- проверка нескольких символов;
- проверка нескольких интервалов;
- проверка типа свечей;
- исследование поведения API после изменений на стороне биржи.
Скрипт не является частью Production-кода
и предназначен исключительно
для инженерной диагностики.
---
# Проверка синтаксиса
```bash
python -m compileall \
scripts/check_ohlc_websocket.py
```
---
# Проверка оформления
```bash
git diff --check
```
---
# Запуск
```bash
EXCHANGE_BASE_URL=https://api-adapter.dzengi.com \
EXCHANGE_WS_URL=wss://api-adapter.dzengi.com \
EXCHANGE_API_KEY="" \
PYTHONPATH=. \
python scripts/check_ohlc_websocket.py
```
---
# Настраиваемые параметры
В начале файла можно изменять
```python
SYMBOLS = (
"BTC/USD_LEVERAGE",
)
```
для исследования нескольких инструментов.
Например
```python
SYMBOLS = (
"BTC/USD_LEVERAGE",
"ETH/USD_LEVERAGE",
)
```
---
Можно изменять интервалы
```python
INTERVALS = (
"1m",
)
```
например
```python
INTERVALS = (
"1m",
"5m",
)
```
---
Можно выбирать тип свечей
```python
CANDLE_TYPE = "classic"
```
или
```python
CANDLE_TYPE = "heikin-ashi"
```
---
Также можно изменять
```python
MAX_MESSAGES
```
и
```python
TOTAL_TIMEOUT_SECONDS
```
для длительного наблюдения.
---
# Что выводит скрипт
После запуска отображаются
- URL подключения;
- список инструментов;
- интервалы;
- тип свечей;
- параметры исследования.
Затем выводится отправляемый JSON подписки.
После подключения отображается ACK.
Например
```json
{
"subscriptions": {
"BTC/USD_LEVERAGE:1m": "PROCESSED"
}
}
```
После этого выводятся все события
```
ohlc.event
```
с указанием
- номера сообщения;
- времени от начала подключения;
- полного JSON.
---
# Проверка нескольких интервалов
При использовании
```python
INTERVALS = (
"1m",
"5m",
)
```
ожидается ACK
```json
{
"subscriptions": {
"BTC/USD_LEVERAGE:1m": "PROCESSED",
"BTC/USD_LEVERAGE:5m": "PROCESSED"
}
}
```
и последующее получение событий
```text
interval = 1m
```
и
```text
interval = 5m
```
в одном WebSocket-соединении.
---
# Проверка Heikin-Ashi
Для исследования синтетических свечей достаточно изменить
```python
CANDLE_TYPE = "heikin-ashi"
```
После этого ожидаются события
```json
{
"type":"heikin-ashi"
}
```
Контракт полностью совпадает
с классическими свечами,
однако значения OHLC рассчитываются
по алгоритму Heikin-Ashi.
---
# Использование в дальнейшем
Скрипт рекомендуется сохранять
в репозитории проекта.
Он может использоваться
- после изменений API Dzengi;
- после обновления WebSocket;
- при регрессионном тестировании;
- при диагностике Production;
- при разработке новых адаптеров;
- при интеграции новых таймфреймов.
Наличие отдельного диагностического инструмента
значительно упрощает проверку реального контракта биржи
без запуска всего приложения Dzentra.
---
# Итог Build 058
Build 058 полностью подтвердил
контракт WebSocket OHLCMarketData.subscribe
на реальном Production API Dzengi.
Полученные результаты являются основанием
для перехода к следующему этапу проекта —
```
Build 059
Интеграция WebSocket OHLC
в канонический Candles Feed.
```