17 KiB
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_nameregistry; - передача
symbolFeed без изменения; - однократное обращение к 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 и существующими потребителями работающего бота.