# 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.
```