Files
dzentra_bot/docs/migrations/build_030.md

630 lines
17 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 030 — Quotes Feed и регистрация в Acquisition Service
**Статус:** Завершён
**Подсистема:** Market Data Acquisition
**Вертикаль:** Quotes Feed
**Проект:** Dzentra
**Тип изменения:** Архитектурная миграция без изменения поведения legacy runtime
---
## 1. Цель Build
Цель Build 030 — собрать ранее реализованные компоненты Quotes Feed в завершённую прикладную цепочку получения канонической котировки и зарегистрировать эту цепочку в слое Acquisition Service.
Build должен обеспечить следующий поток данных:
```text
Dzengi REST /api/v1/ticker/24hr
DzengiQuoteDocumentSource
DzengiQuoteDocumentHandler
QuotesFeed
QuoteFeedRegistry
QuoteAcquisitionService
Quote
```
На данном этапе новый Quotes Feed существует параллельно с legacy-контуром и ещё не подключается к `ExchangeService`, `MarketPriceCache`, market runtime, UI или Execution.
---
## 2. Предпосылки
К началу Build 030 были завершены предыдущие этапы:
```text
Build 027 — Каноническая модель Quote и специализированные контракты
Build 028 — Dzengi REST quote models, parser и validation
Build 029 — Dzengi mapper и Quotes Handler
```
В результате уже существовали:
- каноническая модель `Quote`;
- контракт `QuoteDocumentSource`;
- контракт `QuoteDocumentHandler`;
- контракт `QuoteFeedProtocol`;
- транспортная модель ответа Dzengi;
- schema validation;
- parser;
- value validation;
- mapper;
- `DzengiQuoteDocumentHandler`;
- специализированные исключения Quotes Feed.
Не хватало orchestration-слоя, связывающего эти компоненты в завершённый pipeline.
---
## 3. Границы Build
В Build 030 изменены следующие production-файлы:
```text
src/market_data/acquisition/adapters/dzengi/rest.py
src/market_data/acquisition/feeds/quotes_feed.py
src/market_data/acquisition/registry.py
src/market_data/acquisition/service.py
```
Добавлен новый файл тестов:
```text
tests/unit/market_data/acquisition/feeds/test_quotes_feed.py
```
Расширены существующие тесты:
```text
tests/unit/market_data/acquisition/adapters/dzengi/test_rest.py
tests/unit/market_data/acquisition/test_registry.py
tests/unit/market_data/acquisition/test_service.py
```
Следующие компоненты намеренно не изменялись:
```text
src/integrations/exchange/service.py
src/integrations/exchange/market_cache.py
src/integrations/exchange/market_stream.py
src/integrations/exchange/market_data_runner.py
```
Также не изменялись:
- UI-потребители;
- Execution-потребители;
- торговые стратегии;
- runtime-контур;
- Quote Store;
- legacy market snapshot dict layer.
Эти изменения относятся к следующим Build.
---
## 4. Реализованная архитектура
### 4.1. REST source
В файле:
```text
src/market_data/acquisition/adapters/dzengi/rest.py
```
реализован специализированный источник:
```python
DzengiQuoteDocumentSource
```
Его ответственность ограничена получением сырого транспортного документа текущей котировки.
Целевая операция:
```python
fetch_quote_document(symbol: str) -> object
```
Источник выполняет запрос:
```text
GET /api/v1/ticker/24hr
```
с параметрами:
```python
{
"symbol": symbol,
}
```
REST source:
- принимает торговый символ;
- передаёт его REST-клиенту без изменения;
- получает декодированный транспортный документ;
- возвращает исходный payload;
- преобразует транспортные ошибки в специализированную ошибку Quotes Feed.
REST source не выполняет:
- schema validation;
- parsing;
- value validation;
- mapping;
- кэширование;
- retry;
- нормализацию торгового символа.
---
## 5. Quotes Feed
В файле:
```text
src/market_data/acquisition/feeds/quotes_feed.py
```
реализован:
```python
QuotesFeed
```
Основная операция:
```python
load_quote(symbol: str) -> Quote
```
Внутренняя последовательность:
```text
symbol
QuoteDocumentSource.fetch_quote_document(symbol)
raw document
QuoteDocumentHandler.handle_quote_document(document)
Quote
```
`QuotesFeed` является orchestration-компонентом и не дублирует обязанности других слоёв.
Он не выполняет:
- транспортные запросы самостоятельно;
- schema validation;
- parsing;
- value validation;
- mapping;
- нормализацию символа;
- retry;
- кэширование;
- сохранение в Store;
- обращение к `ExchangeService`.
Ошибки source и handler не переоборачиваются повторно.
---
## 6. Quote Feed Registry
В файле:
```text
src/market_data/acquisition/registry.py
```
добавлен отдельный реестр:
```python
QuoteFeedRegistry
```
Существующий:
```python
InstrumentFeedRegistry
```
сохранён без архитектурного объединения с Quotes Feed.
Это позволяет:
- не изменять стабильный Instrument Reference Data contour;
- сохранить изоляцию вертикалей Acquisition;
- минимизировать область регрессии;
- избежать преждевременной универсализации registry.
`QuoteFeedRegistry` обеспечивает:
- регистрацию `QuoteFeedProtocol`;
- получение зарегистрированного Feed по имени источника;
- нормализацию внешних пробелов имени источника;
- запрет пустого имени;
- запрет повторной регистрации;
- runtime-проверку соответствия `QuoteFeedProtocol`;
- сохранение identity зарегистрированного объекта.
Ошибки registry представлены специализированным типом:
```python
QuoteFeedRegistryError
```
---
## 7. Quote Acquisition Service
В файле:
```text
src/market_data/acquisition/service.py
```
добавлен отдельный прикладной сервис:
```python
QuoteAcquisitionService
```
Основная операция:
```python
load_quote(
source_name: str,
symbol: str,
) -> Quote
```
Внутренняя последовательность:
```text
source_name
QuoteFeedRegistry.get(source_name)
QuoteFeedProtocol
load_quote(symbol)
Quote
```
Сервис:
- выбирает Feed через registry;
- передаёт `symbol` выбранному Feed без изменения;
- возвращает канонический `Quote`;
- не копирует полученную модель;
- не выполняет retry;
- не перехватывает и не переоборачивает ошибки registry или Feed.
Существующий:
```python
InstrumentAcquisitionService
```
не изменяет свою ответственность и продолжает обслуживать Instrument Reference Data.
---
## 8. Dependency Injection
В Build 030 сохранён уже применяемый в Instrument Reference Data подход явной сборки зависимостей.
Пример архитектурной сборки:
```python
source = DzengiQuoteDocumentSource(...)
handler = DzengiQuoteDocumentHandler(...)
feed = QuotesFeed(
source=source,
handler=handler,
)
registry = QuoteFeedRegistry()
registry.register("dzengi", feed)
service = QuoteAcquisitionService(
registry=registry,
)
```
В Build намеренно не добавлены:
- глобальный singleton registry;
- автоматическая регистрация при импорте;
- скрытая сборка production pipeline внутри `QuoteAcquisitionService`;
- глобальное mutable-состояние для Feed.
Такое решение сохраняет:
- dependency injection;
- тестируемость;
- явные зависимости;
- изоляцию composition root от application service.
Фактическое подключение production pipeline к legacy facade отложено до Build 031.
---
## 9. Ответственности компонентов
| Компонент | Ответственность |
|---|---|
| `DzengiQuoteDocumentSource` | Получение сырого REST-документа котировки |
| `DzengiQuoteDocumentHandler` | Полная обработка документа до канонической модели |
| `QuotesFeed` | Оркестрация source → handler |
| `QuoteFeedRegistry` | Регистрация и выбор Quotes Feed |
| `QuoteAcquisitionService` | Прикладная точка получения `Quote` через выбранный Feed |
| `Quote` | Каноническое внутреннее представление текущей котировки |
---
## 10. Полная цепочка обработки
После завершения Build 030 REST Quotes Feed имеет следующую структуру:
```text
GET /api/v1/ticker/24hr
DzengiQuoteDocumentSource
raw object
DzengiQuoteDocumentHandler
validate_dzengi_quote_schema()
parse_dzengi_quote_document()
DzengiQuotePayload
validate_dzengi_quote_values()
map_dzengi_quote()
Quote
QuotesFeed
QuoteFeedRegistry
QuoteAcquisitionService
```
Таким образом, транспортный формат Dzengi полностью изолирован от внешних потребителей Acquisition.
---
## 11. Архитектурные ограничения
Build 030 намеренно не реализует следующие функции:
```text
ExchangeService facade integration
Quote Store
MarketPriceCache migration
WebSocket quote parsing
market runtime migration
read-only consumer migration
UI consumer migration
Execution consumer migration
legacy TickerPrice removal
legacy market snapshot dict removal
MarketPriceCache removal
```
Они относятся к следующим этапам:
```text
Build 031 — Подключение нового REST Quotes Feed под legacy ExchangeService facade
Build 032 — Канонический Quote Store
Build 033 — Перенос MarketPriceCache на Quote Store
Build 034 — Dzengi WebSocket quote parsing и адаптер
Build 035 — Перевод market runtime на Quotes Feed
Build 036 — Перевод read-only и UI-потребителей
Build 037 — Перевод execution-потребителей
Build 038 — Удаление legacy TickerPrice и market snapshot dict layer
Build 039 — Удаление legacy quote parsing и MarketPriceCache
Build 040 — Финальная архитектурная проверка Quotes Feed
```
---
## 12. Тестовое покрытие
Build 030 покрывает следующие сценарии.
### 12.1. REST source
Проверяется:
- использование endpoint `/api/v1/ticker/24hr`;
- передача `symbol` в query parameters;
- возврат исходного payload;
- однократный вызов REST-клиента;
- поддержка dependency injection REST-клиента;
- создание стандартного REST-клиента при отсутствии injected client;
- преобразование транспортной ошибки в `QuoteTransportError`;
- сохранение исходной ошибки через `__cause__`.
### 12.2. Quotes Feed
Проверяется:
- соответствие `QuoteFeedProtocol`;
- однократный вызов source;
- передача `symbol` без изменения;
- однократный вызов handler;
- передача исходного документа handler без изменения;
- возврат `Quote` без копирования;
- отсутствие retry;
- отсутствие повторного переоборачивания ошибок.
### 12.3. Quote Feed Registry
Проверяется:
- регистрация корректного Feed;
- получение Feed по имени;
- нормализация внешних пробелов имени;
- запрет пустого имени;
- запрет повторной регистрации;
- проверка соответствия `QuoteFeedProtocol`;
- сохранение identity объекта;
- специализированные ошибки registry.
### 12.4. Quote Acquisition Service
Проверяется:
- передача `source_name` registry;
- передача `symbol` Feed без изменения;
- однократное обращение к registry;
- однократный вызов Feed;
- возврат `Quote` без копирования;
- сохранение ошибок registry;
- сохранение ошибок Feed;
- отсутствие retry.
---
## 13. Проверка компиляции
Выполнена команда:
```bash
python -m py_compile \
src/market_data/acquisition/adapters/dzengi/rest.py \
src/market_data/acquisition/feeds/quotes_feed.py \
src/market_data/acquisition/registry.py \
src/market_data/acquisition/service.py \
tests/unit/market_data/acquisition/adapters/dzengi/test_rest.py \
tests/unit/market_data/acquisition/feeds/test_quotes_feed.py \
tests/unit/market_data/acquisition/test_registry.py \
tests/unit/market_data/acquisition/test_service.py
```
Результат:
```text
Успешно.
Ошибок компиляции нет.
```
---
## 14. Специализированные тесты
Выполнена команда:
```bash
python -m pytest \
tests/unit/market_data/acquisition/adapters/dzengi/test_rest.py \
tests/unit/market_data/acquisition/feeds/test_quotes_feed.py \
tests/unit/market_data/acquisition/test_registry.py \
tests/unit/market_data/acquisition/test_service.py \
-q
```
Результат:
```text
89 passed in 0.06s
```
---
## 15. Полная регрессия
Выполнена команда:
```bash
python -m pytest -q
```
Результат:
```text
498 passed in 0.25s
```
Регрессий не обнаружено.
---
## 16. Результат Build
Build 030 завершён полностью.
Создана завершённая и протестированная вертикаль REST Quotes Feed:
```text
Dzengi REST API
DzengiQuoteDocumentSource
DzengiQuoteDocumentHandler
QuotesFeed
QuoteFeedRegistry
QuoteAcquisitionService
Quote
```
Новая вертикаль пока работает независимо от legacy runtime, что обеспечивает безопасную поэтапную миграцию без изменения поведения работающего торгового бота.
---
## 17. Следующий этап
Следующий этап утверждённого плана:
```text
Build 031 — Подключение нового REST Quotes Feed под legacy ExchangeService facade
```
Его цель — переключить REST-получение текущей котировки внутри существующего `ExchangeService` на новый канонический Quotes Feed, сохранив текущие публичные интерфейсы и поведение legacy-потребителей.
Целевая переходная схема:
```text
Legacy consumer
ExchangeService facade
QuoteAcquisitionService
QuotesFeed
DzengiQuoteDocumentSource
Dzengi /api/v1/ticker/24hr
Quote
legacy-compatible projection
Legacy consumer
```
До завершения последующих этапов `ExchangeService` остаётся совместимым фасадом между новой архитектурой Market Data Acquisition и существующими потребителями работающего бота.