build 039: complete Quotes Feed migration foundation
This commit is contained in:
594
docs/migrations/build_034.md
Normal file
594
docs/migrations/build_034.md
Normal 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-механизмов и с сохранением работоспособности существующего бота до завершения последующих этапов миграции.
|
||||
Reference in New Issue
Block a user