14 KiB
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()
который:
- выполнял проверку параметров;
- вызывал канонический Acquisition pipeline;
- преобразовывал
CandleвKline; - возвращал
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() выполняет:
- выбор
default_symbol, если symbol не передан; - нормализацию
limit; - проверку поддерживаемого interval;
- нормализацию
price_type; - проверку mock mode;
- валидацию symbol;
- вызов
_load_candles_via_acquisition(); - логирование ошибок Acquisition pipeline;
- преобразование ошибки в
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-контракта.