Files
dzentra_bot/docs/migrations/build_049.md

14 KiB
Raw Permalink Blame History

Build 049 — Предоставление канонических Candle через публичный API ExchangeService

Статус

Завершён


Цель Build

Предоставить существующим потребителям публичный канонический API получения рыночных свечей:

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

без прямого доступа слоя Trading к деталям сборки Market Data Acquisition и без удаления действующего compatibility-контракта:

ExchangeService.get_klines()
    ↓
KlineBatch

Build 049 подготавливает orchestration-компоненты Market Analysis к последующему поэтапному переключению с legacy-моделей Kline и KlineBatch на каноническую модель Candle.


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

До Build 049 ExchangeService уже использовал канонический Candles pipeline:

DzengiCandlesDocumentSource
    ↓
DzengiCandlesDocumentHandler
    ↓
CandlesFeed
    ↓
CandlesFeedRegistry
    ↓
CandlesAcquisitionService
    ↓
tuple[Candle, ...]

Однако публично сервис предоставлял только legacy-метод:

ExchangeService.get_klines()

который:

  1. выполнял проверку параметров;
  2. вызывал канонический Acquisition pipeline;
  3. преобразовывал Candle в Kline;
  4. возвращал KlineBatch.

Из-за отсутствия публичного метода get_candles() непосредственное переключение Market Analysis на канонические модели потребовало бы либо использования внутреннего helper, либо дублирования composition chain в слое Trading.


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

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

src/integrations/exchange/service.py
tests/unit/integrations/exchange/test_service_candles.py
docs/migrations/build_049.md

Существующий файл:

tests/unit/integrations/exchange/test_service_klines.py

не изменялся. Он использован как regression-контракт сохранения поведения legacy API.

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

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

Build не удаляет:

ExchangeService.get_klines()
Kline
KlineBatch
_kline_from_candle()

Новый публичный метод get_candles()

В ExchangeService добавлен метод:

def get_candles(
    self,
    symbol: str | None = None,
    *,
    interval: str = "1m",
    limit: int = 200,
    price_type: str = "bid",
) -> tuple[Candle, ...]:
    ...

Метод возвращает immutable-набор канонических моделей:

tuple[Candle, ...]

без преобразования в legacy-модели, без копирования и без сортировки результата.


Ответственность get_candles()

get_candles() выполняет:

  1. выбор default_symbol, если symbol не передан;
  2. нормализацию limit;
  3. проверку поддерживаемого interval;
  4. нормализацию price_type;
  5. проверку mock mode;
  6. валидацию symbol;
  7. вызов _load_candles_via_acquisition();
  8. логирование ошибок Acquisition pipeline;
  9. преобразование ошибки в ExchangeError.

Нормализация limit:

limit <= 0  → 200
limit > 200 → 200
иначе       → исходное значение

Поддерживаемые intervals:

1m
5m
15m
1h

Поддерживаемые значения price_type:

bid
ask

Неизвестное значение нормализуется в:

bid

Канонический путь получения данных

После Build 049 публичный канонический путь выглядит так:

ExchangeService.get_candles()
    ↓
_load_candles_via_acquisition()
    ↓
DzengiCandlesDocumentSource
    ↓
DzengiCandlesDocumentHandler
    ↓
CandlesFeed
    ↓
CandlesFeedRegistry
    ↓
CandlesAcquisitionService
    ↓
tuple[Candle, ...]

Слой Trading получает стабильную публичную точку доступа к каноническим свечам и не знает о:

DzengiCandlesDocumentSource
DzengiCandlesDocumentHandler
CandlesFeedRegistry
CandlesAcquisitionService composition

Изменение get_klines()

ExchangeService.get_klines() сохранён как legacy compatibility-фасад.

После Build 049 его цепочка:

ExchangeService.get_klines()
    ↓
ExchangeService.get_candles()
    ↓
tuple[Candle, ...]
    ↓
_kline_from_candle()
    ↓
list[Kline]
    ↓
KlineBatch

get_klines() больше не вызывает _load_candles_via_acquisition() напрямую.

Таким образом, логика получения, проверки и обработки Acquisition-ошибок сосредоточена в одном публичном каноническом методе:

get_candles()

Сохранение legacy-контракта

Для get_klines() сохранены:

  • публичная сигнатура;
  • возвращаемый тип KlineBatch;
  • нормализация limit;
  • нормализация price_type;
  • compatibility mapping Candle → Kline;
  • источник вида rest_klines:<price_type>;
  • прежнее сообщение mock mode:
Klines are not available in mock exchange mode.

Для нового канонического API используется отдельное сообщение:

Candles are not available in mock exchange mode.

Это сохраняет прежний контракт legacy-метода и одновременно вводит семантически корректный контракт нового API.


Исключение повторной валидации symbol

При пустом наборе канонических свечей get_klines() не выполняет повторный вызов:

validate_symbol()

Для заполнения symbol в пустом KlineBatch используется локальная нормализация:

normalize_symbol(symbol_to_use)

Это сохраняет:

  • один validation flow;
  • один вызов validate_symbol();
  • корректный symbol пустого KlineBatch;
  • отсутствие повторной загрузки Instrument Reference Data.

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

Добавлен:

tests/unit/integrations/exchange/test_service_candles.py

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

  • использование default_symbol;
  • нормализацию неположительного limit;
  • ограничение limit значением 200;
  • допустимые intervals;
  • отклонение неподдерживаемого interval;
  • нормализацию price_type;
  • ошибку mock mode;
  • ошибку invalid symbol;
  • точные параметры вызова Acquisition helper;
  • сохранение identity возвращённого tuple;
  • сохранение порядка свечей;
  • сохранение identity пустого tuple;
  • логирование Acquisition-ошибок;
  • преобразование ошибки в ExchangeError;
  • сохранение исходного исключения как __cause__.

Результаты targeted-тестов get_candles()

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

python -m pytest -q   tests/unit/integrations/exchange/test_service_candles.py

Результат:

23 passed in 0.13s

Совместная проверка canonical и legacy API

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

python -m pytest -q   tests/unit/integrations/exchange/test_service_candles.py   tests/unit/integrations/exchange/test_service_klines.py

Результат:

49 passed in 0.13s

Это подтверждает одновременно:

get_candles() → новый канонический контракт
get_klines()  → сохранённый legacy compatibility-контракт

Полный regression suite

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

python -m pytest -q

Результат:

789 passed in 2.80s

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


Архитектурная проверка get_candles()

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

grep -RIn   --exclude-dir="__pycache__"   --exclude="*.pyc"   "def get_candles\|\.get_candles("   src tests

Результат подтверждает:

  • определение get_candles() находится в ExchangeService;
  • get_klines() вызывает get_candles();
  • специализированные вызовы находятся в test_service_candles.py;
  • production-компоненты Market Analysis пока не переключены.

Контроль legacy-потребителей

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

grep -RIn   --exclude-dir="__pycache__"   --exclude="*.pyc"   "\.get_klines("   src/trading/market_analysis   tests/unit/integrations/exchange

Production-вызовы сохранены в:

src/trading/market_analysis/service.py
src/trading/market_analysis/htf.py

Всего остаётся три production-вызова:

MarketAnalysisService — один вызов
HTF orchestration — два вызова

Это ожидаемое переходное состояние.


Контроль вызова Acquisition helper

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

grep -RIn   --exclude-dir="__pycache__"   --exclude="*.pyc"   "_load_candles_via_acquisition("   src/integrations/exchange/service.py   tests/unit/integrations/exchange

В production обнаружены:

один вызов внутри get_candles()
одно определение _load_candles_via_acquisition()

get_klines() больше не обращается к Acquisition helper напрямую.


Контроль composition и compatibility bridge

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

grep -nE   "def get_candles|def get_klines|self\.get_candles|_load_candles_via_acquisition|_kline_from_candle"   src/integrations/exchange/service.py

Результат подтверждает ожидаемую последовательность:

get_candles()
    ↓
_load_candles_via_acquisition()

get_klines()
    ↓
self.get_candles()
    ↓
_kline_from_candle()

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

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

git diff --check

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

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


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

После Build 049 доступны два явно разделённых публичных контракта.

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

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

Legacy compatibility API:

ExchangeService.get_klines()
    ↓
get_candles()
    ↓
Candle → Kline
    ↓
KlineBatch

Получение данных и обработка ошибок больше не дублируются между двумя публичными методами.


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

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

  • переключение MarketAnalysisService на get_candles();
  • переключение HTF orchestration на get_candles();
  • удаление get_klines();
  • удаление Kline;
  • удаление KlineBatch;
  • удаление _kline_from_candle();
  • изменение торговой логики;
  • изменение алгоритмов Market Analysis;
  • изменение Market Data Acquisition;
  • изменение структуры каталогов;
  • удаление рабочего compatibility-кода.

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

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

  • добавлен публичный ExchangeService.get_candles();
  • метод возвращает канонический tuple[Candle, ...];
  • get_klines() использует get_candles() как единственный источник свечей;
  • прямой вызов Acquisition helper из get_klines() устранён;
  • legacy-контракт KlineBatch сохранён;
  • повторная symbol validation устранена;
  • новый canonical API покрыт специализированными тестами;
  • legacy API проходит существующие regression-тесты;
  • полный suite проходит;
  • архитектурные grep соответствуют переходному состоянию;
  • git diff --check чистый.

Итог

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

Публичный канонический API:

ExchangeService.get_candles()

готов для поэтапного переключения orchestration-компонентов Market Analysis.

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

Targeted canonical + legacy tests: 49 passed
Full test suite: 789 passed
git diff --check: clean

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

ExchangeService.get_klines()

на:

ExchangeService.get_candles()

без одновременного удаления legacy compatibility-контракта.