1420 lines
36 KiB
Markdown
1420 lines
36 KiB
Markdown
# Build 060.23 — Trade Stream Acquisition Integration
|
||
|
||
**Engineering Migration Report**
|
||
|
||
---
|
||
|
||
# Контроль документа
|
||
|
||
| Свойство | Значение |
|
||
|----------|----------|
|
||
| Build | 060.23 |
|
||
| Название | Trade Stream Acquisition Integration |
|
||
| Статус | Completed |
|
||
| Проект | Dzentra |
|
||
| Подсистема | Market Data Acquisition |
|
||
| Компонент | Trade Stream Acquisition Integration Layer |
|
||
| Версия | 1.0 |
|
||
|
||
---
|
||
|
||
# Связанные документы
|
||
|
||
- `build_060_23_architecture.md` — архитектурная спецификация Build.
|
||
- `build_060_22.md` — Engineering Migration Report предыдущего Build.
|
||
- `build_060_22_architecture.md` — спецификация Acquisition Runtime Service.
|
||
- `build_060_20_1.md` — Engineering Migration Report корректирующего Build владения состоянием Trade Stream.
|
||
- `build_060_19.md` — Engineering Migration Report Trade Recovery.
|
||
- `build_060_18.md` — Engineering Migration Report Trade Stream Consistency.
|
||
|
||
---
|
||
|
||
# Цель Build
|
||
|
||
Build 060.22 завершил создание минимального исполняемого сервисного слоя Acquisition Runtime.
|
||
|
||
После предыдущего этапа в системе уже существовали:
|
||
|
||
```text
|
||
AcquisitionRuntimeServiceProtocol
|
||
AcquisitionRuntimeService
|
||
AcquisitionRuntimeCommand
|
||
AcquisitionRuntimeEvent
|
||
```
|
||
|
||
Runtime Service уже умел детерминированно исполнять инфраструктурные команды:
|
||
|
||
```text
|
||
ConnectCommand
|
||
DisconnectCommand
|
||
SubscribeCommand
|
||
UnsubscribeCommand
|
||
SendTextCommand
|
||
SendBinaryCommand
|
||
```
|
||
|
||
Однако Runtime по-прежнему оставался изолированным от Trade Stream Acquisition Pipeline.
|
||
|
||
В системе отсутствовал отдельный компонент, который:
|
||
|
||
- формировал команду подписки Trade Stream;
|
||
- передавал её в Acquisition Runtime;
|
||
- принимал один входящий WebSocket-документ;
|
||
- преобразовывал его через Unified Adapter;
|
||
- выделял канонический `Trade`;
|
||
- передавал `Trade` в Consistency Layer;
|
||
- возвращал согласованный результат `Trade | None`.
|
||
|
||
Главной задачей Build 060.23 стало создание локального интеграционного слоя:
|
||
|
||
```text
|
||
TradeStreamAcquisitionService
|
||
```
|
||
|
||
и его публичного контракта:
|
||
|
||
```text
|
||
TradeStreamAcquisitionServiceProtocol
|
||
```
|
||
|
||
После завершения Build система получает:
|
||
|
||
- единую сервисную точку подписки Trade Stream;
|
||
- единый путь обработки входящего Trade-сообщения;
|
||
- независимость сервиса от конкретной exchange-specific реализации адаптера;
|
||
- интеграцию с `TradeStreamConsistencyProtocol`;
|
||
- отдельное unit-покрытие координатора;
|
||
- интеграционные тесты полного локального Dzengi pipeline;
|
||
- подтверждённую регрессионную совместимость Runtime, Consistency, Recovery, Feeds и Dzengi Adapters.
|
||
|
||
При этом Build сознательно не реализует production WebSocket transport, receive-loop, reconnect и Runtime Recovery.
|
||
|
||
---
|
||
|
||
# Предпосылки
|
||
|
||
К началу настоящего Build были доступны следующие подсистемы.
|
||
|
||
## Runtime Layer
|
||
|
||
```text
|
||
AcquisitionRuntimeServiceProtocol
|
||
AcquisitionRuntimeService
|
||
AcquisitionRuntimeCommandDispatcherProtocol
|
||
AcquisitionRuntimeEventPublisherProtocol
|
||
```
|
||
|
||
## Trade Subscription Layer
|
||
|
||
```text
|
||
build_trade_subscribe_command()
|
||
```
|
||
|
||
## WebSocket Adapter Layer
|
||
|
||
```text
|
||
DzengiUnifiedWebSocketAdapter
|
||
DzengiWebSocketTradeAdapter
|
||
```
|
||
|
||
## Consistency Layer
|
||
|
||
```text
|
||
TradeStreamConsistencyProtocol
|
||
TradeStreamConsistencyController
|
||
TradeStreamStateStore
|
||
```
|
||
|
||
## Canonical Model
|
||
|
||
```text
|
||
Trade
|
||
```
|
||
|
||
Каждый компонент уже был реализован и протестирован отдельно.
|
||
|
||
Однако их взаимодействие не было оформлено в единый Acquisition Service.
|
||
|
||
Архитектура до Build выглядела следующим образом:
|
||
|
||
```text
|
||
Subscription Builder
|
||
│
|
||
▼
|
||
SubscribeCommand
|
||
│
|
||
▼
|
||
AcquisitionRuntimeService
|
||
```
|
||
|
||
и отдельно:
|
||
|
||
```text
|
||
WebSocket Document
|
||
│
|
||
▼
|
||
DzengiUnifiedWebSocketAdapter
|
||
│
|
||
▼
|
||
Trade
|
||
```
|
||
|
||
и отдельно:
|
||
|
||
```text
|
||
Trade
|
||
│
|
||
▼
|
||
TradeStreamConsistencyController
|
||
│
|
||
▼
|
||
Trade | None
|
||
```
|
||
|
||
Build 060.23 должен был соединить эти независимые части без изменения их существующего поведения.
|
||
|
||
---
|
||
|
||
# Результаты архитектурного аудита
|
||
|
||
Перед реализацией были проанализированы:
|
||
|
||
```text
|
||
src/market_data/acquisition/protocol.py
|
||
src/market_data/acquisition/service.py
|
||
src/market_data/acquisition/registry.py
|
||
src/market_data/acquisition/feeds/trades_feed.py
|
||
src/market_data/acquisition/subscriptions/trades.py
|
||
src/market_data/acquisition/adapters/dzengi/websocket.py
|
||
src/market_data/acquisition/runtime/acquisition_runtime_service.py
|
||
src/market_data/acquisition/runtime/acquisition_runtime_service_protocol.py
|
||
src/market_data/acquisition/consistency/trade_stream_protocol.py
|
||
src/market_data/acquisition/consistency/trade_stream_consistency_controller.py
|
||
src/market_data/acquisition/consistency/trade_stream_state_store.py
|
||
```
|
||
|
||
Дополнительно были изучены legacy-компоненты:
|
||
|
||
```text
|
||
src/integrations/exchange/market_stream.py
|
||
src/integrations/exchange/market_data_runner.py
|
||
src/integrations/exchange/ws_client.py
|
||
src/integrations/exchange/service.py
|
||
```
|
||
|
||
и соответствующие unit-тесты.
|
||
|
||
Аудит подтвердил следующие выводы.
|
||
|
||
---
|
||
|
||
## Существующий TradesFeed остаётся синхронным REST Feed
|
||
|
||
Текущий `TradesFeed` работает по модели:
|
||
|
||
```text
|
||
TradeDocumentSource
|
||
│
|
||
▼
|
||
TradeDocumentHandler
|
||
│
|
||
▼
|
||
Trade
|
||
```
|
||
|
||
Он не владеет:
|
||
|
||
- WebSocket lifecycle;
|
||
- Runtime Commands;
|
||
- активными подписками;
|
||
- receive-loop;
|
||
- reconnect;
|
||
- Consistency State.
|
||
|
||
Поэтому внедрение Runtime в существующий `TradesFeed` было признано нарушением Single Responsibility.
|
||
|
||
Build 060.23 не изменяет `TradesFeed`.
|
||
|
||
---
|
||
|
||
## Legacy MarketDataRunner не является точкой интеграции
|
||
|
||
`MarketDataRunner` уже содержит собственные:
|
||
|
||
- task lifecycle;
|
||
- reconnect-циклы;
|
||
- REST fallback;
|
||
- quote-specific runtime state;
|
||
- журналирование;
|
||
- status events.
|
||
|
||
Подключение нового Trade Stream Acquisition Service внутрь него смешало бы новую Acquisition Architecture с существующим legacy runtime и преждевременно затронуло бы Scope Build 060.24.
|
||
|
||
Поэтому `MarketDataRunner` не изменяется.
|
||
|
||
---
|
||
|
||
## Legacy market_stream.py не является Composition Root
|
||
|
||
`market_stream.py` представляет отдельный quote-specific WebSocket-контур.
|
||
|
||
Его запуск в `main.py` не является активной точкой новой Trade Stream Architecture.
|
||
|
||
Файл не изменяется и не используется как Composition Root Build 060.23.
|
||
|
||
---
|
||
|
||
## ExchangeWebSocketClient не реализует новые Runtime Protocol
|
||
|
||
`ExchangeWebSocketClient` является специализированным legacy-клиентом для depth stream.
|
||
|
||
Он:
|
||
|
||
- самостоятельно открывает соединение;
|
||
- самостоятельно отправляет depth request;
|
||
- самостоятельно выполняет ping;
|
||
- содержит внутренний цикл;
|
||
- возвращает raw document;
|
||
- жёстко связан со стаканом.
|
||
|
||
Следовательно он не является реализацией:
|
||
|
||
```text
|
||
WebSocketTransportProtocol
|
||
WebSocketSessionProtocol
|
||
WebSocketSubscriptionManagerProtocol
|
||
```
|
||
|
||
и не адаптируется в рамках Build 060.23.
|
||
|
||
---
|
||
|
||
## Trade consumer и Trade store отсутствуют
|
||
|
||
Repository-wide поиск не выявил утверждённых компонентов:
|
||
|
||
```text
|
||
TradeStore
|
||
TradeConsumer
|
||
publish_trade()
|
||
on_trade()
|
||
consume_trade()
|
||
```
|
||
|
||
Поэтому новый сервис не публикует и не сохраняет Trade.
|
||
|
||
Его результатом остаётся:
|
||
|
||
```text
|
||
Trade | None
|
||
```
|
||
|
||
---
|
||
|
||
## trades.unsubscribe не поддерживается биржей
|
||
|
||
Предыдущий инженерный аудит Dzengi WebSocket API подтвердил отсутствие поддерживаемой операции:
|
||
|
||
```text
|
||
trades.unsubscribe
|
||
```
|
||
|
||
Поэтому Build 060.23 не создаёт фиктивный exchange-specific unsubscribe builder.
|
||
|
||
Завершение Trade Stream в будущем должно выполняться через lifecycle соединения либо иной подтверждённый механизм.
|
||
|
||
---
|
||
|
||
# Архитектурное решение
|
||
|
||
По результатам аудита утверждена следующая схема.
|
||
|
||
```text
|
||
symbols
|
||
│
|
||
▼
|
||
TradeStreamAcquisitionService.subscribe()
|
||
│
|
||
▼
|
||
build_trade_subscribe_command()
|
||
│
|
||
▼
|
||
SubscribeCommand
|
||
│
|
||
▼
|
||
AcquisitionRuntimeServiceProtocol.dispatch()
|
||
```
|
||
|
||
Входящий поток:
|
||
|
||
```text
|
||
raw WebSocket document
|
||
│
|
||
▼
|
||
TradeStreamMessageAdapterProtocol.map_message()
|
||
│
|
||
▼
|
||
Quote | CandleCloseEvent | Trade
|
||
│
|
||
▼
|
||
isinstance(result, Trade)
|
||
│
|
||
├── нет ──► None
|
||
│
|
||
▼
|
||
TradeStreamConsistencyProtocol.accept()
|
||
│
|
||
▼
|
||
Trade | None
|
||
```
|
||
|
||
Новый сервис является stateless-координатором.
|
||
|
||
Он не создаёт транспорт, не запускает фоновые задачи и не хранит состояние Trade Stream.
|
||
|
||
---
|
||
|
||
# Правило уникальности имён файлов
|
||
|
||
Build 060.23 сохраняет обязательное правило проекта Dzentra:
|
||
|
||
> Новые файлы должны иметь уникальные имена в масштабе репозитория независимо от каталога.
|
||
|
||
Build не создаёт новые общие файлы:
|
||
|
||
```text
|
||
service.py
|
||
protocol.py
|
||
test_service.py
|
||
```
|
||
|
||
Вместо них добавлены уникальные имена:
|
||
|
||
```text
|
||
trade_stream_acquisition_protocol.py
|
||
trade_stream_acquisition_service.py
|
||
trade_stream_message_adapter_protocol.py
|
||
test_trade_stream_acquisition_service.py
|
||
test_dzengi_trade_stream_acquisition_integration.py
|
||
```
|
||
|
||
Имена однозначно отражают назначение файлов и не требуют знания родительского каталога.
|
||
|
||
---
|
||
|
||
# TradeStreamAcquisitionServiceProtocol
|
||
|
||
В рамках Build создан новый публичный контракт:
|
||
|
||
```text
|
||
TradeStreamAcquisitionServiceProtocol
|
||
```
|
||
|
||
Файл:
|
||
|
||
```text
|
||
src/market_data/acquisition/
|
||
trade_stream_acquisition_protocol.py
|
||
```
|
||
|
||
Protocol определяет две операции.
|
||
|
||
## subscribe()
|
||
|
||
```python
|
||
async def subscribe(
|
||
symbols: tuple[str, ...],
|
||
*,
|
||
correlation_id: str | None = None,
|
||
) -> None
|
||
```
|
||
|
||
Метод отвечает только за формирование и передачу команды подписки в Runtime.
|
||
|
||
## handle_message()
|
||
|
||
```python
|
||
def handle_message(
|
||
document: object,
|
||
) -> Trade | None
|
||
```
|
||
|
||
Метод отвечает только за обработку одного входящего документа.
|
||
|
||
Другие публичные операции не вводятся.
|
||
|
||
В частности отсутствуют:
|
||
|
||
```text
|
||
start()
|
||
stop()
|
||
run()
|
||
receive()
|
||
reconnect()
|
||
recover()
|
||
publish()
|
||
```
|
||
|
||
---
|
||
|
||
# TradeStreamMessageAdapterProtocol
|
||
|
||
Во время реализации было выявлено, что первоначальная зависимость сервиса от конкретного:
|
||
|
||
```text
|
||
DzengiUnifiedWebSocketAdapter
|
||
```
|
||
|
||
создавала прямую exchange-specific связь внутри координационного слоя.
|
||
|
||
Для устранения этой связи введён отдельный контракт:
|
||
|
||
```text
|
||
TradeStreamMessageAdapterProtocol
|
||
```
|
||
|
||
Файл:
|
||
|
||
```text
|
||
src/market_data/acquisition/
|
||
trade_stream_message_adapter_protocol.py
|
||
```
|
||
|
||
Protocol определяет одну операцию:
|
||
|
||
```python
|
||
def map_message(
|
||
document: object,
|
||
) -> TradeStreamMappedMessage
|
||
```
|
||
|
||
Тип результата:
|
||
|
||
```text
|
||
TradeStreamMappedMessage
|
||
```
|
||
|
||
объединяет:
|
||
|
||
```text
|
||
Quote
|
||
CandleCloseEvent
|
||
Trade
|
||
```
|
||
|
||
`DzengiUnifiedWebSocketAdapter` структурно удовлетворяет этому Protocol и не требует изменения.
|
||
|
||
Благодаря этому `TradeStreamAcquisitionService` зависит от абстракции, а конкретный Dzengi Adapter подключается только при композиции и в интеграционных тестах.
|
||
|
||
---
|
||
|
||
# TradeStreamAcquisitionService
|
||
|
||
Главным production-компонентом Build является:
|
||
|
||
```text
|
||
TradeStreamAcquisitionService
|
||
```
|
||
|
||
Файл:
|
||
|
||
```text
|
||
src/market_data/acquisition/
|
||
trade_stream_acquisition_service.py
|
||
```
|
||
|
||
Сервис реализует:
|
||
|
||
```text
|
||
TradeStreamAcquisitionServiceProtocol
|
||
```
|
||
|
||
и координирует три зависимости:
|
||
|
||
```text
|
||
AcquisitionRuntimeServiceProtocol
|
||
TradeStreamMessageAdapterProtocol
|
||
TradeStreamConsistencyProtocol
|
||
```
|
||
|
||
Сервис не создаёт зависимости самостоятельно.
|
||
|
||
---
|
||
|
||
# Зависимости сервиса
|
||
|
||
Конструктор принимает:
|
||
|
||
```text
|
||
runtime_service
|
||
adapter
|
||
consistency_controller
|
||
```
|
||
|
||
Полная схема:
|
||
|
||
```text
|
||
TradeStreamAcquisitionService
|
||
│
|
||
├── AcquisitionRuntimeServiceProtocol
|
||
├── TradeStreamMessageAdapterProtocol
|
||
└── TradeStreamConsistencyProtocol
|
||
```
|
||
|
||
Это обеспечивает:
|
||
|
||
- независимость от конкретного Runtime implementation;
|
||
- независимость от конкретной биржи;
|
||
- независимость от конкретной Consistency implementation;
|
||
- возможность изолированного unit-тестирования.
|
||
|
||
---
|
||
|
||
# subscribe()
|
||
|
||
Метод `subscribe()` использует существующий builder:
|
||
|
||
```text
|
||
build_trade_subscribe_command()
|
||
```
|
||
|
||
Последовательность:
|
||
|
||
```text
|
||
symbols
|
||
│
|
||
▼
|
||
build_trade_subscribe_command(
|
||
symbols,
|
||
correlation_id=...
|
||
)
|
||
│
|
||
▼
|
||
SubscribeCommand
|
||
│
|
||
▼
|
||
runtime_service.dispatch(command)
|
||
```
|
||
|
||
Сервис не:
|
||
|
||
- сериализует JSON;
|
||
- создаёт TransportTextMessage вручную;
|
||
- строит subscription key самостоятельно;
|
||
- нормализует символы;
|
||
- хранит активные подписки;
|
||
- выполняет connect;
|
||
- выполняет retry.
|
||
|
||
---
|
||
|
||
# correlation_id
|
||
|
||
Параметр:
|
||
|
||
```text
|
||
correlation_id
|
||
```
|
||
|
||
передаётся без изменения существующему Subscription Builder.
|
||
|
||
Если он не задан, его создание остаётся ответственностью builder.
|
||
|
||
Сервис не генерирует correlation id самостоятельно.
|
||
|
||
---
|
||
|
||
# handle_message()
|
||
|
||
Метод `handle_message()` обрабатывает один входящий документ.
|
||
|
||
Последовательность:
|
||
|
||
```text
|
||
document
|
||
│
|
||
▼
|
||
adapter.map_message(document)
|
||
│
|
||
▼
|
||
mapped result
|
||
│
|
||
▼
|
||
isinstance(mapped result, Trade)
|
||
```
|
||
|
||
Если результат не является `Trade`, сервис возвращает:
|
||
|
||
```text
|
||
None
|
||
```
|
||
|
||
Если результат является `Trade`, сервис вызывает:
|
||
|
||
```text
|
||
consistency_controller.accept(trade)
|
||
```
|
||
|
||
и возвращает результат без изменения.
|
||
|
||
---
|
||
|
||
# Поведение для Quote
|
||
|
||
Если Adapter возвращает:
|
||
|
||
```text
|
||
Quote
|
||
```
|
||
|
||
сервис:
|
||
|
||
- не вызывает Consistency;
|
||
- не преобразует Quote;
|
||
- не публикует Quote;
|
||
- возвращает `None`.
|
||
|
||
---
|
||
|
||
# Поведение для CandleCloseEvent
|
||
|
||
Если Adapter возвращает:
|
||
|
||
```text
|
||
CandleCloseEvent
|
||
```
|
||
|
||
сервис:
|
||
|
||
- не вызывает Consistency;
|
||
- не преобразует CandleCloseEvent;
|
||
- не публикует событие;
|
||
- возвращает `None`.
|
||
|
||
---
|
||
|
||
# Поведение для Trade
|
||
|
||
Если Adapter возвращает:
|
||
|
||
```text
|
||
Trade
|
||
```
|
||
|
||
сервис передаёт тот же экземпляр в:
|
||
|
||
```text
|
||
TradeStreamConsistencyProtocol.accept()
|
||
```
|
||
|
||
Сервис не копирует и не изменяет модель.
|
||
|
||
---
|
||
|
||
# Поведение для дубликата
|
||
|
||
Если Consistency Layer возвращает:
|
||
|
||
```text
|
||
None
|
||
```
|
||
|
||
это означает корректный дубликат.
|
||
|
||
Сервис сохраняет результат и также возвращает:
|
||
|
||
```text
|
||
None
|
||
```
|
||
|
||
Дополнительная логика не выполняется.
|
||
|
||
---
|
||
|
||
# Сохранение идентичности результата Consistency
|
||
|
||
Если `TradeStreamConsistencyProtocol.accept()` возвращает отдельный экземпляр `Trade`, сервис возвращает именно этот объект.
|
||
|
||
Сервис не заменяет его исходным результатом Adapter и не создаёт копию.
|
||
|
||
Данный инвариант подтверждён unit-тестом.
|
||
|
||
---
|
||
|
||
# Обработка исключений
|
||
|
||
Build не вводит отдельную иерархию ошибок Trade Stream Acquisition Service.
|
||
|
||
Исключения распространяются без обёртки.
|
||
|
||
## Ошибка Subscription Builder
|
||
|
||
Ошибка формирования SubscribeCommand передаётся вызывающей стороне.
|
||
|
||
## Ошибка Runtime dispatch
|
||
|
||
Ошибка `runtime_service.dispatch()` передаётся вызывающей стороне.
|
||
|
||
## Ошибка Adapter
|
||
|
||
Schema, parser, value validation или mapper error распространяется без изменения.
|
||
|
||
## Ошибка Consistency
|
||
|
||
`TradeConsistencyError`, `TradeOrderingError` и иные исключения Consistency Layer распространяются без изменения.
|
||
|
||
Сервис не создаёт общий `TradeStreamAcquisitionError`.
|
||
|
||
---
|
||
|
||
# Stateless-архитектура
|
||
|
||
`TradeStreamAcquisitionService` не хранит операционное состояние.
|
||
|
||
В объекте сохраняются только внедрённые зависимости.
|
||
|
||
Сервис не хранит:
|
||
|
||
- symbols;
|
||
- correlation id;
|
||
- active subscriptions;
|
||
- last trade;
|
||
- duplicate window;
|
||
- reconnect attempts;
|
||
- recovery state;
|
||
- runtime status;
|
||
- received documents.
|
||
|
||
Состояние Consistency принадлежит `TradeStreamStateStore`.
|
||
|
||
Состояние Runtime будет принадлежать будущим Session и Subscription Manager.
|
||
|
||
---
|
||
|
||
# Изменённые и добавленные файлы
|
||
|
||
## Новый Acquisition Service Protocol
|
||
|
||
```text
|
||
src/market_data/acquisition/
|
||
trade_stream_acquisition_protocol.py
|
||
```
|
||
|
||
Добавлен:
|
||
|
||
```text
|
||
TradeStreamAcquisitionServiceProtocol
|
||
```
|
||
|
||
---
|
||
|
||
## Новый Trade Stream Message Adapter Protocol
|
||
|
||
```text
|
||
src/market_data/acquisition/
|
||
trade_stream_message_adapter_protocol.py
|
||
```
|
||
|
||
Добавлены:
|
||
|
||
```text
|
||
TradeStreamMappedMessage
|
||
TradeStreamMessageAdapterProtocol
|
||
```
|
||
|
||
---
|
||
|
||
## Новый Trade Stream Acquisition Service
|
||
|
||
```text
|
||
src/market_data/acquisition/
|
||
trade_stream_acquisition_service.py
|
||
```
|
||
|
||
Добавлен:
|
||
|
||
```text
|
||
TradeStreamAcquisitionService
|
||
```
|
||
|
||
---
|
||
|
||
## Unit-тест Acquisition Service
|
||
|
||
```text
|
||
tests/unit/market_data/acquisition/
|
||
test_trade_stream_acquisition_service.py
|
||
```
|
||
|
||
Добавлены Fake-реализации:
|
||
|
||
- `FakeRuntimeService`;
|
||
- `FakeMessageAdapter`;
|
||
- `FakeConsistencyController`.
|
||
|
||
Проверены subscribe и handle_message без привязки к Dzengi.
|
||
|
||
---
|
||
|
||
## Интеграционный тест Dzengi pipeline
|
||
|
||
```text
|
||
tests/unit/market_data/acquisition/
|
||
test_dzengi_trade_stream_acquisition_integration.py
|
||
```
|
||
|
||
Проверен реальный локальный pipeline:
|
||
|
||
```text
|
||
Dzengi WebSocket document
|
||
│
|
||
▼
|
||
DzengiUnifiedWebSocketAdapter
|
||
│
|
||
▼
|
||
Canonical Trade
|
||
│
|
||
▼
|
||
TradeStreamConsistencyController
|
||
│
|
||
▼
|
||
Trade | None
|
||
```
|
||
|
||
---
|
||
|
||
## Архитектурная документация
|
||
|
||
```text
|
||
docs/migrations/
|
||
build_060_23_architecture.md
|
||
```
|
||
|
||
Документ фиксирует:
|
||
|
||
- границы Build;
|
||
- ответственность сервиса;
|
||
- dependency direction;
|
||
- ADR;
|
||
- Definition of Done;
|
||
- связь с последующими этапами.
|
||
|
||
---
|
||
|
||
# Файлы, которые не изменялись
|
||
|
||
Build не изменяет:
|
||
|
||
```text
|
||
src/market_data/acquisition/feeds/trades_feed.py
|
||
src/market_data/acquisition/subscriptions/trades.py
|
||
src/market_data/acquisition/adapters/dzengi/websocket.py
|
||
src/market_data/acquisition/runtime/acquisition_runtime_service.py
|
||
src/market_data/acquisition/runtime/acquisition_runtime_service_protocol.py
|
||
src/market_data/acquisition/consistency/trade_stream_protocol.py
|
||
src/market_data/acquisition/consistency/trade_stream_consistency_controller.py
|
||
src/market_data/acquisition/consistency/trade_stream_state_store.py
|
||
src/market_data/acquisition/recovery/trade_recovery_controller.py
|
||
```
|
||
|
||
Также не изменяются:
|
||
|
||
```text
|
||
src/integrations/exchange/market_stream.py
|
||
src/integrations/exchange/market_data_runner.py
|
||
src/integrations/exchange/ws_client.py
|
||
src/integrations/exchange/service.py
|
||
```
|
||
|
||
Build не затрагивает legacy quote runtime.
|
||
|
||
---
|
||
|
||
# Unit-тестирование TradeStreamAcquisitionService
|
||
|
||
Новый сервис покрыт отдельным набором unit-тестов.
|
||
|
||
Проверены следующие сценарии:
|
||
|
||
- сервис соответствует `TradeStreamAcquisitionServiceProtocol`;
|
||
- `subscribe()` передаёт одну команду Runtime;
|
||
- передаётся именно `SubscribeCommand`;
|
||
- symbols сохраняются в сформированной команде;
|
||
- `correlation_id` сохраняется в payload;
|
||
- Adapter вызывается один раз;
|
||
- Trade передаётся в Consistency;
|
||
- возвращается тот же объект, который вернула Consistency;
|
||
- `None` для корректного дубликата сохраняется;
|
||
- Quote игнорируется;
|
||
- CandleCloseEvent игнорируется;
|
||
- Consistency не вызывается для не-Trade;
|
||
- ошибка Adapter распространяется без изменения;
|
||
- ошибка Consistency распространяется без изменения.
|
||
|
||
Результат совместного локального прогона unit и integration тестов:
|
||
|
||
```text
|
||
14 passed
|
||
```
|
||
|
||
---
|
||
|
||
# Интеграционное тестирование Dzengi pipeline
|
||
|
||
Интеграционный тест использует настоящий:
|
||
|
||
```text
|
||
DzengiUnifiedWebSocketAdapter
|
||
```
|
||
|
||
и документ реального WebSocket Trade-контракта:
|
||
|
||
```text
|
||
status
|
||
destination
|
||
payload
|
||
```
|
||
|
||
Проверяется полный путь:
|
||
|
||
```text
|
||
schema validation
|
||
│
|
||
▼
|
||
parser
|
||
│
|
||
▼
|
||
value validation
|
||
│
|
||
▼
|
||
mapper
|
||
│
|
||
▼
|
||
Trade
|
||
│
|
||
▼
|
||
Consistency
|
||
```
|
||
|
||
Подтверждены канонические поля:
|
||
|
||
```text
|
||
symbol
|
||
trade_id
|
||
price
|
||
quantity
|
||
executed_at
|
||
aggressor_side
|
||
source
|
||
```
|
||
|
||
Для WebSocket Trade подтверждён источник:
|
||
|
||
```text
|
||
dzengi_websocket_trade
|
||
```
|
||
|
||
Также реальный `TradeStreamConsistencyController` подтвердил, что повторная обработка идентичного Trade возвращает:
|
||
|
||
```text
|
||
None
|
||
```
|
||
|
||
---
|
||
|
||
# Расширенное регрессионное тестирование
|
||
|
||
После завершения реализации выполнен совместный прогон:
|
||
|
||
```text
|
||
Runtime
|
||
Consistency
|
||
Recovery
|
||
Feeds
|
||
Dzengi Adapters
|
||
Trade Stream Acquisition Unit Tests
|
||
Dzengi Trade Stream Acquisition Integration Tests
|
||
```
|
||
|
||
Охвачены каталоги:
|
||
|
||
```text
|
||
tests/unit/market_data/acquisition/runtime
|
||
tests/unit/market_data/acquisition/consistency
|
||
tests/unit/market_data/acquisition/recovery
|
||
tests/unit/market_data/acquisition/feeds
|
||
tests/unit/market_data/acquisition/adapters/dzengi
|
||
```
|
||
|
||
и новые файлы:
|
||
|
||
```text
|
||
tests/unit/market_data/acquisition/
|
||
test_trade_stream_acquisition_service.py
|
||
|
||
tests/unit/market_data/acquisition/
|
||
test_dzengi_trade_stream_acquisition_integration.py
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
496 passed
|
||
```
|
||
|
||
Тем самым подтверждено:
|
||
|
||
- Runtime Service не нарушен;
|
||
- Runtime Protocol Layer не нарушен;
|
||
- Trade Stream Consistency не нарушена;
|
||
- TradeStreamStateStore не нарушен;
|
||
- Trade Recovery не нарушена;
|
||
- существующие Feeds не нарушены;
|
||
- REST Trade Adapter не нарушен;
|
||
- WebSocket Quote Adapter не нарушен;
|
||
- WebSocket OHLC Adapter не нарушен;
|
||
- WebSocket Trade Adapter не нарушен;
|
||
- Unified WebSocket Adapter не нарушен;
|
||
- новый Acquisition Service корректно интегрируется с реальным Dzengi Adapter;
|
||
- duplicate filtering работает через реальный Consistency Controller.
|
||
|
||
---
|
||
|
||
# Проверка качества Git diff
|
||
|
||
После реализации выполнены:
|
||
|
||
```text
|
||
git diff --stat
|
||
git diff --check
|
||
```
|
||
|
||
`git diff --check` не выявил ошибок форматирования в отслеживаемых изменениях.
|
||
|
||
Новые production и test-файлы на момент проверки оставались untracked, поэтому не отображались в `git diff --stat`.
|
||
|
||
Изменение:
|
||
|
||
```text
|
||
.gitignore
|
||
```
|
||
|
||
не относится к Scope Build 060.23 и не должно включаться в коммит этапа.
|
||
|
||
---
|
||
|
||
# Обратная совместимость
|
||
|
||
Build сохраняет существующее поведение всех ранее реализованных подсистем.
|
||
|
||
Не изменились:
|
||
|
||
- `TradesFeedProtocol`;
|
||
- `TradesFeed`;
|
||
- `TradeDocumentSource`;
|
||
- `TradeDocumentHandler`;
|
||
- `Trade` model;
|
||
- Runtime Commands;
|
||
- Runtime Events;
|
||
- Acquisition Runtime Service;
|
||
- Trade Subscription Builder;
|
||
- Unified Dzengi Adapter;
|
||
- Trade Stream Consistency API;
|
||
- Trade Recovery API;
|
||
- Feed Registry;
|
||
- legacy Exchange Runtime.
|
||
|
||
Новый сервис добавляется как отдельный слой и не заменяет существующие REST Feed.
|
||
|
||
---
|
||
|
||
# Производительность
|
||
|
||
## subscribe()
|
||
|
||
Метод выполняет:
|
||
|
||
- один вызов Subscription Builder;
|
||
- один асинхронный вызов Runtime dispatch.
|
||
|
||
Алгоритмическая сложность зависит от существующего builder и количества symbols.
|
||
|
||
Сервис не создаёт дополнительных копий команд и payload.
|
||
|
||
## handle_message()
|
||
|
||
Метод выполняет:
|
||
|
||
- один вызов Adapter;
|
||
- одну `isinstance`-проверку;
|
||
- не более одного вызова Consistency.
|
||
|
||
Координационные накладные расходы постоянны:
|
||
|
||
```text
|
||
O(1)
|
||
```
|
||
|
||
без учёта внутренней стоимости Adapter и Consistency Layer.
|
||
|
||
Сервис не вводит:
|
||
|
||
- очереди;
|
||
- блокировки;
|
||
- фоновые задачи;
|
||
- retries;
|
||
- дополнительную сериализацию;
|
||
- storage writes.
|
||
|
||
---
|
||
|
||
# Подтверждённые архитектурные инварианты
|
||
|
||
## Runtime не знает о Trade
|
||
|
||
Новые зависимости направлены из Acquisition Service в Runtime Protocol, а не обратно.
|
||
|
||
---
|
||
|
||
## TradeStreamAcquisitionService не зависит от Dzengi
|
||
|
||
Production-сервис зависит от:
|
||
|
||
```text
|
||
TradeStreamMessageAdapterProtocol
|
||
```
|
||
|
||
а не от конкретного `DzengiUnifiedWebSocketAdapter`.
|
||
|
||
---
|
||
|
||
## Dzengi Adapter проверяется отдельно
|
||
|
||
Exchange-specific корректность подтверждена интеграционным тестом.
|
||
|
||
---
|
||
|
||
## Consistency остаётся единственной точкой проверки последовательности
|
||
|
||
Сервис не реализует duplicate detection или ordering logic самостоятельно.
|
||
|
||
---
|
||
|
||
## TradeStreamStateStore остаётся единственным владельцем Consistency State
|
||
|
||
Новый сервис не хранит состояния по symbols.
|
||
|
||
---
|
||
|
||
## Существующий TradesFeed не изменяется
|
||
|
||
REST Feed и WebSocket Acquisition Integration остаются отдельными путями.
|
||
|
||
---
|
||
|
||
## Сервис не публикует Trade
|
||
|
||
До появления утверждённого Trade Consumer результат возвращается вызывающей стороне.
|
||
|
||
---
|
||
|
||
## Сервис не владеет WebSocket lifecycle
|
||
|
||
Он не реализует connect, disconnect, receive-loop или reconnect.
|
||
|
||
---
|
||
|
||
## trades.unsubscribe не создаётся
|
||
|
||
Build не вводит неподдерживаемую exchange-команду.
|
||
|
||
---
|
||
|
||
## Уникальность новых имён файлов соблюдена
|
||
|
||
Все новые production и test-файлы имеют уникальные имена.
|
||
|
||
---
|
||
|
||
# Что не входит в Scope Build
|
||
|
||
Build сознательно не реализует:
|
||
|
||
- production `WebSocketTransportProtocol`;
|
||
- production `WebSocketSessionProtocol`;
|
||
- production `WebSocketSubscriptionManagerProtocol`;
|
||
- Runtime Event Publisher implementation;
|
||
- Runtime Supervisor;
|
||
- receive-loop;
|
||
- reconnect;
|
||
- heartbeat;
|
||
- scheduler;
|
||
- resubscribe orchestration;
|
||
- автоматический вызов Trade Recovery;
|
||
- восстановление Trade Stream после разрыва;
|
||
- Trade Store;
|
||
- Trade Consumer;
|
||
- публикацию Trade;
|
||
- root Composition;
|
||
- изменение `MarketDataRunner`;
|
||
- изменение `market_stream.py`;
|
||
- изменение `ExchangeWebSocketClient`;
|
||
- изменение существующего `TradesFeed`;
|
||
- биржевую команду `trades.unsubscribe`.
|
||
|
||
Отсутствие перечисленных компонентов является осознанной границей этапа.
|
||
|
||
---
|
||
|
||
# Архитектурное значение Build
|
||
|
||
Build 060.23 впервые соединяет ранее независимые части Trades Acquisition Architecture:
|
||
|
||
```text
|
||
Subscription Builder
|
||
Runtime Service
|
||
Unified Adapter
|
||
Canonical Trade
|
||
Consistency Controller
|
||
```
|
||
|
||
До Build каждый компонент существовал отдельно.
|
||
|
||
После завершения Build появляется единая сервисная граница:
|
||
|
||
```text
|
||
TradeStreamAcquisitionService
|
||
```
|
||
|
||
которая поддерживает оба направления интеграции:
|
||
|
||
```text
|
||
outbound subscription command
|
||
```
|
||
|
||
и:
|
||
|
||
```text
|
||
inbound Trade message processing
|
||
```
|
||
|
||
При этом сервис остаётся stateless, exchange-independent и не вмешивается в lifecycle Runtime.
|
||
|
||
---
|
||
|
||
# Связь с последующими Build
|
||
|
||
## Build 060.24 — Reconnect & Runtime Recovery
|
||
|
||
На основе текущего сервиса должны быть построены:
|
||
|
||
- Runtime reconnect orchestration;
|
||
- автоматическое восстановление подписок;
|
||
- восстановление Trade Stream continuity;
|
||
- координация Consistency и Recovery после разрыва;
|
||
- lifecycle соединения.
|
||
|
||
---
|
||
|
||
## Build 060.25 — Integration & Regression
|
||
|
||
Будет выполнена полная проверка production graph:
|
||
|
||
```text
|
||
Runtime
|
||
Trade Stream Acquisition
|
||
Consistency
|
||
Recovery
|
||
Trades Feed
|
||
```
|
||
|
||
после появления реальных Runtime implementations.
|
||
|
||
---
|
||
|
||
## Build 060.26 — Final Documentation
|
||
|
||
Будет подготовлена итоговая документация всей ветки Trades Feed Runtime.
|
||
|
||
---
|
||
|
||
# Заключение
|
||
|
||
Build 060.23 завершает service-level интеграцию Trade Stream с Acquisition Runtime.
|
||
|
||
В рамках этапа реализованы:
|
||
|
||
```text
|
||
TradeStreamAcquisitionServiceProtocol
|
||
TradeStreamMessageAdapterProtocol
|
||
TradeStreamAcquisitionService
|
||
```
|
||
|
||
Новый сервис:
|
||
|
||
- формирует Trade SubscribeCommand через существующий builder;
|
||
- передаёт команду в `AcquisitionRuntimeServiceProtocol`;
|
||
- обрабатывает один WebSocket-документ через Adapter Protocol;
|
||
- пропускает только канонический `Trade`;
|
||
- делегирует проверку последовательности Consistency Layer;
|
||
- возвращает `Trade | None`;
|
||
- не хранит состояние;
|
||
- не содержит exchange-specific логики;
|
||
- не реализует reconnect и Recovery.
|
||
|
||
Unit-тесты подтверждают поведение координатора независимо от биржи.
|
||
|
||
Интеграционные тесты подтверждают полный локальный Dzengi pipeline и duplicate filtering через реальный Consistency Controller.
|
||
|
||
Расширенная регрессия завершена результатом:
|
||
|
||
```text
|
||
496 passed
|
||
```
|
||
|
||
---
|
||
|
||
# Итог Build
|
||
|
||
После завершения Build 060.23 система обладает следующими возможностями.
|
||
|
||
✓ Создан `TradeStreamAcquisitionServiceProtocol`.
|
||
|
||
✓ Создан `TradeStreamMessageAdapterProtocol`.
|
||
|
||
✓ Реализован `TradeStreamAcquisitionService`.
|
||
|
||
✓ Подписка Trade Stream проходит через существующий Subscription Builder.
|
||
|
||
✓ SubscribeCommand передаётся через `AcquisitionRuntimeServiceProtocol`.
|
||
|
||
✓ Входящие сообщения проходят через Adapter Protocol.
|
||
|
||
✓ `DzengiUnifiedWebSocketAdapter` структурно совместим с новым Protocol.
|
||
|
||
✓ Только `Trade` передаётся в Consistency.
|
||
|
||
✓ Quote и CandleCloseEvent игнорируются без побочных эффектов.
|
||
|
||
✓ Дубликат возвращается как `None`.
|
||
|
||
✓ Исключения Adapter и Consistency распространяются без изменения.
|
||
|
||
✓ Проверена идентичность результата Consistency.
|
||
|
||
✓ Реальный Dzengi WebSocket Trade document проходит полный pipeline.
|
||
|
||
✓ Реальный Consistency Controller отклоняет идентичный дубликат.
|
||
|
||
✓ Существующий `TradesFeed` не изменён.
|
||
|
||
✓ Legacy Exchange Runtime не изменён.
|
||
|
||
✓ Все новые файлы имеют уникальные имена.
|
||
|
||
✓ Локальный функциональный прогон завершён результатом `14 passed`.
|
||
|
||
✓ Расширенный regression завершён результатом `496 passed`.
|
||
|
||
Build **060.23 — Trade Stream Acquisition Integration** считается полностью завершённым и готовым к фиксации в Git.
|