# Build 051 — Переключение HTF-анализа на канонические Candle

## Статус

**Завершён**

---

## Цель Build

Переключить оба HTF-пути подсистемы `Market Analysis` с legacy API:

```text
ExchangeService.get_klines()
    ↓
KlineBatch
    ↓
batch.candles
```

на канонический API:

```text
ExchangeService.get_candles()
    ↓
tuple[Candle, ...]
```

без изменения существующей HTF-семантики, расчётных алгоритмов, порогов, payload-полей и диагностических причин.

---

## Исходное состояние

До Build 051 в файле:

```text
src/trading/market_analysis/htf.py
```

существовали два legacy-пути получения свечей:

```text
htf_volatility_context()
htf_trend_context()
```

Оба использовали:

```text
ExchangeService.get_klines()
```

и затем извлекали:

```text
batch.candles
```

После Build 050 основной `MarketAnalysisService` уже был переключён на:

```text
ExchangeService.get_candles()
```

Поэтому `htf.py` оставался последним активным production-потребителем `get_klines()` внутри `src/trading/market_analysis`.

---

## Объём изменений

В Build 051 изменены:

```text
src/trading/market_analysis/htf.py
tests/unit/trading/market_analysis/test_htf_candles.py
docs/migrations/build_051.md
```

Build 051 не изменяет:

```text
src/trading/market_analysis/service.py
src/integrations/exchange/service.py
src/integrations/exchange/models.py
src/market_data/acquisition/
```

Build также не удаляет:

```text
ExchangeService.get_klines()
Kline
KlineBatch
_kline_from_candle()
```

Их удаление возможно только после отдельной общей проверки всех production-потребителей.

---

## Переключение htf_volatility_context()

До Build 051:

```text
htf_volatility_context()
    ↓
ExchangeService.get_klines()
    ↓
KlineBatch
    ↓
batch.candles
```

После Build 051:

```text
htf_volatility_context()
    ↓
ExchangeService.get_candles()
    ↓
tuple[Candle, ...]
```

Удалена зависимость от:

```text
batch.candles
```

Все остальные расчёты сохранены.

---

## Переключение htf_trend_context()

До Build 051:

```text
htf_trend_context()
    ↓
ExchangeService.get_klines()
    ↓
KlineBatch
    ↓
batch.candles
```

После Build 051:

```text
htf_trend_context()
    ↓
ExchangeService.get_candles()
    ↓
tuple[Candle, ...]
```

Удалена зависимость от legacy-контейнера `KlineBatch`.

---

## Каноническая модель Candle

После Build 051 оба HTF-пути непосредственно получают:

```text
tuple[Candle, ...]
```

Каноническая модель:

```text
src.market_data.acquisition.models.candle.Candle
```

использует:

```text
Decimal
```

для полей:

```text
open_price
high_price
low_price
close_price
volume
```

Поэтому для вычислительных функций была сохранена явная числовая граница:

```text
Decimal
    ↓
safe_float(...)
    ↓
isfinite(...)
    ↓
float
```

---

## Числовая граница в htf_volatility_context()

В `htf_volatility_context()` последняя цена закрытия преобразуется через:

```text
safe_float()
```

и дополнительно проверяется через:

```text
math.isfinite()
```

Невалидные значения:

```text
NaN
Infinity
-Infinity
```

не допускаются к вычислению ATR percent.

При невозможности получить корректную цену закрытия сохраняется существующая причина:

```text
HTF_ATR_UNAVAILABLE
```

---

## Числовая граница в htf_trend_context()

В `htf_trend_context()` формируется:

```text
closes: list[float]
```

Каждая каноническая цена:

```text
Candle.close_price
```

проходит:

```text
safe_float()
isfinite()
```

Если хотя бы одно значение невалидно, HTF-анализ возвращает существующую причину:

```text
HTF_INDICATORS_UNAVAILABLE
```

Невалидные значения не пропускаются выборочно, поскольку это нарушило бы соответствие:

```text
candles[index] ↔ closes[index]
```

---

## Сохранение диагностического контракта

Несмотря на переход с `get_klines()` на `get_candles()`, существующая причина ошибки получения данных сохранена:

```text
HTF_KLINES_ERROR
```

Она используется в обоих HTF-путях.

Переименование в:

```text
HTF_CANDLES_ERROR
```

не выполнялось, поскольку это не требуется для технической миграции и могло изменить внешний диагностический контракт.

---

## Сохранённая логика htf_volatility_context()

Без изменений сохранены:

```text
проверка одинакового base и HTF interval
минимальное количество свечей
ATR
close price validation
ATR percent
ATR percent baseline
volatility ratio
classify_volatility()
rounding
формат payload
HTF_SKIPPED_SAME_INTERVAL
HTF_NOT_ENOUGH_CANDLES
HTF_ATR_UNAVAILABLE
HTF_OK
```

---

## Сохранённая логика htf_trend_context()

Без изменений сохранены:

```text
проверка одинакового base и HTF interval
минимальное количество свечей
EMA fast
EMA slow
ATR
ATR percent
adaptive thresholds
EMA slopes
trend classification
trend gap
trend strength
trend consistency
trend efficiency
EMA distance / ATR
candle noise score
price position score
trend quality
market phase
market state
alignment
confirmation score
формат HTF payload
```

---

## Новый тестовый файл

Добавлен:

```text
tests/unit/trading/market_analysis/test_htf_candles.py
```

Файл создан отдельно, поскольку до Build 051 специализированного `test_htf.py` в проекте не существовало.

---

## Покрытие htf_volatility_context()

Тесты проверяют:

1. пропуск запроса при одинаковом base и HTF interval;
2. точные аргументы `get_candles()`;
3. сохранение причины `HTF_KLINES_ERROR`;
4. сохранение причины `HTF_NOT_ENOUGH_CANDLES`;
5. отклонение не конечной close price;
6. сохранение причины `HTF_ATR_UNAVAILABLE`;
7. успешный HTF volatility payload;
8. поддержку canonical `Candle` с `Decimal`.

---

## Покрытие htf_trend_context()

Тесты проверяют:

1. пропуск запроса при одинаковом base и HTF interval;
2. точные аргументы `get_candles()`;
3. сохранение причины `HTF_KLINES_ERROR`;
4. сохранение причины `HTF_NOT_ENOUGH_CANDLES`;
5. отклонение не конечной close price;
6. сохранение причины `HTF_INDICATORS_UNAVAILABLE`;
7. преобразование `Decimal` closes в `float`;
8. успешный HTF trend payload;
9. неизменность структуры результата.

---

## Targeted tests

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

```bash
python -m pytest -q \
  tests/unit/trading/market_analysis/test_htf_candles.py
```

Результат:

```text
11 passed in 0.12s
```

---

## Regression-набор Market Analysis и стратегий

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

```bash
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
```

Результат:

```text
38 passed in 0.14s
```

---

## Полный regression suite

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

```bash
python -m pytest -q
```

Результат:

```text
805 passed in 2.72s
```

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

---

## Контроль отсутствия get_klines() в Market Analysis

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

```bash
grep -RIn \
  --exclude-dir="__pycache__" \
  --exclude="*.pyc" \
  "\.get_klines(" \
  src/trading/market_analysis
```

Результат:

```text
пусто
```

Это подтверждает, что в активном production-коде `Market Analysis` больше нет вызовов:

```text
ExchangeService.get_klines()
```

---

## Контроль использования get_candles()

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

```bash
grep -RIn \
  --exclude-dir="__pycache__" \
  --exclude="*.pyc" \
  "\.get_candles(" \
  src/trading/market_analysis
```

Результат:

```text
src/trading/market_analysis/service.py:244:            candles = ExchangeService().get_candles(
src/trading/market_analysis/htf.py:57:        candles = ExchangeService().get_candles(
src/trading/market_analysis/htf.py:157:        candles = ExchangeService().get_candles(
```

После Build 051 в `Market Analysis` существует ровно три production-вызова канонического API.

---

## Контроль legacy batch-зависимостей

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

```bash
grep -RIn \
  --exclude-dir="__pycache__" \
  --exclude="*.pyc" \
  "batch\.candles\|batch\.symbol\|KlineBatch\|Kline" \
  src/trading/market_analysis
```

Результат:

```text
src/trading/market_analysis/indicators_legacy.py:5:from src.integrations.exchange.models import Kline
src/trading/market_analysis/indicators_legacy.py:21:def atr(candles: list[Kline], period: int = 14) -> float | None:
```

Следовательно:

- активные production-компоненты `Market Analysis` больше не зависят от `Kline`;
- единственная оставшаяся зависимость находится в подтверждённом неиспользуемом legacy-файле;
- `indicators_legacy.py` намеренно не изменяется в Build 051.

---

## Проверка форматирования

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

```bash
git diff --check
```

Вывод отсутствует.

Whitespace-ошибок не обнаружено.

---

## Архитектурный результат

После Build 051 весь активный production-путь `Market Analysis` использует канонические свечи:

```text
MarketAnalysisService
    ↓
ExchangeService.get_candles()
    ↓
tuple[Candle, ...]

HTF volatility
    ↓
ExchangeService.get_candles()
    ↓
tuple[Candle, ...]

HTF trend
    ↓
ExchangeService.get_candles()
    ↓
tuple[Candle, ...]
```

Legacy API:

```text
ExchangeService.get_klines()
```

больше не используется активным `Market Analysis`.

---

## Что намеренно не выполнено

Build 051 намеренно не включает:

- удаление `ExchangeService.get_klines()`;
- удаление `Kline`;
- удаление `KlineBatch`;
- удаление `_kline_from_candle()`;
- удаление `indicators_legacy.py`;
- изменение диагностической причины `HTF_KLINES_ERROR`;
- изменение алгоритмов HTF;
- изменение торговой логики;
- изменение порогов;
- изменение scoring;
- изменение Market Data Acquisition;
- изменение структуры каталогов;
- удаление рабочего compatibility-кода.

---

## Критерии завершения

Build 051 считается завершённым, поскольку:

- оба HTF-пути используют `get_candles()`;
- зависимости `batch.candles` удалены;
- `Decimal` close prices безопасно преобразуются в `float`;
- `NaN` и бесконечности отклоняются;
- существующие диагностические причины сохранены;
- HTF payload не изменён;
- специализированные тесты проходят;
- regression-набор проходит;
- полный suite проходит;
- `get_klines()` отсутствует во всём активном `Market Analysis`;
- legacy `Kline` остаётся только в неиспользуемом `indicators_legacy.py`;
- `git diff --check` чистый.

---

## Итог

**Build 051 завершён успешно.**

Текущее состояние:

```text
MarketAnalysisService → get_candles()
HTF volatility        → get_candles()
HTF trend             → get_candles()
```

Результаты:

```text
Targeted HTF tests: 11 passed
Market Analysis + strategies: 38 passed
Full test suite: 805 passed
git diff --check: clean
```

Следующий безопасный этап — общая проверка всех оставшихся production-потребителей:

```text
ExchangeService.get_klines()
Kline
KlineBatch
_kline_from_candle()
```

Только после этой проверки можно определять отдельный Build по удалению compatibility bridge.