570 lines
14 KiB
Markdown
570 lines
14 KiB
Markdown
# Build 049 — Предоставление канонических Candle через публичный API ExchangeService
|
||
|
||
## Статус
|
||
|
||
**Завершён**
|
||
|
||
---
|
||
|
||
## Цель Build
|
||
|
||
Предоставить существующим потребителям публичный канонический API получения рыночных свечей:
|
||
|
||
```text
|
||
ExchangeService.get_candles()
|
||
↓
|
||
tuple[Candle, ...]
|
||
```
|
||
|
||
без прямого доступа слоя Trading к деталям сборки `Market Data Acquisition` и без удаления действующего compatibility-контракта:
|
||
|
||
```text
|
||
ExchangeService.get_klines()
|
||
↓
|
||
KlineBatch
|
||
```
|
||
|
||
Build 049 подготавливает orchestration-компоненты `Market Analysis` к последующему поэтапному переключению с legacy-моделей `Kline` и `KlineBatch` на каноническую модель `Candle`.
|
||
|
||
---
|
||
|
||
## Исходное состояние
|
||
|
||
До Build 049 `ExchangeService` уже использовал канонический Candles pipeline:
|
||
|
||
```text
|
||
DzengiCandlesDocumentSource
|
||
↓
|
||
DzengiCandlesDocumentHandler
|
||
↓
|
||
CandlesFeed
|
||
↓
|
||
CandlesFeedRegistry
|
||
↓
|
||
CandlesAcquisitionService
|
||
↓
|
||
tuple[Candle, ...]
|
||
```
|
||
|
||
Однако публично сервис предоставлял только legacy-метод:
|
||
|
||
```text
|
||
ExchangeService.get_klines()
|
||
```
|
||
|
||
который:
|
||
|
||
1. выполнял проверку параметров;
|
||
2. вызывал канонический Acquisition pipeline;
|
||
3. преобразовывал `Candle` в `Kline`;
|
||
4. возвращал `KlineBatch`.
|
||
|
||
Из-за отсутствия публичного метода `get_candles()` непосредственное переключение `Market Analysis` на канонические модели потребовало бы либо использования внутреннего helper, либо дублирования composition chain в слое Trading.
|
||
|
||
---
|
||
|
||
## Объём изменений
|
||
|
||
В Build 049 изменены:
|
||
|
||
```text
|
||
src/integrations/exchange/service.py
|
||
tests/unit/integrations/exchange/test_service_candles.py
|
||
docs/migrations/build_049.md
|
||
```
|
||
|
||
Существующий файл:
|
||
|
||
```text
|
||
tests/unit/integrations/exchange/test_service_klines.py
|
||
```
|
||
|
||
не изменялся. Он использован как regression-контракт сохранения поведения legacy API.
|
||
|
||
Build 049 не изменяет:
|
||
|
||
```text
|
||
src/trading/market_analysis/service.py
|
||
src/trading/market_analysis/htf.py
|
||
src/market_data/acquisition/
|
||
src/integrations/exchange/models.py
|
||
```
|
||
|
||
Build не удаляет:
|
||
|
||
```text
|
||
ExchangeService.get_klines()
|
||
Kline
|
||
KlineBatch
|
||
_kline_from_candle()
|
||
```
|
||
|
||
---
|
||
|
||
## Новый публичный метод get_candles()
|
||
|
||
В `ExchangeService` добавлен метод:
|
||
|
||
```python
|
||
def get_candles(
|
||
self,
|
||
symbol: str | None = None,
|
||
*,
|
||
interval: str = "1m",
|
||
limit: int = 200,
|
||
price_type: str = "bid",
|
||
) -> tuple[Candle, ...]:
|
||
...
|
||
```
|
||
|
||
Метод возвращает immutable-набор канонических моделей:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
limit <= 0 → 200
|
||
limit > 200 → 200
|
||
иначе → исходное значение
|
||
```
|
||
|
||
Поддерживаемые intervals:
|
||
|
||
```text
|
||
1m
|
||
5m
|
||
15m
|
||
1h
|
||
```
|
||
|
||
Поддерживаемые значения `price_type`:
|
||
|
||
```text
|
||
bid
|
||
ask
|
||
```
|
||
|
||
Неизвестное значение нормализуется в:
|
||
|
||
```text
|
||
bid
|
||
```
|
||
|
||
---
|
||
|
||
## Канонический путь получения данных
|
||
|
||
После Build 049 публичный канонический путь выглядит так:
|
||
|
||
```text
|
||
ExchangeService.get_candles()
|
||
↓
|
||
_load_candles_via_acquisition()
|
||
↓
|
||
DzengiCandlesDocumentSource
|
||
↓
|
||
DzengiCandlesDocumentHandler
|
||
↓
|
||
CandlesFeed
|
||
↓
|
||
CandlesFeedRegistry
|
||
↓
|
||
CandlesAcquisitionService
|
||
↓
|
||
tuple[Candle, ...]
|
||
```
|
||
|
||
Слой Trading получает стабильную публичную точку доступа к каноническим свечам и не знает о:
|
||
|
||
```text
|
||
DzengiCandlesDocumentSource
|
||
DzengiCandlesDocumentHandler
|
||
CandlesFeedRegistry
|
||
CandlesAcquisitionService composition
|
||
```
|
||
|
||
---
|
||
|
||
## Изменение get_klines()
|
||
|
||
`ExchangeService.get_klines()` сохранён как legacy compatibility-фасад.
|
||
|
||
После Build 049 его цепочка:
|
||
|
||
```text
|
||
ExchangeService.get_klines()
|
||
↓
|
||
ExchangeService.get_candles()
|
||
↓
|
||
tuple[Candle, ...]
|
||
↓
|
||
_kline_from_candle()
|
||
↓
|
||
list[Kline]
|
||
↓
|
||
KlineBatch
|
||
```
|
||
|
||
`get_klines()` больше не вызывает `_load_candles_via_acquisition()` напрямую.
|
||
|
||
Таким образом, логика получения, проверки и обработки Acquisition-ошибок сосредоточена в одном публичном каноническом методе:
|
||
|
||
```text
|
||
get_candles()
|
||
```
|
||
|
||
---
|
||
|
||
## Сохранение legacy-контракта
|
||
|
||
Для `get_klines()` сохранены:
|
||
|
||
- публичная сигнатура;
|
||
- возвращаемый тип `KlineBatch`;
|
||
- нормализация limit;
|
||
- нормализация `price_type`;
|
||
- compatibility mapping `Candle → Kline`;
|
||
- источник вида `rest_klines:<price_type>`;
|
||
- прежнее сообщение mock mode:
|
||
|
||
```text
|
||
Klines are not available in mock exchange mode.
|
||
```
|
||
|
||
Для нового канонического API используется отдельное сообщение:
|
||
|
||
```text
|
||
Candles are not available in mock exchange mode.
|
||
```
|
||
|
||
Это сохраняет прежний контракт legacy-метода и одновременно вводит семантически корректный контракт нового API.
|
||
|
||
---
|
||
|
||
## Исключение повторной валидации symbol
|
||
|
||
При пустом наборе канонических свечей `get_klines()` не выполняет повторный вызов:
|
||
|
||
```text
|
||
validate_symbol()
|
||
```
|
||
|
||
Для заполнения symbol в пустом `KlineBatch` используется локальная нормализация:
|
||
|
||
```text
|
||
normalize_symbol(symbol_to_use)
|
||
```
|
||
|
||
Это сохраняет:
|
||
|
||
- один validation flow;
|
||
- один вызов `validate_symbol()`;
|
||
- корректный symbol пустого `KlineBatch`;
|
||
- отсутствие повторной загрузки Instrument Reference Data.
|
||
|
||
---
|
||
|
||
## Новый тестовый файл
|
||
|
||
Добавлен:
|
||
|
||
```text
|
||
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()
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
python -m pytest -q tests/unit/integrations/exchange/test_service_candles.py
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
23 passed in 0.13s
|
||
```
|
||
|
||
---
|
||
|
||
## Совместная проверка canonical и legacy API
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
python -m pytest -q tests/unit/integrations/exchange/test_service_candles.py tests/unit/integrations/exchange/test_service_klines.py
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
49 passed in 0.13s
|
||
```
|
||
|
||
Это подтверждает одновременно:
|
||
|
||
```text
|
||
get_candles() → новый канонический контракт
|
||
get_klines() → сохранённый legacy compatibility-контракт
|
||
```
|
||
|
||
---
|
||
|
||
## Полный regression suite
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
python -m pytest -q
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
789 passed in 2.80s
|
||
```
|
||
|
||
Регрессий не обнаружено.
|
||
|
||
---
|
||
|
||
## Архитектурная проверка get_candles()
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
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-потребителей
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
grep -RIn --exclude-dir="__pycache__" --exclude="*.pyc" "\.get_klines(" src/trading/market_analysis tests/unit/integrations/exchange
|
||
```
|
||
|
||
Production-вызовы сохранены в:
|
||
|
||
```text
|
||
src/trading/market_analysis/service.py
|
||
src/trading/market_analysis/htf.py
|
||
```
|
||
|
||
Всего остаётся три production-вызова:
|
||
|
||
```text
|
||
MarketAnalysisService — один вызов
|
||
HTF orchestration — два вызова
|
||
```
|
||
|
||
Это ожидаемое переходное состояние.
|
||
|
||
---
|
||
|
||
## Контроль вызова Acquisition helper
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
grep -RIn --exclude-dir="__pycache__" --exclude="*.pyc" "_load_candles_via_acquisition(" src/integrations/exchange/service.py tests/unit/integrations/exchange
|
||
```
|
||
|
||
В production обнаружены:
|
||
|
||
```text
|
||
один вызов внутри get_candles()
|
||
одно определение _load_candles_via_acquisition()
|
||
```
|
||
|
||
`get_klines()` больше не обращается к Acquisition helper напрямую.
|
||
|
||
---
|
||
|
||
## Контроль composition и compatibility bridge
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
grep -nE "def get_candles|def get_klines|self\.get_candles|_load_candles_via_acquisition|_kline_from_candle" src/integrations/exchange/service.py
|
||
```
|
||
|
||
Результат подтверждает ожидаемую последовательность:
|
||
|
||
```text
|
||
get_candles()
|
||
↓
|
||
_load_candles_via_acquisition()
|
||
|
||
get_klines()
|
||
↓
|
||
self.get_candles()
|
||
↓
|
||
_kline_from_candle()
|
||
```
|
||
|
||
---
|
||
|
||
## Проверка форматирования
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
git diff --check
|
||
```
|
||
|
||
Вывод отсутствует.
|
||
|
||
Whitespace-ошибок не обнаружено.
|
||
|
||
---
|
||
|
||
## Архитектурный результат
|
||
|
||
После Build 049 доступны два явно разделённых публичных контракта.
|
||
|
||
Канонический API:
|
||
|
||
```text
|
||
ExchangeService.get_candles()
|
||
↓
|
||
tuple[Candle, ...]
|
||
```
|
||
|
||
Legacy compatibility API:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
ExchangeService.get_candles()
|
||
```
|
||
|
||
готов для поэтапного переключения orchestration-компонентов `Market Analysis`.
|
||
|
||
Текущее состояние:
|
||
|
||
```text
|
||
Targeted canonical + legacy tests: 49 passed
|
||
Full test suite: 789 passed
|
||
git diff --check: clean
|
||
```
|
||
|
||
Следующий безопасный этап — отдельное переключение непосредственных production-потребителей с:
|
||
|
||
```text
|
||
ExchangeService.get_klines()
|
||
```
|
||
|
||
на:
|
||
|
||
```text
|
||
ExchangeService.get_candles()
|
||
```
|
||
|
||
без одновременного удаления legacy compatibility-контракта.
|