Files
dzentra_bot/docs/migrations/build_060_23.md

1440 lines
37 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](build_060_23_architecture.md) —
архитектурная спецификация Build.
- [Build 060.22 Engineering Migration Report](build_060_22.md) —
предыдущий Build.
- [Build 060.22 Architecture](build_060_22_architecture.md) —
спецификация Acquisition Runtime Service.
- [Build 060.20.1 Engineering Migration Report](build_060_20_1.md) —
корректирующий Build владения состоянием Trade Stream.
- [Build 060.19 Engineering Migration Report](build_060_19.md) —
Trade Recovery.
- [Build 060.18 Engineering Migration Report](build_060_18.md) —
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
> **Ретроспективное уточнение 060.30.5 (2026-08-03).**
>
> Названия ниже сохраняют ранний прогноз. Фактически
> [060.24](build_060_24.md) завершил внутреннюю Runtime Recovery,
> [060.25](build_060_25.md) — Production Runtime Integration,
> [060.26](build_060_26.md) — Integration & Regression,
> [060.27](build_060_27.md)[060.29](build_060_29.md) —
> Storage/Checkpoint/Access, а Final Documentation выполняется в
> [060.30](build_060_30_architecture.md).
> Актуальная последовательность:
> [Master Roadmap](../roadmap/master-roadmap.md).
> После 060.30 утверждён только 061.00; номера следующих Feed-веток ещё
> не назначены.
## 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.