# Build 046 — Интеграция канонического Candles Feed в сервисный слой Market Data Acquisition ## Статус **Завершён** --- ## Цель Build Интегрировать канонический `Candles Feed` в существующий сервисный слой подсистемы: ```text src/market_data/acquisition/ ``` без переключения legacy-потребителей `ExchangeService.get_klines()` и без изменения существующего поведения работающего торгового бота. Build продолжает поэтапную миграцию получения OHLCV-данных из legacy-подсистемы: ```text src/integrations/exchange/ ``` в каноническую подсистему: ```text src/market_data/acquisition/ ``` --- ## Исходное состояние До начала Build 046 канонический OHLCV-контур уже включал: ```text Candle ↓ DzengiCandlesDocumentSource ↓ schema validation ↓ parser ↓ value validation ↓ mapper ↓ DzengiCandlesDocumentHandler ↓ CandlesFeed ↓ CandlesFeedRegistry ``` Однако сервисный слой `Market Data Acquisition` ещё не предоставлял application-level сервис для получения канонического набора свечей. В файле: ```text src/market_data/acquisition/service.py ``` существовали отдельные сервисы для: ```text InstrumentAcquisitionService QuoteAcquisitionService ``` При этом `CandlesAcquisitionService` отсутствовал. --- ## Принятое архитектурное решение В рамках Build 046 не создавался новый объединяющий класс `MarketDataAcquisitionService`. Существующая архитектура сервисного слоя использует отдельный application-level сервис для каждого типа рыночных данных: ```text InstrumentAcquisitionService QuoteAcquisitionService ``` Поэтому для OHLCV-данных добавлен отдельный: ```text CandlesAcquisitionService ``` Это сохраняет существующий архитектурный паттерн и не требует изменения уже работающих контрактов Instrument Reference Data и Quotes Feed. --- ## Границы Build В рамках Build 046 изменены только: ```text src/market_data/acquisition/service.py tests/unit/market_data/acquisition/test_service.py docs/migrations/build_046.md ``` Не изменялись: ```text src/market_data/acquisition/registry.py src/market_data/acquisition/protocol.py src/market_data/acquisition/feeds/candles_feed.py src/market_data/acquisition/handlers/candles_handler.py src/market_data/acquisition/adapters/dzengi/ src/integrations/exchange/service.py src/integrations/exchange/models.py src/trading/market_analysis/ ``` --- ## Реализованные изменения ### 1. Добавлен `CandlesAcquisitionService` В файл: ```text src/market_data/acquisition/service.py ``` добавлен класс: ```python class CandlesAcquisitionService: def __init__( self, *, registry: CandlesFeedRegistry, ) -> None: self._registry = registry def load_candles( self, source_name: str, symbol: str, *, interval: str, limit: int, price_type: str, ) -> tuple[Candle, ...]: feed = self._registry.get(source_name) return feed.load_candles( symbol, interval=interval, limit=limit, price_type=price_type, ) ``` --- ### 2. Добавлены зависимости сервисного слоя В `service.py` добавлены импорты: ```python from src.market_data.acquisition.models.candle import Candle ``` и: ```python from src.market_data.acquisition.registry import CandlesFeedRegistry ``` --- ### 3. Зафиксирована ответственность `CandlesAcquisitionService` `CandlesAcquisitionService` выполняет только две операции: 1. получает зарегистрированный `Candles Feed` по имени источника; 2. делегирует ему загрузку свечей. Каноническая цепочка: ```text source_name ↓ CandlesFeedRegistry.get() ↓ CandlesFeedProtocol ↓ load_candles() ↓ tuple[Candle, ...] ``` Сервис не выполняет: ```text нормализацию source_name нормализацию symbol валидацию interval ограничение limit нормализацию price_type schema validation parsing value validation mapping REST-запросы retry кэширование сортировку свечей копирование результата оборачивание исключений ``` Это сохраняет строгие границы ответственности между слоями. --- ## Контракт `CandlesAcquisitionService` Метод: ```python load_candles( source_name: str, symbol: str, *, interval: str, limit: int, price_type: str, ) -> tuple[Candle, ...] ``` гарантирует следующую последовательность: ```text 1. Получить Feed: registry.get(source_name) 2. Передать Feed исходные параметры: symbol interval limit price_type 3. Вернуть результат Feed без преобразований. ``` Сервис не изменяет входные параметры. Сервис не изменяет порядок свечей. Сервис сохраняет identity возвращённого immutable `tuple`. Сервис не выполняет автоматический retry при ошибке. Исключения нижележащих слоёв пробрасываются без оборачивания. --- ## Добавленные тесты В: ```text tests/unit/market_data/acquisition/test_service.py ``` добавлено тестовое покрытие `CandlesAcquisitionService`. Проверяются следующие свойства: 1. сервис возвращает результат зарегистрированного Feed; 2. сохраняется identity возвращённого `tuple`; 3. `source_name` передаётся в registry без изменений; 4. registry вызывается ровно один раз; 5. `symbol` передаётся в Feed без изменений; 6. `interval` передаётся без изменений; 7. `limit` передаётся без изменений; 8. `price_type` передаётся без изменений; 9. Feed вызывается ровно один раз; 10. пустой `tuple` возвращается без изменения; 11. порядок свечей сохраняется; 12. `CandleFeedRegistryError` пробрасывается без оборачивания; 13. после ошибки registry Feed не вызывается; 14. ошибки Feed пробрасываются без оборачивания; 15. после ошибки Feed автоматический retry не выполняется. Для проверки ошибок Feed используются: ```text CandleTransportError CandleValueError CandleMappingError ``` --- ## Результаты проверок ### Компиляция Выполнена команда: ```bash python -m compileall \ src/market_data/acquisition/service.py \ tests/unit/market_data/acquisition/test_service.py ``` Результат: ```text Compiling 'src/market_data/acquisition/service.py'... Compiling 'tests/unit/market_data/acquisition/test_service.py'... ``` Ошибок компиляции нет. --- ### Targeted tests Выполнена команда: ```bash python -m pytest -q \ tests/unit/market_data/acquisition/test_service.py ``` Результат: ```text 40 passed in 0.03s ``` --- ### Полный test suite Выполнена команда: ```bash python -m pytest -q ``` Результат: ```text 724 passed in 2.43s ``` Регрессий не обнаружено. --- ### Проверка Git diff Выполнена команда: ```bash git diff --check ``` Результат: ```text пустой вывод ``` Whitespace errors отсутствуют. --- ## Архитектурный результат После Build 046 канонический OHLCV-контур имеет следующую структуру: ```text External Dzengi API ↓ DzengiCandlesDocumentSource ↓ raw transport document ↓ validate_candles_schema() ↓ parse_candles() ↓ validate_candles_values() ↓ map_candles() ↓ DzengiCandlesDocumentHandler ↓ CandlesFeed ↓ CandlesFeedRegistry ↓ CandlesAcquisitionService ↓ tuple[Candle, ...] ``` Таким образом, канонический Candles Feed теперь имеет полный application-level путь от внешнего REST API до сервисного слоя `Market Data Acquisition`. --- ## Состояние legacy-контура В рамках Build 046 legacy-метод: ```text ExchangeService.get_klines() ``` не изменялся и не удалялся. Legacy-потребители: ```text src/trading/market_analysis/service.py src/trading/market_analysis/htf.py ``` продолжают использовать существующий API. Это соответствует основному правилу миграции Dzentra: > Сначала строится и проверяется новый канонический путь, затем потребители переключаются на него поэтапно, и только после подтверждения полной совместимости удаляется legacy-код. --- ## Что намеренно не выполнялось Build 046 не включает: ```text переключение ExchangeService.get_klines() переключение trading/market_analysis удаление legacy KlineBatch удаление legacy Kline удаление legacy parser удаление legacy endpoint /api/v1/klines регистрацию production Candles Feed на composition root изменение runtime изменение scheduler кэширование свечей хранение свечей проверку последовательности свечей ``` Эти задачи должны выполняться отдельными Build с собственными границами и проверками. --- ## Критерии завершения Build 046 считается завершённым, поскольку: - `CandlesAcquisitionService` реализован; - сервис использует `CandlesFeedRegistry`; - сервис возвращает канонический `tuple[Candle, ...]`; - входные параметры передаются без изменений; - результат не копируется и не сортируется; - ошибки не оборачиваются; - retry отсутствует; - существующие Instrument и Quote сервисы не нарушены; - targeted tests успешно проходят; - полный test suite успешно проходит; - `git diff --check` не обнаруживает ошибок; - legacy-потребители не переключались; - работающий торговый бот сохраняет обратную совместимость. --- ## Итог **Build 046 завершён успешно.** Канонический `Candles Feed` интегрирован в сервисный слой `Market Data Acquisition`. Текущий завершённый путь: ```text Dzengi REST API ↓ DzengiCandlesDocumentSource ↓ Schema Validation ↓ Parser ↓ Value Validation ↓ Mapper ↓ DzengiCandlesDocumentHandler ↓ CandlesFeed ↓ CandlesFeedRegistry ↓ CandlesAcquisitionService ↓ tuple[Candle, ...] ``` Следующий Build должен продолжить миграцию OHLCV-контура без преждевременного удаления legacy-реализации и без нарушения работы существующего торгового бота.