Build 060.15 — Unified WebSocket Routing
This commit is contained in:
@@ -12,14 +12,19 @@ from src.market_data.acquisition.adapters.dzengi.parser import (
|
|||||||
parse_dzengi_websocket_ohlc,
|
parse_dzengi_websocket_ohlc,
|
||||||
parse_dzengi_websocket_quote,
|
parse_dzengi_websocket_quote,
|
||||||
)
|
)
|
||||||
|
from src.market_data.acquisition.adapters.dzengi.websocket_trade_adapter import (
|
||||||
|
adapt_websocket_trade_document,
|
||||||
|
)
|
||||||
from src.market_data.acquisition.exceptions import (
|
from src.market_data.acquisition.exceptions import (
|
||||||
WebSocketMessageRoutingError,
|
WebSocketMessageRoutingError,
|
||||||
)
|
)
|
||||||
from src.market_data.acquisition.models.candle_close import CandleCloseEvent
|
from src.market_data.acquisition.models.candle_close import CandleCloseEvent
|
||||||
from src.market_data.acquisition.models.quote import Quote
|
from src.market_data.acquisition.models.quote import Quote
|
||||||
|
from src.market_data.acquisition.models.trade import Trade
|
||||||
from src.market_data.acquisition.validation.schema import (
|
from src.market_data.acquisition.validation.schema import (
|
||||||
validate_dzengi_websocket_ohlc_schema,
|
validate_dzengi_websocket_ohlc_schema,
|
||||||
validate_dzengi_websocket_quote_schema,
|
validate_dzengi_websocket_quote_schema,
|
||||||
|
validate_dzengi_websocket_trade_schema,
|
||||||
)
|
)
|
||||||
from src.market_data.acquisition.validation.values import (
|
from src.market_data.acquisition.validation.values import (
|
||||||
validate_dzengi_websocket_ohlc_values,
|
validate_dzengi_websocket_ohlc_values,
|
||||||
@@ -64,6 +69,22 @@ class DzengiWebSocketOhlcAdapter:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Преобразует одно декодированное сообщение Dzengi WebSocket
|
||||||
|
# internal.trade в каноническую модель Trade.
|
||||||
|
class DzengiWebSocketTradeAdapter:
|
||||||
|
def map_message(
|
||||||
|
self,
|
||||||
|
document: object,
|
||||||
|
) -> Trade:
|
||||||
|
validated = validate_dzengi_websocket_trade_schema(
|
||||||
|
document,
|
||||||
|
)
|
||||||
|
|
||||||
|
return adapt_websocket_trade_document(
|
||||||
|
validated,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Определяет тип декодированного сообщения Dzengi WebSocket
|
# Определяет тип декодированного сообщения Dzengi WebSocket
|
||||||
# и передаёт его соответствующему специализированному адаптеру.
|
# и передаёт его соответствующему специализированному адаптеру.
|
||||||
class DzengiUnifiedWebSocketAdapter:
|
class DzengiUnifiedWebSocketAdapter:
|
||||||
@@ -72,27 +93,47 @@ class DzengiUnifiedWebSocketAdapter:
|
|||||||
*,
|
*,
|
||||||
quote_adapter: DzengiWebSocketQuoteAdapter | None = None,
|
quote_adapter: DzengiWebSocketQuoteAdapter | None = None,
|
||||||
ohlc_adapter: DzengiWebSocketOhlcAdapter | None = None,
|
ohlc_adapter: DzengiWebSocketOhlcAdapter | None = None,
|
||||||
|
trade_adapter: DzengiWebSocketTradeAdapter | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._quote_adapter = quote_adapter or DzengiWebSocketQuoteAdapter()
|
self._quote_adapter = (
|
||||||
self._ohlc_adapter = ohlc_adapter or DzengiWebSocketOhlcAdapter()
|
quote_adapter
|
||||||
|
or DzengiWebSocketQuoteAdapter()
|
||||||
|
)
|
||||||
|
|
||||||
|
self._ohlc_adapter = (
|
||||||
|
ohlc_adapter
|
||||||
|
or DzengiWebSocketOhlcAdapter()
|
||||||
|
)
|
||||||
|
|
||||||
|
self._trade_adapter = (
|
||||||
|
trade_adapter
|
||||||
|
or DzengiWebSocketTradeAdapter()
|
||||||
|
)
|
||||||
|
|
||||||
def map_message(
|
def map_message(
|
||||||
self,
|
self,
|
||||||
document: object,
|
document: object,
|
||||||
*,
|
*,
|
||||||
received_at: datetime | None = None,
|
received_at: datetime | None = None,
|
||||||
) -> Quote | CandleCloseEvent:
|
) -> Quote | CandleCloseEvent | Trade:
|
||||||
if not isinstance(document, dict):
|
if not isinstance(document, dict):
|
||||||
raise WebSocketMessageRoutingError(
|
raise WebSocketMessageRoutingError(
|
||||||
"Сообщение Dzengi WebSocket должно быть объектом."
|
"Сообщение Dzengi WebSocket должно быть объектом."
|
||||||
)
|
)
|
||||||
|
|
||||||
if document.get("destination") == "ohlc.event":
|
destination = document.get("destination")
|
||||||
|
|
||||||
|
if destination == "ohlc.event":
|
||||||
return self._ohlc_adapter.map_message(
|
return self._ohlc_adapter.map_message(
|
||||||
document,
|
document,
|
||||||
received_at=received_at,
|
received_at=received_at,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if destination == "internal.trade":
|
||||||
|
return self._trade_adapter.map_message(
|
||||||
|
document,
|
||||||
|
)
|
||||||
|
|
||||||
if "Payload" in document:
|
if "Payload" in document:
|
||||||
return self._quote_adapter.map_message(
|
return self._quote_adapter.map_message(
|
||||||
document,
|
document,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from src.market_data.acquisition.exceptions import (
|
|||||||
)
|
)
|
||||||
from src.market_data.acquisition.models.candle_close import CandleCloseEvent
|
from src.market_data.acquisition.models.candle_close import CandleCloseEvent
|
||||||
from src.market_data.acquisition.models.quote import Quote
|
from src.market_data.acquisition.models.quote import Quote
|
||||||
|
from src.market_data.acquisition.models.trade import Trade
|
||||||
|
|
||||||
|
|
||||||
def test_adapter_routes_quote_message() -> None:
|
def test_adapter_routes_quote_message() -> None:
|
||||||
@@ -57,6 +58,30 @@ def test_adapter_routes_ohlc_message() -> None:
|
|||||||
assert str(result.close_price) == "103"
|
assert str(result.close_price) == "103"
|
||||||
|
|
||||||
|
|
||||||
|
def test_adapter_routes_trade_message() -> None:
|
||||||
|
result = DzengiUnifiedWebSocketAdapter().map_message(
|
||||||
|
{
|
||||||
|
"status": "OK",
|
||||||
|
"destination": "internal.trade",
|
||||||
|
"payload": {
|
||||||
|
"id": 123456,
|
||||||
|
"price": "105.50",
|
||||||
|
"size": "0.25",
|
||||||
|
"ts": 1752364800123,
|
||||||
|
"symbol": "BTC/USD",
|
||||||
|
"buyer": True,
|
||||||
|
"orderId": "order-123",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(result, Trade)
|
||||||
|
assert result.symbol == "BTC/USD"
|
||||||
|
assert result.trade_id == 123456
|
||||||
|
assert str(result.price) == "105.50"
|
||||||
|
assert str(result.quantity) == "0.25"
|
||||||
|
|
||||||
|
|
||||||
def test_adapter_passes_received_at_to_quote_adapter() -> None:
|
def test_adapter_passes_received_at_to_quote_adapter() -> None:
|
||||||
received_at = datetime(2026, 7, 17, tzinfo=timezone.utc)
|
received_at = datetime(2026, 7, 17, tzinfo=timezone.utc)
|
||||||
|
|
||||||
|
|||||||
926
docs/migrations/build_060_15.md
Normal file
926
docs/migrations/build_060_15.md
Normal file
@@ -0,0 +1,926 @@
|
|||||||
|
# Build 060.15 — Unified WebSocket Routing
|
||||||
|
|
||||||
|
**Engineering Migration Report**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Контроль документа
|
||||||
|
|
||||||
|
| Свойство | Значение |
|
||||||
|
|----------|----------|
|
||||||
|
| Build | 060.15 |
|
||||||
|
| Название | Unified WebSocket Routing |
|
||||||
|
| Статус | Completed |
|
||||||
|
| Проект | Dzentra |
|
||||||
|
| Подсистема | Market Data Acquisition |
|
||||||
|
| Компонент | Trades Feed |
|
||||||
|
| Версия | 1.0 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Цель Build
|
||||||
|
|
||||||
|
После завершения Build 060.14 система получила полностью реализованный уровень **WebSocket Trade Adapter**.
|
||||||
|
|
||||||
|
К этому моменту Pipeline обработки WebSocket Trade уже гарантировал:
|
||||||
|
|
||||||
|
- корректность структуры транспортного документа;
|
||||||
|
- успешное построение транспортной модели;
|
||||||
|
- корректность всех обязательных значений;
|
||||||
|
- успешное преобразование транспортной модели в каноническую модель предметной области;
|
||||||
|
- наличие единой точки входа транспортного Pipeline.
|
||||||
|
|
||||||
|
Однако полученный Adapter ещё не был встроен в существующую инфраструктуру получения WebSocket-сообщений.
|
||||||
|
|
||||||
|
Архитектура Dzentra уже содержала единый компонент маршрутизации входящих сообщений —
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiUnifiedWebSocketAdapter
|
||||||
|
```
|
||||||
|
|
||||||
|
который определял тип входящего сообщения и направлял его в соответствующий специализированный Adapter.
|
||||||
|
|
||||||
|
До начала Build поддерживались только два типа сообщений:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Quote
|
||||||
|
```
|
||||||
|
|
||||||
|
и
|
||||||
|
|
||||||
|
```text
|
||||||
|
OHLC
|
||||||
|
```
|
||||||
|
|
||||||
|
Поддержка сообщений
|
||||||
|
|
||||||
|
```text
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
отсутствовала.
|
||||||
|
|
||||||
|
В результате Trade Pipeline существовал как полностью реализованный, но оставался недоступным для общего механизма маршрутизации WebSocket.
|
||||||
|
|
||||||
|
Основная задача Build заключается в расширении существующего Unified WebSocket Routing без изменения ранее реализованной архитектуры.
|
||||||
|
|
||||||
|
После завершения Build компонент
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiUnifiedWebSocketAdapter
|
||||||
|
```
|
||||||
|
|
||||||
|
должен уметь автоматически определять сообщения
|
||||||
|
|
||||||
|
```text
|
||||||
|
internal.trade
|
||||||
|
```
|
||||||
|
|
||||||
|
и передавать их в специализированный Adapter обработки Trade.
|
||||||
|
|
||||||
|
При этом Build не затрагивает:
|
||||||
|
|
||||||
|
- WebSocket Runtime;
|
||||||
|
- транспортный протокол;
|
||||||
|
- Schema Validation;
|
||||||
|
- Parser;
|
||||||
|
- Value Validation;
|
||||||
|
- Mapper;
|
||||||
|
- внутреннюю архитектуру Trade Adapter;
|
||||||
|
- Trades Feed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Предпосылки
|
||||||
|
|
||||||
|
К началу Build архитектура WebSocket уже содержала единый механизм маршрутизации сообщений.
|
||||||
|
|
||||||
|
Он обеспечивал разделение различных типов транспортных событий между специализированными Adapter.
|
||||||
|
|
||||||
|
Конвейер обработки выглядел следующим образом.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Raw WebSocket Message
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DzengiUnifiedWebSocketAdapter
|
||||||
|
│
|
||||||
|
├────────► Quote Adapter
|
||||||
|
│
|
||||||
|
└────────► OHLC Adapter
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый специализированный Adapter полностью инкапсулировал собственный транспортный Pipeline.
|
||||||
|
|
||||||
|
После завершения Build 060.14 аналогичный Pipeline уже существовал и для Trade.
|
||||||
|
|
||||||
|
```text
|
||||||
|
ValidatedWebSocketTradeDocument
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
WebSocket Trade Adapter
|
||||||
|
│
|
||||||
|
├────────► Parser
|
||||||
|
│
|
||||||
|
├────────► Value Validation
|
||||||
|
│
|
||||||
|
├────────► Mapper
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
Однако данный Adapter не был подключён к общему механизму маршрутизации.
|
||||||
|
|
||||||
|
В результате система не могла автоматически обрабатывать входящие сообщения о сделках, несмотря на полностью реализованный транспортный Pipeline.
|
||||||
|
|
||||||
|
Таким образом архитектура Unified WebSocket Routing оставалась незавершённой.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Архитектурное основание
|
||||||
|
|
||||||
|
Одним из фундаментальных принципов архитектуры Dzentra является централизованная маршрутизация транспортных сообщений.
|
||||||
|
|
||||||
|
Определение типа входящего сообщения должно происходить исключительно в одном месте системы.
|
||||||
|
|
||||||
|
Именно эту задачу выполняет
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiUnifiedWebSocketAdapter
|
||||||
|
```
|
||||||
|
|
||||||
|
После определения типа события дальнейшая обработка полностью передаётся специализированному Adapter соответствующего типа данных.
|
||||||
|
|
||||||
|
Полная схема обработки WebSocket-сообщений принимает следующий вид.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Raw WebSocket Message
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Unified Routing
|
||||||
|
│
|
||||||
|
├────────► Quote Adapter
|
||||||
|
│
|
||||||
|
├────────► OHLC Adapter
|
||||||
|
│
|
||||||
|
└────────► Trade Adapter
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый специализированный Adapter полностью инкапсулирует собственный транспортный Pipeline.
|
||||||
|
|
||||||
|
Unified Router принципиально **не выполняет**:
|
||||||
|
|
||||||
|
- Schema Validation;
|
||||||
|
- Parser;
|
||||||
|
- Value Validation;
|
||||||
|
- Mapper;
|
||||||
|
- бизнес-логику;
|
||||||
|
- принятие торговых решений;
|
||||||
|
- обработку Runtime.
|
||||||
|
|
||||||
|
Его единственная ответственность — определить тип входящего сообщения и передать его соответствующему Adapter.
|
||||||
|
|
||||||
|
Подобное разделение ответственности обеспечивает:
|
||||||
|
|
||||||
|
- единое место маршрутизации всех WebSocket-событий;
|
||||||
|
- независимое развитие отдельных Pipeline;
|
||||||
|
- повторное использование существующих Adapter;
|
||||||
|
- отсутствие дублирования логики определения типа сообщений.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Результаты архитектурного аудита
|
||||||
|
|
||||||
|
Перед реализацией Build был выполнен аудит существующей архитектуры WebSocket Routing.
|
||||||
|
|
||||||
|
В ходе анализа подтверждено наличие полностью реализованного компонента
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiUnifiedWebSocketAdapter
|
||||||
|
```
|
||||||
|
|
||||||
|
который уже обеспечивал маршрутизацию сообщений Quote и OHLC.
|
||||||
|
|
||||||
|
Также был выполнен аудит транспортного уровня Runtime.
|
||||||
|
|
||||||
|
Анализ подтвердил, что
|
||||||
|
|
||||||
|
```text
|
||||||
|
runtime/websocket_protocol.py
|
||||||
|
```
|
||||||
|
|
||||||
|
не содержит логики определения типов сообщений и не должен выполнять:
|
||||||
|
|
||||||
|
- Schema Validation;
|
||||||
|
- Parser;
|
||||||
|
- Value Validation;
|
||||||
|
- Mapper;
|
||||||
|
- транспортную маршрутизацию.
|
||||||
|
|
||||||
|
Данный принцип уже закреплён архитектурой подсистемы Market Data Acquisition.
|
||||||
|
|
||||||
|
Кроме того, аудит подтвердил полную готовность WebSocket Trade Adapter, реализованного в Build 060.14.
|
||||||
|
|
||||||
|
Таким образом единственным отсутствующим элементом архитектуры являлось подключение нового Adapter к существующему механизму Unified Routing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Архитектурное решение
|
||||||
|
|
||||||
|
По результатам проведённого аудита было принято решение сохранить существующую архитектуру маршрутизации без изменения её принципов.
|
||||||
|
|
||||||
|
В компонент
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiUnifiedWebSocketAdapter
|
||||||
|
```
|
||||||
|
|
||||||
|
добавлена поддержка нового специализированного Adapter.
|
||||||
|
|
||||||
|
После определения значения
|
||||||
|
|
||||||
|
```text
|
||||||
|
destination = "internal.trade"
|
||||||
|
```
|
||||||
|
|
||||||
|
маршрутизатор передаёт документ в
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiWebSocketTradeAdapter
|
||||||
|
```
|
||||||
|
|
||||||
|
который выполняет полный транспортный Pipeline обработки и возвращает готовую каноническую модель
|
||||||
|
|
||||||
|
```text
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
После завершения обработки вызывающая сторона получает результат в виде одной из трёх канонических моделей предметной области.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Quote
|
||||||
|
|
||||||
|
CandleCloseEvent
|
||||||
|
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
Конвейер обработки WebSocket принимает следующий вид.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Raw WebSocket Message
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DzengiUnifiedWebSocketAdapter
|
||||||
|
│
|
||||||
|
├────────► Quote Adapter
|
||||||
|
│ │
|
||||||
|
│ ▼
|
||||||
|
│ Quote
|
||||||
|
│
|
||||||
|
├────────► OHLC Adapter
|
||||||
|
│ │
|
||||||
|
│ ▼
|
||||||
|
│ CandleCloseEvent
|
||||||
|
│
|
||||||
|
└────────► Trade Adapter
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
Build 060.15 не изменяет архитектуру Runtime, не затрагивает ранее реализованные Pipeline и не изменяет внутреннюю структуру специализированных Adapter.
|
||||||
|
|
||||||
|
Он исключительно расширяет существующий механизм Unified Routing новым типом сообщений.
|
||||||
|
|
||||||
|
# Реализованный уровень Unified Routing
|
||||||
|
|
||||||
|
В файл
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/market_data/acquisition/adapters/dzengi/websocket.py
|
||||||
|
```
|
||||||
|
|
||||||
|
добавлен специализированный Adapter
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiWebSocketTradeAdapter
|
||||||
|
```
|
||||||
|
|
||||||
|
Новый Adapter становится частью существующей архитектуры маршрутизации WebSocket-сообщений и отвечает исключительно за подключение уже реализованного транспортного Pipeline Trade к Unified Router.
|
||||||
|
|
||||||
|
Во время обработки сообщения Adapter последовательно выполняет:
|
||||||
|
|
||||||
|
1. Schema Validation;
|
||||||
|
2. WebSocket Trade Adapter.
|
||||||
|
|
||||||
|
При этом полный транспортный Pipeline продолжает оставаться инкапсулированным внутри ранее реализованного Adapter Build 060.14.
|
||||||
|
|
||||||
|
После успешного завершения обработки вызывающая сторона получает готовую каноническую модель
|
||||||
|
|
||||||
|
```text
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
Промежуточные транспортные модели по-прежнему не покидают слой адаптеров.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Почему маршрутизация выполняется в Unified Adapter
|
||||||
|
|
||||||
|
Во время проектирования отдельно рассматривался вопрос о переносе логики определения типа сообщения в Runtime.
|
||||||
|
|
||||||
|
По результатам анализа данный вариант был отклонён.
|
||||||
|
|
||||||
|
Основные причины:
|
||||||
|
|
||||||
|
- Runtime не должен знать типы рыночных сообщений;
|
||||||
|
- Runtime не должен зависеть от конкретной биржи;
|
||||||
|
- Runtime не должен содержать транспортную маршрутизацию;
|
||||||
|
- вся логика определения типа сообщения уже сосредоточена в Unified Adapter.
|
||||||
|
|
||||||
|
Таким образом архитектурная ответственность остаётся неизменной.
|
||||||
|
|
||||||
|
Runtime отвечает исключительно за получение транспортных сообщений.
|
||||||
|
|
||||||
|
Unified Adapter отвечает исключительно за определение их типа.
|
||||||
|
|
||||||
|
Специализированные Adapter отвечают исключительно за преобразование данных.
|
||||||
|
|
||||||
|
Подобное разделение полностью соответствует базовым архитектурным принципам Dzentra.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Последовательность обработки
|
||||||
|
|
||||||
|
После завершения Build полный конвейер обработки WebSocket-сообщений принимает следующий вид.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Raw WebSocket Message
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DzengiUnifiedWebSocketAdapter
|
||||||
|
│
|
||||||
|
├────────► Quote Adapter
|
||||||
|
│ │
|
||||||
|
│ ▼
|
||||||
|
│ Quote
|
||||||
|
│
|
||||||
|
├────────► OHLC Adapter
|
||||||
|
│ │
|
||||||
|
│ ▼
|
||||||
|
│ CandleCloseEvent
|
||||||
|
│
|
||||||
|
└────────► Trade Adapter
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Schema Validation
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
WebSocket Trade Adapter
|
||||||
|
│
|
||||||
|
├────────► Parser
|
||||||
|
├────────► Value Validation
|
||||||
|
├────────► Mapper
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый уровень Pipeline сохраняет собственную область ответственности.
|
||||||
|
|
||||||
|
Ни один из существующих компонентов не изменяет своё назначение.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Использование существующих компонентов
|
||||||
|
|
||||||
|
Build 060.15 не вводит новых механизмов обработки транспортных данных.
|
||||||
|
|
||||||
|
Unified Router повторно использует уже существующую инфраструктуру проекта.
|
||||||
|
|
||||||
|
Для обработки Quote используются:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiWebSocketQuoteAdapter
|
||||||
|
```
|
||||||
|
|
||||||
|
Для обработки OHLC используются:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiWebSocketOhlcAdapter
|
||||||
|
```
|
||||||
|
|
||||||
|
Для обработки Trade используются:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiWebSocketTradeAdapter
|
||||||
|
```
|
||||||
|
|
||||||
|
Новый специализированный Adapter, в свою очередь, повторно использует ранее реализованный
|
||||||
|
|
||||||
|
```text
|
||||||
|
adapt_websocket_trade_document(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
из Build 060.14.
|
||||||
|
|
||||||
|
Таким образом Build не дублирует существующую функциональность и полностью основан на уже реализованных компонентах проекта.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Проброс исключений
|
||||||
|
|
||||||
|
Unified Router не выполняет собственной обработки ошибок.
|
||||||
|
|
||||||
|
Все исключения специализированных Adapter пробрасываются вызывающей стороне без изменения.
|
||||||
|
|
||||||
|
При обработке Quote сохраняется существующий механизм обработки ошибок.
|
||||||
|
|
||||||
|
При обработке OHLC сохраняется существующий механизм обработки ошибок.
|
||||||
|
|
||||||
|
При обработке Trade сохраняются исключения:
|
||||||
|
|
||||||
|
```text
|
||||||
|
TradeSchemaError
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
TradeParseError
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
TradeValueError
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
TradeMappingError
|
||||||
|
```
|
||||||
|
|
||||||
|
Подобный подход сохраняет единообразную архитектуру обработки ошибок во всей подсистеме Market Data Acquisition.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Изменённые файлы
|
||||||
|
|
||||||
|
В рамках Build были изменены только два файла.
|
||||||
|
|
||||||
|
## Unified Adapter
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/market_data/acquisition/adapters/dzengi/websocket.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Добавлен специализированный
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiWebSocketTradeAdapter
|
||||||
|
```
|
||||||
|
|
||||||
|
и расширена маршрутизация сообщений.
|
||||||
|
|
||||||
|
Существующая логика обработки Quote и OHLC не изменялась.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Unit-тесты
|
||||||
|
|
||||||
|
```text
|
||||||
|
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_unified_adapter.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Добавлен unit-тест маршрутизации сообщений
|
||||||
|
|
||||||
|
```text
|
||||||
|
internal.trade
|
||||||
|
```
|
||||||
|
|
||||||
|
Все существующие тесты сохранены без изменений.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Добавленные тесты
|
||||||
|
|
||||||
|
В рамках Build реализован дополнительный unit-тест, подтверждающий корректную маршрутизацию сообщений
|
||||||
|
|
||||||
|
```text
|
||||||
|
internal.trade
|
||||||
|
```
|
||||||
|
|
||||||
|
через Unified Router.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка успешной маршрутизации Trade
|
||||||
|
|
||||||
|
Подтверждается, что сообщение
|
||||||
|
|
||||||
|
```text
|
||||||
|
destination = "internal.trade"
|
||||||
|
```
|
||||||
|
|
||||||
|
автоматически направляется в специализированный
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiWebSocketTradeAdapter
|
||||||
|
```
|
||||||
|
|
||||||
|
и после завершения полного транспортного Pipeline возвращается каноническая модель
|
||||||
|
|
||||||
|
```text
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Результаты тестирования
|
||||||
|
|
||||||
|
После завершения реализации выполнен запуск целевого набора unit-тестов нового Adapter.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH="$PWD" python -m pytest \
|
||||||
|
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_adapter.py -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
4 passed
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
После этого выполнено тестирование Unified Router.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH="$PWD" python -m pytest \
|
||||||
|
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_unified_adapter.py -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
10 passed
|
||||||
|
```
|
||||||
|
|
||||||
|
Подтверждена корректная маршрутизация:
|
||||||
|
|
||||||
|
- Quote;
|
||||||
|
- OHLC;
|
||||||
|
- Trade.
|
||||||
|
|
||||||
|
Также подтверждено отсутствие изменений существующей функциональности.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Регрессионное тестирование Adapter
|
||||||
|
|
||||||
|
После завершения реализации выполнен полный запуск тестов адаптеров Dzengi.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH="$PWD" python -m pytest \
|
||||||
|
tests/unit/market_data/acquisition/adapters/dzengi
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
302 passed
|
||||||
|
```
|
||||||
|
|
||||||
|
Регрессий существующих Adapter, Parser, Mapper и Validation не обнаружено.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Регрессионное тестирование Market Data
|
||||||
|
|
||||||
|
После завершения реализации выполнен запуск полного набора unit-тестов подсистемы Market Data.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH="$PWD" python -m pytest \
|
||||||
|
tests/unit/market_data
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
914 passed
|
||||||
|
```
|
||||||
|
|
||||||
|
Подтверждено отсутствие регрессий во всех ранее реализованных компонентах подсистемы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Полное регрессионное тестирование
|
||||||
|
|
||||||
|
После завершения реализации выполнен полный запуск unit-тестов проекта.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH="$PWD" python -m pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1235 passed
|
||||||
|
```
|
||||||
|
|
||||||
|
Регрессий существующей функциональности не обнаружено.
|
||||||
|
|
||||||
|
Добавление поддержки Trade в Unified Routing не повлияло на существующие механизмы обработки Quote, OHLC, REST Trade и остальные подсистемы проекта.
|
||||||
|
|
||||||
|
# Проверка компиляции
|
||||||
|
|
||||||
|
После завершения реализации выполнена полная проверка компиляции проекта.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m compileall src tests
|
||||||
|
```
|
||||||
|
|
||||||
|
Компиляция завершилась успешно.
|
||||||
|
|
||||||
|
Ошибок синтаксиса не обнаружено.
|
||||||
|
|
||||||
|
Все изменённые файлы успешно компилируются и не нарушают целостность проекта.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Проверка Git diff
|
||||||
|
|
||||||
|
После завершения реализации выполнена финальная проверка изменений.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
без замечаний
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверка подтвердила отсутствие:
|
||||||
|
|
||||||
|
- trailing whitespace;
|
||||||
|
- ошибок окончания строк;
|
||||||
|
- конфликтов diff;
|
||||||
|
- нарушений форматирования.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Scope Build 060.15
|
||||||
|
|
||||||
|
В рамках данного Build реализован исключительно уровень
|
||||||
|
|
||||||
|
```text
|
||||||
|
Unified WebSocket Routing
|
||||||
|
```
|
||||||
|
|
||||||
|
Build **не включает**:
|
||||||
|
|
||||||
|
- WebSocket Runtime;
|
||||||
|
- WebSocket Protocol;
|
||||||
|
- Trades Feed;
|
||||||
|
- Dispatcher;
|
||||||
|
- Event Bus;
|
||||||
|
- Runtime Integration;
|
||||||
|
- обработку торговых решений.
|
||||||
|
|
||||||
|
Подобное ограничение полностью соответствует принятому принципу атомарной реализации Build.
|
||||||
|
|
||||||
|
Каждый этап дорожной карты реализует только один архитектурный уровень системы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Архитектурный результат
|
||||||
|
|
||||||
|
После завершения Build система содержит полностью реализованный механизм Unified WebSocket Routing.
|
||||||
|
|
||||||
|
Конвейер обработки принимает следующий вид.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Raw WebSocket Message
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DzengiUnifiedWebSocketAdapter
|
||||||
|
│
|
||||||
|
├────────► Quote Adapter
|
||||||
|
│ │
|
||||||
|
│ ▼
|
||||||
|
│ Quote
|
||||||
|
│
|
||||||
|
├────────► OHLC Adapter
|
||||||
|
│ │
|
||||||
|
│ ▼
|
||||||
|
│ CandleCloseEvent
|
||||||
|
│
|
||||||
|
└────────► Trade Adapter
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
Unified Router полностью изолирует вызывающий код от деталей транспортного Pipeline.
|
||||||
|
|
||||||
|
Все специализированные Adapter продолжают самостоятельно выполнять полный цикл преобразования транспортных данных в канонические модели предметной области.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Состояние WebSocket Trade Pipeline
|
||||||
|
|
||||||
|
После завершения Build 060.15 транспортный конвейер принимает следующий вид.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Raw WebSocket Trade
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Unified WebSocket Routing
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Schema Validation
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
ValidatedWebSocketTradeDocument
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Adapter
|
||||||
|
│
|
||||||
|
├────────► Parser
|
||||||
|
│
|
||||||
|
├────────► Value Validation
|
||||||
|
│
|
||||||
|
├────────► Mapper
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
Статус реализации компонентов:
|
||||||
|
|
||||||
|
| Компонент | Build | Статус |
|
||||||
|
|-----------|-------|--------|
|
||||||
|
| Canonical Trade Model | 060.1 | ✔ Completed |
|
||||||
|
| WebSocket Trade Transport Model | 060.9 | ✔ Completed |
|
||||||
|
| WebSocket Trade Schema Validation | 060.10 | ✔ Completed |
|
||||||
|
| WebSocket Trade Parser | 060.11 | ✔ Completed |
|
||||||
|
| WebSocket Trade Value Validation | 060.12 | ✔ Completed |
|
||||||
|
| WebSocket Trade Mapper | 060.13 | ✔ Completed |
|
||||||
|
| WebSocket Trade Adapter | 060.14 | ✔ Completed |
|
||||||
|
| Unified WebSocket Routing | 060.15 | ✔ Completed |
|
||||||
|
| Trades Feed | 060.16 | Pending |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Соблюдение архитектурных принципов
|
||||||
|
|
||||||
|
В рамках Build полностью сохранены архитектурные инварианты Dzentra.
|
||||||
|
|
||||||
|
## Локальность изменений
|
||||||
|
|
||||||
|
Изменены только:
|
||||||
|
|
||||||
|
- `src/market_data/acquisition/adapters/dzengi/websocket.py`;
|
||||||
|
- `tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_unified_adapter.py`.
|
||||||
|
|
||||||
|
Все остальные компоненты проекта остались без изменений.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Повторное использование архитектуры
|
||||||
|
|
||||||
|
Build полностью использует существующую архитектуру Unified Router.
|
||||||
|
|
||||||
|
Новый механизм маршрутизации не проектировался.
|
||||||
|
|
||||||
|
Расширен уже существующий механизм обработки сообщений.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Повторное использование инфраструктуры
|
||||||
|
|
||||||
|
Build полностью использует ранее реализованные компоненты проекта:
|
||||||
|
|
||||||
|
- Quote Adapter;
|
||||||
|
- OHLC Adapter;
|
||||||
|
- WebSocket Trade Adapter.
|
||||||
|
|
||||||
|
Новые Parser, Mapper или Validation не создавались.
|
||||||
|
|
||||||
|
Вся существующая инфраструктура используется повторно.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Разделение ответственности
|
||||||
|
|
||||||
|
Unified Router отвечает исключительно за определение типа входящего сообщения.
|
||||||
|
|
||||||
|
Специализированные Adapter отвечают исключительно за преобразование транспортных данных.
|
||||||
|
|
||||||
|
Runtime продолжает отвечать исключительно за получение транспортных сообщений.
|
||||||
|
|
||||||
|
Подобное разделение полностью соответствует архитектурным принципам Dzentra.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Обратная совместимость
|
||||||
|
|
||||||
|
Существующая обработка:
|
||||||
|
|
||||||
|
- Quote;
|
||||||
|
- OHLC;
|
||||||
|
- REST Trade;
|
||||||
|
|
||||||
|
не изменилась.
|
||||||
|
|
||||||
|
Добавленная поддержка Trade полностью совместима с существующей архитектурой и не оказывает влияния на ранее реализованные компоненты проекта.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Архитектурные решения Build (ADR)
|
||||||
|
|
||||||
|
## ADR-060.15-001
|
||||||
|
|
||||||
|
**Определение типа WebSocket-сообщения выполняется исключительно Unified Router.**
|
||||||
|
|
||||||
|
Runtime не должен содержать транспортную маршрутизацию.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-060.15-002
|
||||||
|
|
||||||
|
**Каждый тип рыночных данных обрабатывается собственным специализированным Adapter.**
|
||||||
|
|
||||||
|
Unified Router не выполняет преобразование транспортных данных.
|
||||||
|
|
||||||
|
Он только определяет тип сообщения и передаёт его соответствующему Adapter.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-060.15-003
|
||||||
|
|
||||||
|
**Trade Pipeline повторно использует ранее реализованный Adapter Build 060.14.**
|
||||||
|
|
||||||
|
Unified Router не вызывает Parser, Value Validation и Mapper напрямую.
|
||||||
|
|
||||||
|
Полный транспортный Pipeline остаётся полностью инкапсулированным внутри специализированного Adapter.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-060.15-004
|
||||||
|
|
||||||
|
**Подключение нового типа сообщений не изменяет существующую архитектуру Runtime.**
|
||||||
|
|
||||||
|
Поддержка Trade реализуется исключительно расширением Unified Router.
|
||||||
|
|
||||||
|
Это сохраняет независимость Runtime от особенностей конкретных транспортных сообщений.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Критерии завершения Build
|
||||||
|
|
||||||
|
Build 060.15 считается завершённым, поскольку выполнены все поставленные задачи.
|
||||||
|
|
||||||
|
- ✔ реализован `DzengiWebSocketTradeAdapter`;
|
||||||
|
- ✔ Trade подключён к Unified Router;
|
||||||
|
- ✔ добавлена маршрутизация сообщений `internal.trade`;
|
||||||
|
- ✔ Runtime не изменён;
|
||||||
|
- ✔ транспортный Pipeline Trade повторно использован без дублирования;
|
||||||
|
- ✔ сохранено разделение ответственности между Runtime, Router и Adapter;
|
||||||
|
- ✔ добавлен unit-тест маршрутизации Trade;
|
||||||
|
- ✔ успешно пройдены целевые тесты;
|
||||||
|
- ✔ успешно пройдена регрессия Adapter;
|
||||||
|
- ✔ успешно пройдена регрессия Market Data;
|
||||||
|
- ✔ успешно пройдена полная регрессия проекта (`1235 passed`);
|
||||||
|
- ✔ проект успешно компилируется;
|
||||||
|
- ✔ `git diff --check` не выявил замечаний;
|
||||||
|
- ✔ изменения полностью укладываются в согласованный scope Build.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Следующий этап
|
||||||
|
|
||||||
|
Следующим этапом дорожной карты является
|
||||||
|
|
||||||
|
```text
|
||||||
|
Build 060.16 — Trades Feed
|
||||||
|
```
|
||||||
|
|
||||||
|
Цель следующего Build:
|
||||||
|
|
||||||
|
- реализовать специализированный Feed обработки сделок;
|
||||||
|
- подключить поток Trade к инфраструктуре Market Data Acquisition;
|
||||||
|
- обеспечить публикацию канонических моделей `Trade`;
|
||||||
|
- подготовить основу для дальнейшего использования сделок подсистемами анализа рынка и торговых стратегий.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Итог
|
||||||
|
|
||||||
|
Build 060.15 завершил интеграцию **WebSocket Trade Pipeline** в существующий механизм Unified WebSocket Routing.
|
||||||
|
|
||||||
|
Новая реализация полностью повторно использует архитектуру Dzentra, не изменяет Runtime, не дублирует транспортный Pipeline и сохраняет строгое разделение ответственности между компонентами системы.
|
||||||
|
|
||||||
|
После завершения Build все три типа рыночных данных — **Quote**, **CandleCloseEvent** и **Trade** — обрабатываются единым механизмом маршрутизации и возвращаются в виде канонических моделей предметной области.
|
||||||
|
|
||||||
|
Build ограничен согласованным scope, успешно прошёл целевое и полное регрессионное тестирование, подтвердил отсутствие регрессий и завершил интеграцию WebSocket Trade в общий механизм маршрутизации Market Data Acquisition.
|
||||||
Reference in New Issue
Block a user