Build 060.9: add WebSocket Trade Transport Model
This commit is contained in:
@@ -161,6 +161,19 @@ class DzengiRestAggTrade:
|
|||||||
buyer_is_maker: bool
|
buyer_is_maker: bool
|
||||||
|
|
||||||
|
|
||||||
|
# Транспортное представление одного события
|
||||||
|
# Dzengi WebSocket internal.trade.
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class DzengiWebSocketTradeEvent:
|
||||||
|
trade_id: int
|
||||||
|
price: DzengiRawNumeric
|
||||||
|
size: DzengiRawNumeric
|
||||||
|
timestamp: int
|
||||||
|
symbol: str
|
||||||
|
buyer: bool
|
||||||
|
order_id: str
|
||||||
|
|
||||||
|
|
||||||
# Транспортное представление одного события Dzengi WebSocket ohlc.event.
|
# Транспортное представление одного события Dzengi WebSocket ohlc.event.
|
||||||
#
|
#
|
||||||
# Событие содержит завершённую OHLC-свечу без объёма и поэтому не является
|
# Событие содержит завершённую OHLC-свечу без объёма и поэтому не является
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from src.market_data.acquisition.adapters.dzengi.models import (
|
|||||||
DzengiRateLimit,
|
DzengiRateLimit,
|
||||||
DzengiRestAggTrade,
|
DzengiRestAggTrade,
|
||||||
DzengiUnknownFilter,
|
DzengiUnknownFilter,
|
||||||
|
DzengiWebSocketTradeEvent,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -250,3 +251,57 @@ def test_rest_agg_trade_is_immutable_and_uses_slots() -> None:
|
|||||||
|
|
||||||
with pytest.raises(FrozenInstanceError):
|
with pytest.raises(FrozenInstanceError):
|
||||||
trade.price = "1.00" # type: ignore[misc]
|
trade.price = "1.00" # type: ignore[misc]
|
||||||
|
|
||||||
|
|
||||||
|
def test_websocket_trade_event_stores_complete_transport_contract() -> None:
|
||||||
|
event = DzengiWebSocketTradeEvent(
|
||||||
|
trade_id=2134846831,
|
||||||
|
price=64555.55,
|
||||||
|
size=0.002,
|
||||||
|
timestamp=1784218012030,
|
||||||
|
symbol="BTC/USD_LEVERAGE",
|
||||||
|
buyer=False,
|
||||||
|
order_id="00a02503-0079-54c4-0000-000081e62b58",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert event.trade_id == 2134846831
|
||||||
|
assert event.price == 64555.55
|
||||||
|
assert event.size == 0.002
|
||||||
|
assert event.timestamp == 1784218012030
|
||||||
|
assert event.symbol == "BTC/USD_LEVERAGE"
|
||||||
|
assert event.buyer is False
|
||||||
|
assert event.order_id == "00a02503-0079-54c4-0000-000081e62b58"
|
||||||
|
|
||||||
|
|
||||||
|
def test_websocket_trade_event_preserves_raw_numeric_values() -> None:
|
||||||
|
event = DzengiWebSocketTradeEvent(
|
||||||
|
trade_id=2134846831,
|
||||||
|
price="64555.55",
|
||||||
|
size="0.002",
|
||||||
|
timestamp=1784218012030,
|
||||||
|
symbol="BTC/USD_LEVERAGE",
|
||||||
|
buyer=True,
|
||||||
|
order_id="order-id",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert event.price == "64555.55"
|
||||||
|
assert isinstance(event.price, str)
|
||||||
|
assert event.size == "0.002"
|
||||||
|
assert isinstance(event.size, str)
|
||||||
|
|
||||||
|
|
||||||
|
def test_websocket_trade_event_is_immutable_and_uses_slots() -> None:
|
||||||
|
event = DzengiWebSocketTradeEvent(
|
||||||
|
trade_id=2134846831,
|
||||||
|
price=64555.55,
|
||||||
|
size=0.002,
|
||||||
|
timestamp=1784218012030,
|
||||||
|
symbol="BTC/USD_LEVERAGE",
|
||||||
|
buyer=False,
|
||||||
|
order_id="order-id",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not hasattr(event, "__dict__")
|
||||||
|
|
||||||
|
with pytest.raises(FrozenInstanceError):
|
||||||
|
event.price = 1.0 # type: ignore[misc]
|
||||||
|
|||||||
957
docs/migrations/build_060_9.md
Normal file
957
docs/migrations/build_060_9.md
Normal file
@@ -0,0 +1,957 @@
|
|||||||
|
# Build 060.9 — WebSocket Trade Transport Model
|
||||||
|
|
||||||
|
**Engineering Migration Report**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Контроль документа
|
||||||
|
|
||||||
|
| Свойство | Значение |
|
||||||
|
|----------|----------|
|
||||||
|
| Build | 060.9 |
|
||||||
|
| Название | WebSocket Trade Transport Model |
|
||||||
|
| Статус | Completed |
|
||||||
|
| Проект | Dzentra |
|
||||||
|
| Подсистема | Market Data Acquisition |
|
||||||
|
| Компонент | Trades Feed |
|
||||||
|
| Версия | 1.0 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Цель Build
|
||||||
|
|
||||||
|
После завершения Build 060.8 система получила источник получения сырых REST-документов Trade (`DzengiTradesDocumentSource`).
|
||||||
|
|
||||||
|
Следующим этапом развития является построение полного конвейера обработки сделок, поступающих по WebSocket.
|
||||||
|
|
||||||
|
Build 060.9 открывает вторую ветку реализации Trades Feed и вводит транспортную модель, описывающую одно WebSocket-событие биржи.
|
||||||
|
|
||||||
|
Данный Build ограничивается исключительно транспортным уровнем (Transport Layer) и не затрагивает Parser, Schema Validation, Value Validation, Mapper, Adapter, Routing и Runtime.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Предпосылки
|
||||||
|
|
||||||
|
К моменту начала Build архитектура Dzentra уже содержала транспортные модели для остальных типов рыночных данных.
|
||||||
|
|
||||||
|
## REST Trades
|
||||||
|
|
||||||
|
```text
|
||||||
|
REST Document
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DzengiRestAggTrade
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
## WebSocket Quote
|
||||||
|
|
||||||
|
```text
|
||||||
|
WebSocket Quote Document
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DzengiWebSocketQuoteResponse
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Quote
|
||||||
|
```
|
||||||
|
|
||||||
|
## WebSocket OHLC
|
||||||
|
|
||||||
|
```text
|
||||||
|
WebSocket OHLC Document
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DzengiWebSocketOhlcEvent
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Candle
|
||||||
|
```
|
||||||
|
|
||||||
|
Для WebSocket Trade аналогичная транспортная модель отсутствовала.
|
||||||
|
|
||||||
|
В результате архитектура обработки сделок оставалась неполной и асимметричной относительно остальных типов рыночных данных.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Архитектурное основание
|
||||||
|
|
||||||
|
Build 060.9 не проектирует транспортный контракт самостоятельно.
|
||||||
|
|
||||||
|
В качестве первичного источника использованы результаты инженерного исследования, выполненного в рамках Build 057.
|
||||||
|
|
||||||
|
Во время исследования были изучены:
|
||||||
|
|
||||||
|
- REST `aggTrades`;
|
||||||
|
- WebSocket `trades.subscribe`;
|
||||||
|
- реальные Production-сообщения;
|
||||||
|
- соответствие REST и WebSocket контрактов.
|
||||||
|
|
||||||
|
Исследование подтвердило фактический формат события:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "OK",
|
||||||
|
"destination": "internal.trade",
|
||||||
|
"payload": {
|
||||||
|
"buyer": false,
|
||||||
|
"id": 2134846831,
|
||||||
|
"orderId": "00a02503-0079-54c4-0000-000081e62b58",
|
||||||
|
"price": 64555.55,
|
||||||
|
"size": 0.002,
|
||||||
|
"symbol": "BTC/USD_LEVERAGE",
|
||||||
|
"ts": 1784218012030
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Именно этот контракт принят за основу реализации Build 060.9.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Результаты архитектурного аудита
|
||||||
|
|
||||||
|
Перед началом реализации был выполнен аудит существующего адаптера Dzengi.
|
||||||
|
|
||||||
|
Подтверждено наличие транспортных моделей:
|
||||||
|
|
||||||
|
- `DzengiRestAggTrade`;
|
||||||
|
- `DzengiWebSocketQuoteResponse`;
|
||||||
|
- `DzengiWebSocketOhlcEvent`.
|
||||||
|
|
||||||
|
Также подтверждено существование полной иерархии ошибок обработки Trade:
|
||||||
|
|
||||||
|
- `TradeTransportError`;
|
||||||
|
- `TradeSchemaError`;
|
||||||
|
- `TradeParseError`;
|
||||||
|
- `TradeValueError`;
|
||||||
|
- `TradeMappingError`.
|
||||||
|
|
||||||
|
Одновременно подтверждено отсутствие следующих компонентов WebSocket Trade Pipeline:
|
||||||
|
|
||||||
|
- Transport Model;
|
||||||
|
- Schema Validation;
|
||||||
|
- Parser;
|
||||||
|
- Value Validation;
|
||||||
|
- Mapper;
|
||||||
|
- Adapter.
|
||||||
|
|
||||||
|
Таким образом Build 060.9 полностью соответствует утверждённой дорожной карте серии 060 и закрывает первый этап WebSocket-ветки обработки сделок.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Архитектурное решение
|
||||||
|
|
||||||
|
В транспортный слой адаптера введена новая модель
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiWebSocketTradeEvent
|
||||||
|
```
|
||||||
|
|
||||||
|
Она представляет собой типизированное описание одного события
|
||||||
|
|
||||||
|
```text
|
||||||
|
destination = internal.trade
|
||||||
|
```
|
||||||
|
|
||||||
|
и относится исключительно к транспортному уровню адаптера.
|
||||||
|
|
||||||
|
Модель не является:
|
||||||
|
|
||||||
|
- внутренней моделью Dzentra;
|
||||||
|
- бизнес-сущностью;
|
||||||
|
- канонической моделью `Trade`.
|
||||||
|
|
||||||
|
Её единственная ответственность — хранение уже разобранных транспортных данных WebSocket-сообщения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Реализованная транспортная модель
|
||||||
|
|
||||||
|
В файл
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/market_data/acquisition/adapters/dzengi/models.py
|
||||||
|
```
|
||||||
|
|
||||||
|
добавлена новая транспортная модель:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class DzengiWebSocketTradeEvent:
|
||||||
|
trade_id: int
|
||||||
|
price: DzengiRawNumeric
|
||||||
|
size: DzengiRawNumeric
|
||||||
|
timestamp: int
|
||||||
|
symbol: str
|
||||||
|
buyer: bool
|
||||||
|
order_id: str
|
||||||
|
```
|
||||||
|
|
||||||
|
Модель размещена рядом с существующими транспортными моделями адаптера.
|
||||||
|
|
||||||
|
Итоговая последовательность моделей выглядит следующим образом:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiRestAggTrade
|
||||||
|
DzengiWebSocketTradeEvent
|
||||||
|
DzengiWebSocketOhlcEvent
|
||||||
|
```
|
||||||
|
|
||||||
|
Такое расположение сохраняет логическую группировку транспортных сущностей по типам рыночных данных.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Почему используется dataclass
|
||||||
|
|
||||||
|
`DzengiWebSocketTradeEvent` представляет собой неизменяемый контейнер транспортных данных.
|
||||||
|
|
||||||
|
Модель:
|
||||||
|
|
||||||
|
- не содержит бизнес-логики;
|
||||||
|
- не выполняет преобразование типов;
|
||||||
|
- не выполняет структурную проверку;
|
||||||
|
- не выполняет Value Validation;
|
||||||
|
- не выполняет Mapping;
|
||||||
|
- не взаимодействует с сетью;
|
||||||
|
- не содержит изменяемого состояния.
|
||||||
|
|
||||||
|
Поэтому используется конструкция
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
что полностью соответствует архитектурному стилю остальных транспортных моделей проекта.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Назначение полей
|
||||||
|
|
||||||
|
В модель включены только поля, подтверждённые реальными Production-сообщениями.
|
||||||
|
|
||||||
|
| Поле | Назначение |
|
||||||
|
|------|------------|
|
||||||
|
| `trade_id` | идентификатор сделки |
|
||||||
|
| `price` | цена сделки |
|
||||||
|
| `size` | объём сделки |
|
||||||
|
| `timestamp` | время исполнения |
|
||||||
|
| `symbol` | торговый инструмент |
|
||||||
|
| `buyer` | сторона инициатора |
|
||||||
|
| `order_id` | идентификатор ордера |
|
||||||
|
|
||||||
|
Каждое поле соответствует данным, полученным в ходе исследования Build 057.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Нормализация имён полей
|
||||||
|
|
||||||
|
Во входящем WebSocket JSON используются ключи
|
||||||
|
|
||||||
|
```text
|
||||||
|
id
|
||||||
|
ts
|
||||||
|
orderId
|
||||||
|
```
|
||||||
|
|
||||||
|
Во внутренней транспортной модели используются нормализованные имена
|
||||||
|
|
||||||
|
```text
|
||||||
|
trade_id
|
||||||
|
timestamp
|
||||||
|
order_id
|
||||||
|
```
|
||||||
|
|
||||||
|
Подобная нормализация уже используется в остальных транспортных моделях Dzentra и обеспечивает единый стиль внутренних контрактов.
|
||||||
|
|
||||||
|
Преобразование JSON-ключей выполняется Parser.
|
||||||
|
|
||||||
|
Транспортная модель не зависит от формата сериализации входящего сообщения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Использование DzengiRawNumeric
|
||||||
|
|
||||||
|
Поля
|
||||||
|
|
||||||
|
```python
|
||||||
|
price
|
||||||
|
size
|
||||||
|
```
|
||||||
|
|
||||||
|
имеют тип
|
||||||
|
|
||||||
|
```python
|
||||||
|
DzengiRawNumeric
|
||||||
|
```
|
||||||
|
|
||||||
|
Такое решение уже применяется в REST Trade и других транспортных моделях адаптера.
|
||||||
|
|
||||||
|
Использование `DzengiRawNumeric` означает, что транспортная модель сохраняет числовые значения в исходном виде без выполнения каких-либо преобразований.
|
||||||
|
|
||||||
|
Ответственность за интерпретацию числовых значений относится к последующим этапам конвейера.
|
||||||
|
|
||||||
|
Это позволяет полностью разделить:
|
||||||
|
|
||||||
|
- транспортное представление данных;
|
||||||
|
- их синтаксическую корректность;
|
||||||
|
- семантическую валидацию;
|
||||||
|
- преобразование в каноническую модель.
|
||||||
|
|
||||||
|
# Неизменяемость модели
|
||||||
|
|
||||||
|
Параметр
|
||||||
|
|
||||||
|
```python
|
||||||
|
frozen=True
|
||||||
|
```
|
||||||
|
|
||||||
|
гарантирует, что после создания экземпляра его поля не могут быть изменены.
|
||||||
|
|
||||||
|
Это важно для транспортного слоя, поскольку модель представляет собой результат разбора одного входящего сообщения и после создания должна оставаться неизменной.
|
||||||
|
|
||||||
|
После формирования экземпляр проходит через последующие этапы конвейера:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Validated WebSocket Document
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DzengiWebSocketTradeEvent
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Value Validation
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Mapper
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
Неизменяемость транспортной модели обеспечивает воспроизводимость обработки сообщения и исключает случайную модификацию данных на последующих этапах.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Использование slots
|
||||||
|
|
||||||
|
Параметр
|
||||||
|
|
||||||
|
```python
|
||||||
|
slots=True
|
||||||
|
```
|
||||||
|
|
||||||
|
используется для:
|
||||||
|
|
||||||
|
- фиксации структуры модели;
|
||||||
|
- предотвращения динамического добавления атрибутов;
|
||||||
|
- уменьшения накладных расходов на экземпляр;
|
||||||
|
- сохранения единого архитектурного стиля транспортных моделей Dzentra.
|
||||||
|
|
||||||
|
Экземпляр `DzengiWebSocketTradeEvent` не содержит `__dict__`, что дополнительно подтверждает его роль как лёгкого транспортного контейнера.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Граница ответственности модели
|
||||||
|
|
||||||
|
`DzengiWebSocketTradeEvent` отвечает исключительно за хранение уже разобранных транспортных данных.
|
||||||
|
|
||||||
|
В обязанности модели **не входит**:
|
||||||
|
|
||||||
|
- проверка структуры исходного JSON;
|
||||||
|
- проверка значения `status`;
|
||||||
|
- проверка `destination`;
|
||||||
|
- проверка наличия обязательных полей;
|
||||||
|
- проверка корректности цены;
|
||||||
|
- проверка корректности объёма;
|
||||||
|
- проверка корректности timestamp;
|
||||||
|
- определение бизнес-семантики сделки;
|
||||||
|
- преобразование в каноническую модель `Trade`.
|
||||||
|
|
||||||
|
Каждая из перечисленных задач относится к отдельному уровню архитектуры и будет реализована в соответствующих Build.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Целевой конвейер обработки WebSocket Trade
|
||||||
|
|
||||||
|
После завершения Builds 060.9–060.14 полный конвейер обработки будет иметь следующий вид:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Raw WebSocket Object
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
WebSocket Trade Schema Validation
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
ValidatedWebSocketTradeDocument
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
WebSocket Trade Parser
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DzengiWebSocketTradeEvent
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
WebSocket Trade Value Validation
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
WebSocket Trade Mapper
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
Build 060.9 реализует только один компонент этого конвейера:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiWebSocketTradeEvent
|
||||||
|
```
|
||||||
|
|
||||||
|
Все остальные этапы будут реализованы последовательно в следующих Build.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Соотношение с канонической моделью Trade
|
||||||
|
|
||||||
|
В Build 060.1 была реализована каноническая модель
|
||||||
|
|
||||||
|
```text
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
Она используется внутренними компонентами Dzentra и полностью независима от способа получения рыночных данных.
|
||||||
|
|
||||||
|
`DzengiWebSocketTradeEvent` не заменяет `Trade`.
|
||||||
|
|
||||||
|
Эти модели относятся к различным архитектурным уровням.
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiWebSocketTradeEvent
|
||||||
|
```
|
||||||
|
|
||||||
|
— транспортное представление WebSocket-события конкретной биржи.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
— единая внутренняя модель сделки, используемая всеми компонентами Dzentra.
|
||||||
|
|
||||||
|
После реализации Mapper транспортная модель будет преобразовываться в каноническую.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Соотношение REST и WebSocket транспортных моделей
|
||||||
|
|
||||||
|
REST и WebSocket описывают одну и ту же биржевую сделку, но используют различные транспортные контракты.
|
||||||
|
|
||||||
|
REST:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"a": 2134846831,
|
||||||
|
"p": "64555.55",
|
||||||
|
"q": "0.002",
|
||||||
|
"T": 1784218012030,
|
||||||
|
"m": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
WebSocket:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 2134846831,
|
||||||
|
"price": 64555.55,
|
||||||
|
"size": 0.002,
|
||||||
|
"symbol": "BTC/USD_LEVERAGE",
|
||||||
|
"ts": 1784218012030,
|
||||||
|
"buyer": false,
|
||||||
|
"orderId": "00a02503-0079-54c4-0000-000081e62b58"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Несмотря на различие транспортных контрактов, обе модели описывают одну и ту же биржевую сущность и далее преобразуются в единую каноническую модель `Trade`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Сопоставление полей REST и WebSocket
|
||||||
|
|
||||||
|
| Каноническая семантика | REST | WebSocket |
|
||||||
|
|------------------------|------|-----------|
|
||||||
|
| идентификатор сделки | `a` | `id` |
|
||||||
|
| цена | `p` | `price` |
|
||||||
|
| объём | `q` | `size` |
|
||||||
|
| timestamp | `T` | `ts` |
|
||||||
|
| сторона инициатора | `m` | `buyer` |
|
||||||
|
| торговый инструмент | отсутствует | `symbol` |
|
||||||
|
| идентификатор ордера | отсутствует | `orderId` |
|
||||||
|
|
||||||
|
Согласно результатам исследования Build 057 выполняется соответствие:
|
||||||
|
|
||||||
|
```text
|
||||||
|
buyer == !m
|
||||||
|
```
|
||||||
|
|
||||||
|
Данная нормализация будет реализована WebSocket Trade Mapper в Build 060.13.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Обработка транспортной оболочки сообщения
|
||||||
|
|
||||||
|
Фактическое WebSocket-сообщение имеет следующую структуру:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "OK",
|
||||||
|
"destination": "internal.trade",
|
||||||
|
"payload": {
|
||||||
|
"...": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`DzengiWebSocketTradeEvent` представляет исключительно содержимое объекта `payload`.
|
||||||
|
|
||||||
|
Поля
|
||||||
|
|
||||||
|
```text
|
||||||
|
status
|
||||||
|
destination
|
||||||
|
```
|
||||||
|
|
||||||
|
не входят в транспортную модель, поскольку относятся к внешней оболочке WebSocket-документа.
|
||||||
|
|
||||||
|
Их обработка будет реализована в Build 060.10 — WebSocket Trade Schema Validation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Решение по полю clientOrderId
|
||||||
|
|
||||||
|
Во время исследования было обнаружено, что Swagger содержит дополнительное поле
|
||||||
|
|
||||||
|
```text
|
||||||
|
clientOrderId
|
||||||
|
```
|
||||||
|
|
||||||
|
Однако анализ реальных Production-сообщений подтвердил обязательное наличие только поля
|
||||||
|
|
||||||
|
```text
|
||||||
|
orderId
|
||||||
|
```
|
||||||
|
|
||||||
|
Поэтому `clientOrderId` не включён в обязательный транспортный контракт Build 060.9.
|
||||||
|
|
||||||
|
Это исключает зависимость Parser от поля, которое отсутствует в фактических сообщениях биржи.
|
||||||
|
|
||||||
|
При реализации Schema Validation будут использоваться следующие принципы:
|
||||||
|
|
||||||
|
- обязательными считаются только поля, подтверждённые Production;
|
||||||
|
- наличие дополнительных полей не должно приводить к ошибке;
|
||||||
|
- неподтверждённые поля не становятся обязательными автоматически.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Изменённые файлы
|
||||||
|
|
||||||
|
В рамках Build были изменены только два файла.
|
||||||
|
|
||||||
|
## Транспортная модель
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/market_data/acquisition/adapters/dzengi/models.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Добавлена новая модель
|
||||||
|
|
||||||
|
```text
|
||||||
|
DzengiWebSocketTradeEvent
|
||||||
|
```
|
||||||
|
|
||||||
|
При этом существующие модели, импорты и поведение файла не изменялись.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Unit-тесты
|
||||||
|
|
||||||
|
```text
|
||||||
|
tests/unit/market_data/acquisition/adapters/dzengi/test_models.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Добавлены три новых unit-теста транспортной модели.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Добавленные тесты
|
||||||
|
|
||||||
|
## Проверка полного транспортного контракта
|
||||||
|
|
||||||
|
Тест
|
||||||
|
|
||||||
|
```text
|
||||||
|
test_websocket_trade_event_stores_complete_transport_contract
|
||||||
|
```
|
||||||
|
|
||||||
|
проверяет сохранение всех подтверждённых полей транспортной модели.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка сохранения исходных числовых значений
|
||||||
|
|
||||||
|
Тест
|
||||||
|
|
||||||
|
```text
|
||||||
|
test_websocket_trade_event_preserves_raw_numeric_values
|
||||||
|
```
|
||||||
|
|
||||||
|
подтверждает, что модель сохраняет цену и объём без преобразования типов.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка неизменяемости и slots
|
||||||
|
|
||||||
|
Тест
|
||||||
|
|
||||||
|
```text
|
||||||
|
test_websocket_trade_event_is_immutable_and_uses_slots
|
||||||
|
```
|
||||||
|
|
||||||
|
подтверждает:
|
||||||
|
|
||||||
|
- отсутствие `__dict__`;
|
||||||
|
- невозможность изменения экземпляра после создания.
|
||||||
|
|
||||||
|
# Результаты тестирования
|
||||||
|
|
||||||
|
Выполнен целевой запуск unit-тестов транспортных моделей:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pytest \
|
||||||
|
tests/unit/market_data/acquisition/adapters/dzengi/test_models.py \
|
||||||
|
-q
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат выполнения:
|
||||||
|
|
||||||
|
```text
|
||||||
|
12 passed in 0.03s
|
||||||
|
```
|
||||||
|
|
||||||
|
До реализации Build 060.9 файл содержал девять тестов.
|
||||||
|
|
||||||
|
После добавления транспортной модели количество тестов увеличилось:
|
||||||
|
|
||||||
|
```text
|
||||||
|
9 → 12
|
||||||
|
```
|
||||||
|
|
||||||
|
что соответствует трём новым проверкам.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Регрессионное тестирование
|
||||||
|
|
||||||
|
После завершения реализации был выполнен полный запуск тестов адаптера Dzengi.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pytest \
|
||||||
|
tests/unit/market_data/acquisition/adapters/dzengi \
|
||||||
|
-q
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
246 passed in 0.11s
|
||||||
|
```
|
||||||
|
|
||||||
|
До реализации Build 060.9 набор содержал:
|
||||||
|
|
||||||
|
```text
|
||||||
|
243 passed
|
||||||
|
```
|
||||||
|
|
||||||
|
После добавления новой транспортной модели:
|
||||||
|
|
||||||
|
```text
|
||||||
|
246 passed
|
||||||
|
```
|
||||||
|
|
||||||
|
Регрессий не обнаружено.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Проверка компиляции
|
||||||
|
|
||||||
|
Выполнена проверка компиляции изменённых файлов:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m compileall \
|
||||||
|
src/market_data/acquisition/adapters/dzengi/models.py \
|
||||||
|
tests/unit/market_data/acquisition/adapters/dzengi/test_models.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Компиляция завершилась успешно.
|
||||||
|
|
||||||
|
Синтаксические ошибки отсутствуют.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Проверка Git diff
|
||||||
|
|
||||||
|
Финальный анализ изменений подтвердил, что Build затронул только согласованный scope.
|
||||||
|
|
||||||
|
Добавлены:
|
||||||
|
|
||||||
|
- одна транспортная модель;
|
||||||
|
- три unit-теста.
|
||||||
|
|
||||||
|
Не изменялись:
|
||||||
|
|
||||||
|
```text
|
||||||
|
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
|
||||||
|
src/market_data/acquisition/exceptions.py
|
||||||
|
src/market_data/acquisition/runtime/
|
||||||
|
src/market_data/acquisition/feeds/
|
||||||
|
src/market_data/acquisition/handlers/
|
||||||
|
```
|
||||||
|
|
||||||
|
Таким образом Build полностью соответствует принципу локальности изменений.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Scope Build 060.9
|
||||||
|
|
||||||
|
В рамках данного Build реализовано только:
|
||||||
|
|
||||||
|
```text
|
||||||
|
WebSocket Trade Transport Model
|
||||||
|
```
|
||||||
|
|
||||||
|
В Build **не входят**:
|
||||||
|
|
||||||
|
- WebSocket Trade Schema Validation;
|
||||||
|
- WebSocket Trade Parser;
|
||||||
|
- WebSocket Trade Value Validation;
|
||||||
|
- WebSocket Trade Mapper;
|
||||||
|
- WebSocket Trade Adapter;
|
||||||
|
- Unified WebSocket Routing;
|
||||||
|
- Trade Subscription Layer;
|
||||||
|
- Trades Feed Core;
|
||||||
|
- Runtime Integration.
|
||||||
|
|
||||||
|
Такое разделение обеспечивает атомарность миграции и позволяет независимо проверять каждый архитектурный уровень.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Архитектурный результат
|
||||||
|
|
||||||
|
После завершения Build система содержит транспортные представления обеих форм получения сделок.
|
||||||
|
|
||||||
|
REST:
|
||||||
|
|
||||||
|
```text
|
||||||
|
REST Trade Document
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DzengiRestAggTrade
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
WebSocket:
|
||||||
|
|
||||||
|
```text
|
||||||
|
WebSocket Trade Document
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DzengiWebSocketTradeEvent
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
На текущем этапе WebSocket-ветка содержит только транспортную модель.
|
||||||
|
|
||||||
|
Остальные уровни конвейера будут добавлены последовательно в следующих Build.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Состояние WebSocket Trade Pipeline
|
||||||
|
|
||||||
|
После завершения Build 060.9 архитектура конвейера выглядит следующим образом:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Raw WebSocket Trade Document
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Schema Validation
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Parser
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DzengiWebSocketTradeEvent
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Value Validation
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Mapper
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Trade
|
||||||
|
```
|
||||||
|
|
||||||
|
Статус реализации компонентов:
|
||||||
|
|
||||||
|
| Компонент | Build | Статус |
|
||||||
|
|-----------|-------|--------|
|
||||||
|
| Canonical Trade Model | 060.1 | ✔ Completed |
|
||||||
|
| WebSocket Trade Transport Model | 060.9 | ✔ Completed |
|
||||||
|
| WebSocket Trade Schema Validation | 060.10 | Pending |
|
||||||
|
| WebSocket Trade Parser | 060.11 | Pending |
|
||||||
|
| WebSocket Trade Value Validation | 060.12 | Pending |
|
||||||
|
| WebSocket Trade Mapper | 060.13 | Pending |
|
||||||
|
| WebSocket Trade Adapter | 060.14 | Pending |
|
||||||
|
| Unified WebSocket Routing | 060.15 | Pending |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Соблюдение архитектурных принципов
|
||||||
|
|
||||||
|
В рамках реализации Build сохранены все архитектурные инварианты проекта Dzentra.
|
||||||
|
|
||||||
|
## Локальность изменений
|
||||||
|
|
||||||
|
Изменены только транспортная модель и её unit-тесты.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Отсутствие преждевременной интеграции
|
||||||
|
|
||||||
|
Новая модель не подключена к Adapter, Runtime и Trades Feed до появления всех промежуточных этапов обработки.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Разделение транспортной и канонической моделей
|
||||||
|
|
||||||
|
`DzengiWebSocketTradeEvent` используется только внутри транспортного слоя адаптера.
|
||||||
|
|
||||||
|
Внутренние компоненты системы продолжают работать исключительно с канонической моделью `Trade`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Отсутствие бизнес-логики
|
||||||
|
|
||||||
|
Транспортная модель не выполняет:
|
||||||
|
|
||||||
|
- Parsing;
|
||||||
|
- Schema Validation;
|
||||||
|
- Value Validation;
|
||||||
|
- Mapping;
|
||||||
|
- Routing;
|
||||||
|
- Subscription;
|
||||||
|
- Network I/O.
|
||||||
|
|
||||||
|
Она представляет собой исключительно неизменяемый контейнер транспортных данных.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Неизменность существующего поведения
|
||||||
|
|
||||||
|
Рабочая логика обработки Quote, OHLC и REST Trade не изменялась.
|
||||||
|
|
||||||
|
Build является полностью обратимо-совместимым с существующей архитектурой.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Критерии завершения Build
|
||||||
|
|
||||||
|
Build 060.9 считается завершённым, поскольку выполнены все поставленные задачи.
|
||||||
|
|
||||||
|
- ✔ исследован Production-контракт WebSocket Trade;
|
||||||
|
- ✔ использованы результаты Build 057;
|
||||||
|
- ✔ реализована транспортная модель `DzengiWebSocketTradeEvent`;
|
||||||
|
- ✔ модель содержит все подтверждённые поля события;
|
||||||
|
- ✔ цена и объём сохраняются как `DzengiRawNumeric`;
|
||||||
|
- ✔ модель является неизменяемой;
|
||||||
|
- ✔ используется `slots`;
|
||||||
|
- ✔ добавлены три unit-теста;
|
||||||
|
- ✔ все целевые тесты успешно проходят;
|
||||||
|
- ✔ регрессионное тестирование успешно завершено;
|
||||||
|
- ✔ компиляция выполнена без ошибок;
|
||||||
|
- ✔ изменения не выходят за пределы согласованного scope.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Следующий этап
|
||||||
|
|
||||||
|
Следующим этапом дорожной карты является
|
||||||
|
|
||||||
|
```text
|
||||||
|
Build 060.10 — WebSocket Trade Schema Validation
|
||||||
|
```
|
||||||
|
|
||||||
|
Цель Build:
|
||||||
|
|
||||||
|
- реализовать структурную проверку WebSocket Trade-документа;
|
||||||
|
- проверить транспортную оболочку сообщения;
|
||||||
|
- проверить `status`;
|
||||||
|
- проверить `destination = internal.trade`;
|
||||||
|
- проверить наличие объекта `payload`;
|
||||||
|
- проверить наличие обязательных ключей транспортного события;
|
||||||
|
- сформировать `ValidatedWebSocketTradeDocument`;
|
||||||
|
- использовать `TradeSchemaError` для ошибок структуры.
|
||||||
|
|
||||||
|
После завершения Build 060.10 конвейер примет следующий вид:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Raw WebSocket Object
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
validate_dzengi_websocket_trade_schema(...)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
ValidatedWebSocketTradeDocument
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Build 060.11 — WebSocket Trade Parser
|
||||||
|
```
|
||||||
|
|
||||||
|
Build 060.10 по-прежнему не будет выполнять:
|
||||||
|
|
||||||
|
- преобразование значений;
|
||||||
|
- семантическую валидацию;
|
||||||
|
- Mapping в `Trade`;
|
||||||
|
- WebSocket Routing;
|
||||||
|
- Runtime Integration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Итог
|
||||||
|
|
||||||
|
Build 060.9 завершил формирование транспортного уровня обработки WebSocket Trade, добавив отсутствующую транспортную модель `DzengiWebSocketTradeEvent`.
|
||||||
|
|
||||||
|
Новая модель представляет собой типизированное описание события
|
||||||
|
|
||||||
|
```text
|
||||||
|
destination = internal.trade
|
||||||
|
```
|
||||||
|
|
||||||
|
и основана на фактическом Production-контракте, подтверждённом в ходе исследования Build 057.
|
||||||
|
|
||||||
|
Реализация полностью соответствует архитектурным принципам Dzentra:
|
||||||
|
|
||||||
|
- транспортный слой отделён от канонической модели;
|
||||||
|
- Build ограничен согласованным scope;
|
||||||
|
- существующее поведение системы не изменено;
|
||||||
|
- создан фундамент для последующей реализации WebSocket Trade Schema Validation, Parser, Mapper и полного конвейера Trades Feed.
|
||||||
|
|
||||||
|
На этом Build 060.9 считается полностью завершённым.
|
||||||
Reference in New Issue
Block a user