# Build 045 — регистрация канонического Candles Feed ## Статус **Завершён** --- ## Цель Build Продолжить поэтапную миграцию **OHLCV Feed** в утверждённую архитектуру `Market Data Acquisition` после завершения фундаментального слоя Candles Feed в Build 044. Build 045 добавляет реестр канонических источников свечей и завершает следующий минимальный архитектурный шаг: ```text Candles Feed ↓ CandlesFeedRegistry ``` При этом: - существующий runtime не переключается; - `ExchangeService.get_klines()` не изменяется; - legacy-путь получения свечей продолжает работать; - новая архитектура остаётся изолированной от существующего торгового runtime; - обратная совместимость полностью сохраняется. --- ## Исходное состояние До Build 045 проект уже содержал фундамент канонического Candles Feed, созданный в Build 044: ```text REST /api/v1/klines ↓ DzengiCandlesDocumentSource ↓ validate_candles_schema() ↓ parse_candles() ↓ validate_candles_values() ↓ map_candles() ↓ Candle ↓ DzengiCandlesDocumentHandler ↓ CandlesFeed ``` Также существовали: - каноническая модель `Candle`; - транспортная raw-модель Dzengi для свечей; - REST document source; - schema validation; - parser; - value validation; - mapper; - document handler; - `CandlesFeed`; - протоколы Candles Feed; - специализированные исключения; - unit-тесты фундаментального слоя. Однако отсутствовал реестр, позволяющий регистрировать и выбирать конкретный `CandlesFeed` по имени источника. --- ## Объём изменений Build 045 изменяет только следующие файлы: ```text src/market_data/acquisition/exceptions.py src/market_data/acquisition/registry.py tests/unit/market_data/acquisition/test_registry.py docs/migrations/build_045.md ``` Build не изменяет: ```text src/market_data/acquisition/service.py src/integrations/exchange/service.py src/integrations/exchange/models.py src/trading/ src/telegram/ ``` --- ## 1. Исключение CandleFeedRegistryError В файл: ```text src/market_data/acquisition/exceptions.py ``` добавлено специализированное исключение: ```python class CandleFeedRegistryError(MarketDataAcquisitionError): pass ``` Назначение исключения — изолировать ошибки регистрации и получения Candles Feed от других ошибок подсистемы `Market Data Acquisition`. Иерархия: ```text MarketDataAcquisitionError ↓ CandleFeedRegistryError ``` --- ## 2. Реестр CandlesFeedRegistry В файл: ```text src/market_data/acquisition/registry.py ``` добавлен класс: ```text CandlesFeedRegistry ``` Реестр отвечает исключительно за регистрацию и получение реализаций: ```text CandlesFeedProtocol ``` Реестр не выполняет: - REST-запросы; - schema validation; - parsing; - value validation; - mapping; - хранение свечей; - управление runtime; - переключение legacy-потребителей. --- ## 3. Контракт реестра `CandlesFeedRegistry` предоставляет только операции регистрации и получения Candles Feed. Публичный контракт: ```text register() get() ``` Внутренняя нормализация имени источника выполняется методом: ```text _normalize_source_name() ``` Концептуально: ```text source name ↓ CandlesFeedRegistry ↓ CandlesFeedProtocol ``` Пример: ```text "dzengi" ↓ CandlesFeedRegistry ↓ CandlesFeed ``` Реестр принимает только объекты, соответствующие каноническому контракту: ```text CandlesFeedProtocol ``` --- ## 4. Нормализация имени источника Имена источников нормализуются перед использованием. Поддерживается удаление внешних пробелов: ```text "dzengi" " dzengi " ``` После нормализации оба значения соответствуют одному ключу: ```text dzengi ``` Пустые имена источников не допускаются. Регистр символов сохраняется. Имена источников являются case-sensitive: ```text "dzengi" ``` и: ```text "DZENGI" ``` считаются разными именами источников. --- ## 5. Защита от некорректной регистрации `CandlesFeedRegistry` отклоняет: - пустое имя источника; - имя, состоящее только из пробелов; - объект, не соответствующий `CandlesFeedProtocol`; - повторную регистрацию уже существующего нормализованного имени источника. Ошибки представлены через: ```text CandleFeedRegistryError ``` --- ## 6. Защита от повторной регистрации Повторная регистрация одного и того же нормализованного имени источника запрещена. Пример: ```text register("dzengi", feed_a) ↓ dzengi → feed_a ``` Повторная регистрация: ```text register("dzengi", feed_b) ↓ CandleFeedRegistryError ``` Исходный Feed при ошибке повторной регистрации не заменяется. Для замены зарегистрированного Feed требуется отдельное явное изменение архитектурного контракта. В Build 045 такая возможность не добавлялась. --- ## 7. Получение зарегистрированного Feed Зарегистрированный Candles Feed может быть получен по имени источника. Концептуально: ```text registry.get("dzengi") ↓ CandlesFeedProtocol ``` При успешном получении реестр возвращает тот же объект Feed, который был ранее зарегистрирован: ```text feed = CandlesFeed(...) registry.register("dzengi", feed) registry.get("dzengi") is feed ↓ True ``` Попытка получить неизвестный источник приводит к: ```text CandleFeedRegistryError ``` --- ## 8. Ограниченный публичный контракт Публичный контракт `CandlesFeedRegistry` в Build 045 ограничен двумя операциями: ```text register() get() ``` В Build 045 намеренно не добавлены: ```text contains() remove() replace() clear() ``` Реестр не предоставляет управление жизненным циклом зарегистрированных Feed и не выполняет их запуск или остановку. Такое ограничение соответствует принципу минимального безопасного Build: добавляется только функциональность, необходимая для последующей интеграции Candles Feed в сервисный слой. --- ## 9. Изоляция от Acquisition Service В рамках Build 045 намеренно не добавлялись: ```text CandlesAcquisitionService ``` или метод: ```text load_candles() ``` в существующий: ```text src/market_data/acquisition/service.py ``` Архитектурная проверка: ```bash grep -RIn \ --exclude-dir="__pycache__" \ --exclude="*.pyc" \ "CandlesAcquisitionService\|load_candles" \ src/market_data/acquisition/service.py ``` Результат: ```text пусто ``` Это подтверждает, что Build 045 ограничен добавлением реестра и не выполняет преждевременную интеграцию сервисного слоя. --- ## 10. Изоляция от legacy runtime Build 045 не изменяет существующий runtime-путь получения свечей: ```text Trading / Market Analysis ↓ ExchangeService.get_klines() ↓ legacy parsing ↓ KlineBatch ``` После Build 045 этот путь продолжает работать без изменений. Новый канонический путь пока существует параллельно: ```text DzengiCandlesDocumentSource ↓ DzengiCandlesDocumentHandler ↓ CandlesFeed ↓ CandlesFeedRegistry ``` Таким образом, в системе временно существуют два пути: ```text Legacy runtime path + Canonical Candles Feed path ``` Это ожидаемое переходное состояние безопасной поэтапной миграции. --- ## 11. Unit-тесты Расширен файл: ```text tests/unit/market_data/acquisition/test_registry.py ``` Добавлено покрытие для: - создания `CandlesFeedRegistry`; - регистрации Candles Feed; - получения зарегистрированного Feed; - нормализации внешних пробелов имени источника; - case-sensitive поведения имён источников; - отклонения пустого имени; - отклонения имени, состоящего только из пробелов; - отклонения объекта, не соответствующего `CandlesFeedProtocol`; - защиты от повторной регистрации; - проверки, что повторная регистрация не заменяет исходный Feed; - получения неизвестного источника; - сохранения identity зарегистрированного Feed; - отсутствия вызова `load_candles()` при регистрации Feed; - отсутствия вызова `load_candles()` при получении Feed; - соответствия зарегистрированного объекта `CandlesFeedProtocol`; - корректной работы специализированного `CandleFeedRegistryError`. --- ## 12. Результаты targeted-тестов Выполнена команда: ```bash python -m pytest -q \ tests/unit/market_data/acquisition/test_registry.py ``` Результат: ```text 61 passed in 0.04s ``` --- ## 13. Результаты полного набора тестов Выполнена команда: ```bash python -m pytest -q ``` Результат: ```text 707 passed in 2.87s ``` Все unit-тесты проекта проходят успешно. --- ## 14. Архитектурная проверка реестра Выполнена команда: ```bash grep -RIn \ --exclude-dir="__pycache__" \ --exclude="*.pyc" \ "CandlesFeedRegistry\|CandleFeedRegistryError" \ src tests ``` Результат подтверждает, что новые сущности находятся только в ожидаемых областях: ```text src/market_data/acquisition/registry.py src/market_data/acquisition/exceptions.py tests/unit/market_data/acquisition/test_registry.py ``` Неожиданных зависимостей не обнаружено. --- ## 15. Проверка отсутствия преждевременной сервисной интеграции Выполнена команда: ```bash grep -RIn \ --exclude-dir="__pycache__" \ --exclude="*.pyc" \ "CandlesAcquisitionService\|load_candles" \ src/market_data/acquisition/service.py ``` Результат: ```text пусто ``` Это подтверждает, что Build 045 не расширяет `MarketDataAcquisitionService` и не переключает runtime. --- ## 16. Проверка синтаксиса Выполнена команда: ```bash python -m compileall \ src/market_data/acquisition/exceptions.py \ src/market_data/acquisition/registry.py \ tests/unit/market_data/acquisition/test_registry.py ``` Результат: ```text успешно ``` Ошибок синтаксиса не обнаружено. --- ## 17. Проверка форматирования diff Первоначальная проверка: ```bash git diff --check ``` обнаружила одну лишнюю пустую строку в конце: ```text app/tests/unit/market_data/acquisition/test_registry.py:691: new blank line at EOF. ``` После удаления лишней пустой строки необходимо повторно выполнить: ```bash git diff --check ``` Финальный ожидаемый результат: ```text пустой вывод ``` До создания commit эта проверка должна быть подтверждена. --- ## 18. Итоговая архитектура после Build 045 После завершения Build 045 канонический Candles Feed имеет следующую структуру: ```text Dzengi REST /api/v1/klines ↓ DzengiCandlesDocumentSource ↓ validate_candles_schema() ↓ parse_candles() ↓ validate_candles_values() ↓ map_candles() ↓ Candle ↓ DzengiCandlesDocumentHandler ↓ CandlesFeed ↓ CandlesFeedRegistry ``` При этом существующий production/runtime-путь остаётся неизменным: ```text Trading / Market Analysis ↓ ExchangeService.get_klines() ↓ legacy parsing ↓ KlineBatch ``` --- ## 19. Что намеренно не входит в Build 045 Build 045 намеренно не выполняет: - интеграцию Candles Feed в `MarketDataAcquisitionService`; - изменение `ExchangeService.get_klines()`; - изменение `Kline`; - изменение `KlineBatch`; - переключение `trading/market_analysis`; - переключение HTF-анализа; - удаление legacy parser свечей; - добавление постоянного Candle Store; - изменение runtime; - изменение Telegram UI; - изменение торговой логики. Эти ограничения являются частью стратегии безопасной миграции. --- ## 20. Следующий Build Следующий минимальный безопасный этап: ```text Build 046 — интеграция канонического Candles Feed в MarketDataAcquisitionService ``` Предполагаемая цель: ```text CandlesFeedRegistry ↓ MarketDataAcquisitionService ↓ load_candles(...) ↓ tuple[Candle, ...] ``` Build 046 должен: - добавить поддержку `CandlesFeedRegistry` в сервисный слой `Market Data Acquisition`; - добавить публичный метод загрузки свечей через канонический сервис; - сохранить существующие Instrument Reference Data и Quotes Feed без изменений; - не переключать `ExchangeService.get_klines()`; - не удалять legacy `Kline` и `KlineBatch`; - не изменять торговую логику; - сохранить полную обратную совместимость. --- ## Итог Build 045 завершает регистрацию канонического Candles Feed. Достигнуто состояние: ```text OHLCV Feed foundation ✓ Candle model ✓ REST document source ✓ schema validation ✓ parser ✓ value validation ✓ mapper ✓ document handler ✓ Candles Feed ✓ CandlesFeedRegistry ☐ Acquisition Service integration ☐ ExchangeService compatibility bridge ☐ Consumer migration ☐ Legacy Kline removal ``` Build 045 является небольшим изолированным архитектурным шагом и не изменяет поведение существующего рабочего торгового бота.