Files
dzentra_bot/docs/migrations/build_034.md

15 KiB
Raw Blame History

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 реализована цепочка:

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

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

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

src/market_data/acquisition/

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

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

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

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

src/market_data/acquisition/models/quote.py

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

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

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


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

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

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-адаптер:

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

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

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

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. Оболочки сообщения

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

payload
Payload

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

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

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

и:

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

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

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

symbolName
symbol

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

Quote.symbol

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

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

bid
ask

вариант Dzengi:

bid
ofr

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

bids
asks

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

list
dict

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

price
p
bidPrice
askPrice

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

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

last_price = midpoint(best_bid, best_ask)

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

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


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

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

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

Quote.exchange_timestamp

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

exchange_timestamp = None

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

Quote.received_at

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

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

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

10. Schema validation

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

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

Schema validation не должна:

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

11. Parser

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

DzengiWebSocketQuoteResponse

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

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

и:

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

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


12. Value validation

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

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

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

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


13. Mapper

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

Quote

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

Dzengi-specific representation
        ↓
canonical Dzentra representation

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


14. DzengiWebSocketQuoteAdapter

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

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

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

Quote

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

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

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


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

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

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

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

market runtime → Quotes Feed

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

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

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

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

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

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

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

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

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

last_price = midpoint(best_bid, best_ask)

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


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

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

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

Результат:

успешно

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

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

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

Результат:

24 passed in 0.04s

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

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

python -m pytest -q

Результат:

602 passed in 0.30s

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

578 passed

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

602 passed

Добавлено:

24 теста

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


20. Итог Build

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

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

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

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

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

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

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

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

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

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

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

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

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