Files
dzentra_bot/docs/migrations/build_030.md

17 KiB
Raw Permalink Blame History

Build 030 — Quotes Feed и регистрация в Acquisition Service

Статус: Завершён
Подсистема: Market Data Acquisition
Вертикаль: Quotes Feed
Проект: Dzentra
Тип изменения: Архитектурная миграция без изменения поведения legacy runtime


1. Цель Build

Цель Build 030 — собрать ранее реализованные компоненты Quotes Feed в завершённую прикладную цепочку получения канонической котировки и зарегистрировать эту цепочку в слое Acquisition Service.

Build должен обеспечить следующий поток данных:

Dzengi REST /api/v1/ticker/24hr
        ↓
DzengiQuoteDocumentSource
        ↓
DzengiQuoteDocumentHandler
        ↓
QuotesFeed
        ↓
QuoteFeedRegistry
        ↓
QuoteAcquisitionService
        ↓
Quote

На данном этапе новый Quotes Feed существует параллельно с legacy-контуром и ещё не подключается к ExchangeService, MarketPriceCache, market runtime, UI или Execution.


2. Предпосылки

К началу Build 030 были завершены предыдущие этапы:

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-файлы:

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/feeds/test_quotes_feed.py

Расширены существующие тесты:

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

Следующие компоненты намеренно не изменялись:

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

В файле:

src/market_data/acquisition/adapters/dzengi/rest.py

реализован специализированный источник:

DzengiQuoteDocumentSource

Его ответственность ограничена получением сырого транспортного документа текущей котировки.

Целевая операция:

fetch_quote_document(symbol: str) -> object

Источник выполняет запрос:

GET /api/v1/ticker/24hr

с параметрами:

{
    "symbol": symbol,
}

REST source:

  • принимает торговый символ;
  • передаёт его REST-клиенту без изменения;
  • получает декодированный транспортный документ;
  • возвращает исходный payload;
  • преобразует транспортные ошибки в специализированную ошибку Quotes Feed.

REST source не выполняет:

  • schema validation;
  • parsing;
  • value validation;
  • mapping;
  • кэширование;
  • retry;
  • нормализацию торгового символа.

5. Quotes Feed

В файле:

src/market_data/acquisition/feeds/quotes_feed.py

реализован:

QuotesFeed

Основная операция:

load_quote(symbol: str) -> Quote

Внутренняя последовательность:

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

В файле:

src/market_data/acquisition/registry.py

добавлен отдельный реестр:

QuoteFeedRegistry

Существующий:

InstrumentFeedRegistry

сохранён без архитектурного объединения с Quotes Feed.

Это позволяет:

  • не изменять стабильный Instrument Reference Data contour;
  • сохранить изоляцию вертикалей Acquisition;
  • минимизировать область регрессии;
  • избежать преждевременной универсализации registry.

QuoteFeedRegistry обеспечивает:

  • регистрацию QuoteFeedProtocol;
  • получение зарегистрированного Feed по имени источника;
  • нормализацию внешних пробелов имени источника;
  • запрет пустого имени;
  • запрет повторной регистрации;
  • runtime-проверку соответствия QuoteFeedProtocol;
  • сохранение identity зарегистрированного объекта.

Ошибки registry представлены специализированным типом:

QuoteFeedRegistryError

7. Quote Acquisition Service

В файле:

src/market_data/acquisition/service.py

добавлен отдельный прикладной сервис:

QuoteAcquisitionService

Основная операция:

load_quote(
    source_name: str,
    symbol: str,
) -> Quote

Внутренняя последовательность:

source_name
    ↓
QuoteFeedRegistry.get(source_name)
    ↓
QuoteFeedProtocol
    ↓
load_quote(symbol)
    ↓
Quote

Сервис:

  • выбирает Feed через registry;
  • передаёт symbol выбранному Feed без изменения;
  • возвращает канонический Quote;
  • не копирует полученную модель;
  • не выполняет retry;
  • не перехватывает и не переоборачивает ошибки registry или Feed.

Существующий:

InstrumentAcquisitionService

не изменяет свою ответственность и продолжает обслуживать Instrument Reference Data.


8. Dependency Injection

В Build 030 сохранён уже применяемый в Instrument Reference Data подход явной сборки зависимостей.

Пример архитектурной сборки:

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 имеет следующую структуру:

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 намеренно не реализует следующие функции:

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

Они относятся к следующим этапам:

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. Проверка компиляции

Выполнена команда:

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

Результат:

Успешно.
Ошибок компиляции нет.

14. Специализированные тесты

Выполнена команда:

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

Результат:

89 passed in 0.06s

15. Полная регрессия

Выполнена команда:

python -m pytest -q

Результат:

498 passed in 0.25s

Регрессий не обнаружено.


16. Результат Build

Build 030 завершён полностью.

Создана завершённая и протестированная вертикаль REST Quotes Feed:

Dzengi REST API
        ↓
DzengiQuoteDocumentSource
        ↓
DzengiQuoteDocumentHandler
        ↓
QuotesFeed
        ↓
QuoteFeedRegistry
        ↓
QuoteAcquisitionService
        ↓
Quote

Новая вертикаль пока работает независимо от legacy runtime, что обеспечивает безопасную поэтапную миграцию без изменения поведения работающего торгового бота.


17. Следующий этап

Следующий этап утверждённого плана:

Build 031 — Подключение нового REST Quotes Feed под legacy ExchangeService facade

Его цель — переключить REST-получение текущей котировки внутри существующего ExchangeService на новый канонический Quotes Feed, сохранив текущие публичные интерфейсы и поведение legacy-потребителей.

Целевая переходная схема:

Legacy consumer
        ↓
ExchangeService facade
        ↓
QuoteAcquisitionService
        ↓
QuotesFeed
        ↓
DzengiQuoteDocumentSource
        ↓
Dzengi /api/v1/ticker/24hr
        ↓
Quote
        ↓
legacy-compatible projection
        ↓
Legacy consumer

До завершения последующих этапов ExchangeService остаётся совместимым фасадом между новой архитектурой Market Data Acquisition и существующими потребителями работающего бота.