# Build 050 — Переключение MarketAnalysisService на канонические Candle ## Статус **Завершён** --- ## Цель Build Переключить основной orchestration-компонент анализа рынка: `MarketAnalysisService` с legacy API получения свечей: ExchangeService.get_klines() ↓ KlineBatch ↓ list[Kline] на канонический API: ExchangeService.get_candles() ↓ tuple[Candle, ...] без изменения торговой логики, алгоритмов анализа рынка, порогов, scoring, HTF-логики и существующих compatibility-контрактов. --- ## Исходное состояние До Build 050 основной orchestration-компонент: `src/trading/market_analysis/service.py` получал свечи следующим образом: ExchangeService.get_klines() ↓ KlineBatch ↓ batch.candles ↓ list[Kline] При этом после Build 048 следующие вычислительные компоненты уже были переключены на каноническую модель `Candle`: src/trading/market_analysis/indicators/volatility.py src/trading/market_analysis/quality.py src/trading/market_analysis/structure.py Таким образом возникло переходное несоответствие типов: MarketAnalysisService ↓ list[Kline] market_structure() candle_noise_score() ATR calculations ↓ Sequence[Candle] Build 050 устраняет это несоответствие для основного `MarketAnalysisService`. --- ## Объём изменений В Build 050 изменены: src/trading/market_analysis/service.py tests/unit/trading/market_analysis/test_market_analysis_service_candles.py docs/migrations/build_050.md Build 050 намеренно не изменяет: src/trading/market_analysis/htf.py src/integrations/exchange/service.py src/integrations/exchange/models.py src/market_data/acquisition/ Также не изменяются: ExchangeService.get_klines() Kline KlineBatch _kline_from_candle() --- ## Переключение MarketAnalysisService на get_candles() До Build 050 основной путь получения свечей выглядел так: MarketAnalysisService.analyze() ↓ ExchangeService.get_klines() ↓ KlineBatch ↓ batch.candles После Build 050: MarketAnalysisService.analyze() ↓ ExchangeService.get_candles() ↓ tuple[Candle, ...] Таким образом основной orchestration-компонент больше не зависит от: Kline KlineBatch batch.candles batch.symbol --- ## Каноническая модель данных После Build 050 `MarketAnalysisService` непосредственно получает: tuple[Candle, ...] где каждая свеча представлена канонической моделью: `src.market_data.acquisition.models.candle.Candle` Основные числовые поля модели: open_price: Decimal high_price: Decimal low_price: Decimal close_price: Decimal volume: Decimal Таким образом основной Market Analysis orchestration теперь работает на той же канонической модели свечи, что и вычислительные компоненты, переключённые в Build 048. --- ## Определение analysis_symbol Ранее symbol результата получался из: batch.symbol После удаления зависимости от `KlineBatch` введена локальная переменная: analysis_symbol = candles[0].symbol if candles else symbol Логика: - если получена хотя бы одна каноническая свеча, используется `Candle.symbol`; - если набор свечей пуст, сохраняется исходный запрошенный symbol. Все прежние обращения: batch.symbol в `MarketAnalysisService` заменены на: analysis_symbol Это устраняет зависимость основного orchestration от legacy-контейнера `KlineBatch`. --- ## Числовая граница Decimal → float Каноническая модель `Candle` хранит цены как: Decimal При этом существующие индикаторы и часть вычислительного pipeline работают с: float Поэтому в `MarketAnalysisService` введена явная числовая граница преобразования цен закрытия: Candle.close_price ↓ safe_float() ↓ float ↓ list[float] После Build 050: closes: list[float] = [] for candle in candles: close_price_value = safe_float(candle.close_price) ... closes.append(close_price_value) Это сохраняет существующий числовой контракт индикаторов и одновременно позволяет основному orchestration работать непосредственно с каноническими `Candle`. --- ## Проверка невалидных цен закрытия При преобразовании `Candle.close_price` выполняется проверка: - результат `safe_float()` не должен быть `None`; - результат должен быть конечным числом; - `NaN` не допускается; - положительная или отрицательная бесконечность не допускается. Для проверки конечности используется: math.isfinite() Если обнаружена некорректная цена закрытия, анализ безопасно завершается результатом `UNKNOWN` с причиной: Получены некорректные цены закрытия свечей. При этом сохраняется полное соответствие: candles[index] ↔ closes[index] Невалидные значения не пропускаются выборочно, поскольку это нарушило бы временное соответствие последовательностей свечей и цен. --- ## Сохранение недостаточного количества свечей Проверка минимального количества свечей сохранена. Если: len(candles) < 60 анализ завершается безопасным результатом `UNKNOWN` с причиной: Недостаточно свечей для анализа рынка. При пустом наборе свечей используется исходный запрошенный symbol. При непустом наборе используется canonical symbol первой свечи. --- ## Сохранение обработки ошибок ExchangeService Вызов: ExchangeService.get_candles() остаётся внутри существующей обработки исключений. Если получение свечей завершается ошибкой, `MarketAnalysisService` возвращает безопасный результат `UNKNOWN` с причиной, содержащей сообщение исходной ошибки. Build 050 не изменяет общую стратегию обработки ошибок анализа рынка. --- ## Сохранение вычислительной логики Build 050 не изменяет: - EMA; - ATR; - RSI; - momentum; - market phase; - market structure; - candle noise score; - price position score; - scoring; - confidence; - payload; - причины решений; - пороги; - HTF calculations; - индексы текущей и закрытой свечи; - торговые решения. Изменяется только источник и тип входных свечей основного orchestration-компонента: Было: KlineBatch → list[Kline] Стало: tuple[Candle, ...] --- ## HTF намеренно не изменён В Build 050 файл: `src/trading/market_analysis/htf.py` не изменяется. В нём остаются два legacy-вызова: ExchangeService().get_klines(...) Это намеренное переходное состояние. HTF должен быть переключён на канонический `Candle` отдельным Build после анализа его фактических контрактов и зависимостей. --- ## Новый тестовый файл Добавлен: `tests/unit/trading/market_analysis/test_market_analysis_service_candles.py` Изначально тестовый файл имел имя: test_service_candles.py Однако такое имя уже использовалось другим тестовым модулем: `tests/unit/integrations/exchange/test_service_candles.py` Из-за одинакового basename pytest обнаружил import mismatch. Новый Market Analysis тест был переименован в: test_market_analysis_service_candles.py После переименования конфликт модулей устранён. --- ## Покрытие специализированных тестов Новый тестовый файл проверяет: 1. вызов `ExchangeService.get_candles()` с точными аргументами; 2. использование canonical symbol первой полученной свечи; 3. использование исходного запрошенного symbol при пустом наборе; 4. безопасный `UNKNOWN` при ошибке получения свечей; 5. отклонение нечислового конечного значения `NaN`; 6. преобразование `Decimal`-цен закрытия в `float` до передачи индикаторам. --- ## Targeted tests Выполнена команда: python -m pytest -q \ tests/unit/trading/market_analysis/test_market_analysis_service_candles.py Результат: 5 passed in 0.12s --- ## Regression-набор Market Analysis и стратегий До финальной полной проверки был выполнен набор: python -m pytest -q \ tests/unit/trading/market_analysis \ tests/unit/trading/strategies/test_scalp_quote.py \ tests/unit/trading/strategies/test_trend_quote.py Результат: 27 passed in 0.14s --- ## Полный regression suite После устранения конфликта имён тестовых модулей выполнена команда: python -m pytest -q Результат: 794 passed in 2.97s Регрессий не обнаружено. --- ## Архитектурная проверка legacy get_klines() Выполнена команда: grep -RIn \ --exclude-dir="__pycache__" \ --exclude="*.pyc" \ "\.get_klines(" \ src/trading/market_analysis Результат: src/trading/market_analysis/htf.py:55: batch = ExchangeService().get_klines( src/trading/market_analysis/htf.py:153: batch = ExchangeService().get_klines( Таким образом: - основной `MarketAnalysisService` больше не использует `get_klines()`; - остаются ровно два legacy-вызова; - оба находятся в `htf.py`; - их миграция отложена на отдельный Build. --- ## Архитектурная проверка canonical get_candles() Выполнена команда: grep -RIn \ --exclude-dir="__pycache__" \ --exclude="*.pyc" \ "\.get_candles(" \ src/trading/market_analysis \ tests/unit/trading/market_analysis Production-вызов обнаружен в: src/trading/market_analysis/service.py Это подтверждает переключение основного orchestration-компонента на канонический API. --- ## Проверка отсутствия legacy-моделей в MarketAnalysisService Выполнена команда: grep -nE \ "Kline|KlineBatch|batch\.candles|batch\.symbol" \ src/trading/market_analysis/service.py Вывод отсутствует. Это подтверждает отсутствие в основном orchestration-файле зависимостей от: Kline KlineBatch batch.candles batch.symbol --- ## Проверка форматирования Выполнена команда: git diff --check Вывод отсутствует. Whitespace-ошибок не обнаружено. --- ## Фактический diff Build 050 Перед подготовкой документации состояние изменений: app/src/trading/market_analysis/service.py | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) Дополнительно добавлен новый тестовый файл: tests/unit/trading/market_analysis/test_market_analysis_service_candles.py Документация Build добавляется как: docs/migrations/build_050.md --- ## Архитектурный результат После Build 050 основной Market Analysis orchestration имеет следующий путь: MarketAnalysisService.analyze() ↓ ExchangeService.get_candles() ↓ tuple[Candle, ...] ↓ canonical Candle consumers ↓ explicit Decimal → float boundary for closes ↓ Market Analysis calculations Переходный HTF-путь пока остаётся: htf.py ↓ ExchangeService.get_klines() ↓ KlineBatch Такое разделение позволяет продолжить миграцию поэтапно без удаления рабочего compatibility-кода. --- ## Что намеренно не выполнено Build 050 намеренно не включает: - переключение `htf.py` на `get_candles()`; - удаление `ExchangeService.get_klines()`; - удаление `Kline`; - удаление `KlineBatch`; - удаление `_kline_from_candle()`; - изменение Market Data Acquisition; - изменение алгоритмов анализа; - изменение торговой логики; - изменение scoring; - изменение порогов; - изменение HTF-логики; - изменение структуры каталогов; - удаление рабочего legacy-кода. --- ## Критерии завершения Build 050 считается завершённым, поскольку: - `MarketAnalysisService` переключён на `ExchangeService.get_candles()`; - основной orchestration получает `tuple[Candle, ...]`; - зависимость от `KlineBatch` в `service.py` устранена; - обращения `batch.candles` устранены; - обращения `batch.symbol` устранены; - canonical symbol берётся из первой свечи; - для пустого набора сохраняется исходный symbol; - `Decimal` close prices явно преобразуются в `float`; - невалидные и не конечные close prices безопасно отклоняются; - новый контракт покрыт специализированными тестами; - targeted tests проходят; - regression-набор Market Analysis и стратегий проходит; - полный suite проходит; - legacy-вызовы остаются только в `htf.py`; - `git diff --check` чистый. --- ## Итог **Build 050 завершён успешно.** Текущее состояние: MarketAnalysisService → get_candles() → tuple[Candle, ...] HTF → get_klines() → KlineBatch Результаты проверок: Targeted tests: 5 passed Market Analysis + strategies: 27 passed Full test suite: 794 passed git diff --check: clean Следующий безопасный этап — отдельная миграция двух оставшихся HTF-вызовов с: ExchangeService.get_klines() на: ExchangeService.get_candles() без одновременного удаления legacy compatibility-контракта.