# Build 034 — Dzengi WebSocket quote parsing и адаптер

**Статус:** Завершён  
**Подсистема:** Market Data  
**Контур:** Market Data Acquisition / Quotes Feed  
**Проект:** Dzentra  
**Язык документации:** Русский  

---

## 1. Цель Build

Цель Build 034 — создать специализированный контур обработки WebSocket-сообщений котировок Dzengi и преобразования их в каноническую модель `Quote`.

Build должен был изолировать знание транспортных форматов Dzengi WebSocket от канонического слоя Market Data и подготовить архитектурную основу для последующего перевода market runtime на новый Quotes Feed.

В рамках Build реализована цепочка:

```text
Dzengi WebSocket message
        ↓
schema validation
        ↓
parser
        ↓
DzengiWebSocketQuoteResponse
        ↓
value validation
        ↓
mapper
        ↓
DzengiWebSocketQuoteAdapter
        ↓
canonical Quote
```

---

## 2. Архитектурный результат

После Build 034 обработка WebSocket-котировок Dzengi получила специализированный адаптерный контур внутри:

```text
src/market_data/acquisition/
```

Транспортные особенности Dzengi WebSocket больше не должны распространяться на каноническую модель `Quote` и будущих потребителей Quotes Feed.

Архитектурная граница имеет следующий вид:

```text
Dzengi-specific transport formats
        ↓
adapters/dzengi
        ↓
canonical Quote
        ↓
Quotes Feed / Quote Store / consumers
```

Каноническая модель:

```text
src/market_data/acquisition/models/quote.py
```

остаётся независимой от:

```text
payload
Payload
symbolName
bid
ask
ofr
bids
asks
price
p
bidPrice
askPrice
```

Эти имена являются особенностями внешнего транспорта Dzengi и обрабатываются внутри адаптерного слоя.

---

## 3. Изменённые файлы

В рамках Build 034 изменены следующие файлы:

```text
src/market_data/acquisition/adapters/dzengi/models.py
src/market_data/acquisition/adapters/dzengi/parser.py
src/market_data/acquisition/adapters/dzengi/mapper.py
src/market_data/acquisition/validation/schema.py
src/market_data/acquisition/validation/values.py
```

Добавлен специализированный WebSocket-адаптер:

```text
src/market_data/acquisition/adapters/dzengi/websocket.py
```

---

## 4. Добавленные тесты

Добавлены следующие специализированные тестовые файлы:

```text
tests/unit/market_data/acquisition/validation/test_websocket_quote_schema.py
tests/unit/market_data/acquisition/validation/test_websocket_quote_values.py
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_parser.py
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_mapper.py
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_adapter.py
```

---

## 5. Поддерживаемые WebSocket-форматы

Новый адаптерный контур поддерживает транспортные варианты, существовавшие в legacy-реализации Dzengi WebSocket.

### 5.1. Оболочки сообщения

Поддерживаются:

```text
payload
Payload
```

а также вложенная двойная оболочка.

Примеры допустимой структуры:

```json
{
  "payload": {
    "symbolName": "BTC/USD_LEVERAGE",
    "bid": "64159.45",
    "ask": "64159.55"
  }
}
```

и:

```json
{
  "Payload": {
    "Payload": {
      "symbolName": "BTC/USD_LEVERAGE",
      "bids": [
        ["64159.45", "1.0"]
      ],
      "asks": [
        ["64159.55", "1.0"]
      ]
    }
  }
}
```

---

## 6. Поддерживаемые поля символа

Адаптер поддерживает следующие транспортные имена символа:

```text
symbolName
symbol
```

После обработки внешнее представление преобразуется в каноническое поле:

```text
Quote.symbol
```

---

## 7. Поддерживаемые представления bid и ask

Поддерживаются прямые поля:

```text
bid
ask
```

вариант Dzengi:

```text
bid
ofr
```

а также depth-представление:

```text
bids
asks
```

Для элементов depth поддерживаются представления в виде:

```text
list
dict
```

Поддерживаемые имена поля цены внутри depth-элементов:

```text
price
p
bidPrice
askPrice
```

---

## 8. Семантика last_price для depth-сообщений

Для WebSocket depth-сообщений, содержащих лучшие цены bid и ask, сохранена legacy-семантика:

```text
last_price = midpoint(best_bid, best_ask)
```

То есть каноническое значение `Quote.last_price` определяется как середина между лучшей ценой покупки и лучшей ценой продажи.

Это решение сохраняет обратную совместимость с существующим поведением market runtime до его последующего архитектурного перевода.

---

## 9. Обработка timestamp

WebSocket timestamp является необязательным.

Если транспортное сообщение содержит допустимый timestamp биржи, он преобразуется в:

```text
Quote.exchange_timestamp
```

Если timestamp отсутствует, каноническая модель допускает:

```text
exchange_timestamp = None
```

Время фактического получения и обработки котировки фиксируется отдельно:

```text
Quote.received_at
```

Таким образом, сохраняется разделение двух временных характеристик:

```text
exchange_timestamp
    время события по данным биржи

received_at
    время получения котировки системой Dzentra
```

---

## 10. Schema validation

Schema validation отвечает исключительно за структурную корректность WebSocket-документа.

На этом этапе проверяется возможность извлечения необходимых частей сообщения без переноса бизнес-логики в транспортный слой.

Schema validation не должна:

```text
создавать canonical Quote
выполнять mapping
управлять runtime
записывать данные в Quote Store
обращаться к MarketPriceCache
```

---

## 11. Parser

Parser преобразует структурно проверенный WebSocket-документ в специализированную raw-модель Dzengi:

```text
DzengiWebSocketQuoteResponse
```

Parser сохраняет границу между:

```text
сырой внешний документ
```

и:

```text
типизированное транспортное представление Dzengi
```

Parser не создаёт канонический `Quote`.

---

## 12. Value validation

Value validation проверяет семантическую допустимость извлечённых значений.

В частности, контур должен обеспечивать корректность значений, необходимых для построения канонической котировки:

```text
symbol
bid price
ask price
timestamp, если присутствует
```

Проверка значений выполняется до mapping в каноническую модель.

---

## 13. Mapper

Mapper преобразует проверенную raw-модель Dzengi WebSocket в:

```text
Quote
```

На этой границе происходит переход:

```text
Dzengi-specific representation
        ↓
canonical Dzentra representation
```

После mapping потребитель не должен зависеть от исходного формата WebSocket-сообщения.

---

## 14. DzengiWebSocketQuoteAdapter

Специализированный адаптер инкапсулирует полный конвейер обработки одного WebSocket-сообщения:

```text
raw document
    ↓
schema validation
    ↓
parsing
    ↓
value validation
    ↓
mapping
    ↓
Quote
```

Результатом успешной обработки является канонический объект:

```text
Quote
```

Адаптер не отвечает за:

```text
поддержание WebSocket-соединения
reconnect
runtime lifecycle
регистрацию market runtime
запись в Quote Store
legacy MarketPriceCache facade
```

Эти обязанности принадлежат другим архитектурным слоям.

---

## 15. Что намеренно не изменялось

В Build 034 не изменялись runtime-файлы:

```text
src/integrations/exchange/ws_client.py
src/integrations/exchange/market_stream.py
src/integrations/exchange/market_data_runner.py
```

Также Build 034 не выполнял переключение:

```text
market runtime → Quotes Feed
```

и не удалял legacy-механизмы.

Это принципиальная граница Build.

Build 034 создаёт новый специализированный адаптерный контур, но не переключает на него существующий runtime.

Перевод runtime предусмотрен следующим этапом:

```text
Build 035 — Перевод market runtime на Quotes Feed
```

---

## 16. Обратная совместимость

В Build 034 сохранены существующие транспортные варианты legacy WebSocket-контура:

```text
payload / Payload
двойная оболочка
symbolName / symbol
bid + ask
bid + ofr
bids + asks
depth item list
depth item dict
price / p / bidPrice / askPrice
необязательный timestamp
```

Для depth-сообщений сохранено существующее правило:

```text
last_price = midpoint(best_bid, best_ask)
```

Таким образом, Build не требует одномоментного удаления legacy runtime и подготавливает безопасный переход к новой архитектуре.

---

## 17. Проверка компиляции

Выполнена проверка:

```bash
python -m py_compile \
  src/market_data/acquisition/adapters/dzengi/models.py \
  src/market_data/acquisition/adapters/dzengi/parser.py \
  src/market_data/acquisition/adapters/dzengi/mapper.py \
  src/market_data/acquisition/adapters/dzengi/websocket.py \
  src/market_data/acquisition/validation/schema.py \
  src/market_data/acquisition/validation/values.py \
  tests/unit/market_data/acquisition/validation/test_websocket_quote_schema.py \
  tests/unit/market_data/acquisition/validation/test_websocket_quote_values.py \
  tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_parser.py \
  tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_mapper.py \
  tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_adapter.py
```

Результат:

```text
успешно
```

---

## 18. Специализированные тесты

Выполнена команда:

```bash
python -m pytest \
  tests/unit/market_data/acquisition/validation/test_websocket_quote_schema.py \
  tests/unit/market_data/acquisition/validation/test_websocket_quote_values.py \
  tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_parser.py \
  tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_mapper.py \
  tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_adapter.py \
  -q
```

Результат:

```text
24 passed in 0.04s
```

---

## 19. Полная регрессия

Выполнена команда:

```bash
python -m pytest -q
```

Результат:

```text
602 passed in 0.30s
```

Количество тестов до Build 034:

```text
578 passed
```

Количество тестов после Build 034:

```text
602 passed
```

Добавлено:

```text
24 теста
```

Полная регрессия подтверждает отсутствие обнаруженных регрессий в существующем коде проекта.

---

## 20. Итог Build

Build 034 завершён полностью.

Реализованы:

```text
специализированная WebSocket raw-модель Dzengi
WebSocket schema validation
WebSocket parser
WebSocket value validation
WebSocket mapper
DzengiWebSocketQuoteAdapter
преобразование WebSocket-сообщения в canonical Quote
поддержка legacy-вариантов формата Dzengi
24 специализированных теста
```

Не выполнялись:

```text
переключение market runtime
изменение ws_client.py
изменение market_stream.py
изменение market_data_runner.py
удаление legacy WebSocket parsing
удаление MarketPriceCache
```

Архитектурный результат:

```text
Dzengi WebSocket transport
        ↓
Dzengi-specific validation / parsing / mapping
        ↓
canonical Quote
```

---

## 21. Следующий Build

Следующий этап:

```text
Build 035 — Перевод market runtime на Quotes Feed
```

Его задача — подключить существующий market runtime к новому каноническому контуру котировок, используя созданные ранее:

```text
Quote
Quote Store
Quotes Feed
Dzengi REST Quotes Feed
Dzengi WebSocket quote adapter
```

При этом переход должен выполняться без преждевременного удаления legacy-механизмов и с сохранением работоспособности существующего бота до завершения последующих этапов миграции.