feat: add market data architecture and complete migration through build 039

This commit is contained in:
2026-07-14 09:58:16 +03:00
parent 26deb861bc
commit a996f2f797
443 changed files with 80452 additions and 1335 deletions

View File

@@ -0,0 +1,594 @@
# 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-механизмов и с сохранением работоспособности существующего бота до завершения последующих этапов миграции.