build 047: switch ExchangeService klines to canonical candles feed

This commit is contained in:
2026-07-15 16:35:54 +03:00
parent a10a760166
commit 77e87c0504
3 changed files with 1184 additions and 139 deletions

View File

@@ -0,0 +1,573 @@
# Build 047 — Переключение `ExchangeService.get_klines()` на канонический Candles Feed
**Статус:** Completed
**Дата:** 2026-07-15
**Подсистема:** Market Data Acquisition / OHLCV Feed
**Тип изменения:** Миграция legacy-потребителя на канонический Acquisition pipeline
---
## 1. Цель Build
Цель Build 047 — переключить существующий публичный метод:
```text
ExchangeService.get_klines()
```
с прямого получения и самостоятельной обработки ответа Dzengi `/api/v1/klines` на канонический pipeline подсистемы:
```text
src/market_data/acquisition/
```
При этом необходимо сохранить существующий публичный контракт `ExchangeService.get_klines()` и обратную совместимость с текущими потребителями legacy-слоя.
---
## 2. Исходное состояние
До Build 047 метод:
```text
ExchangeService.get_klines()
```
самостоятельно:
1. выполнял прямой REST-запрос к `/api/v1/klines`;
2. извлекал элементы ответа;
3. разбирал отдельные свечи;
4. создавал legacy-модели `Kline`;
5. формировал `KlineBatch`.
В `ExchangeService` существовали собственные legacy-функции обработки свечей:
```text
_parse_klines_payload()
_extract_klines_items()
_parse_kline_item()
```
Таким образом, после появления канонического Candles Feed в системе существовали два независимых пути получения и обработки OHLCV-данных.
Это создавало дублирование ответственности между:
```text
src/integrations/exchange/
```
и:
```text
src/market_data/acquisition/
```
---
## 3. Границы Build
В Build 047 выполнено:
1. переключение внутренней реализации `ExchangeService.get_klines()` на канонический Candles Feed;
2. сохранение публичного контракта `ExchangeService.get_klines()`;
3. сохранение legacy-моделей `Kline` и `KlineBatch`;
4. добавление внутреннего адаптационного преобразования `Candle -> Kline`;
5. удаление прямого REST-запроса `/api/v1/klines` из `ExchangeService`;
6. удаление legacy-функций:
- `_parse_klines_payload()`;
- `_extract_klines_items()`;
- `_parse_kline_item()`;
7. добавление специализированных unit-тестов для `ExchangeService.get_klines()` и его интеграции с каноническим Acquisition pipeline.
В Build 047 не выполнялось:
- изменение публичного контракта `ExchangeService.get_klines()`;
- изменение legacy-потребителей в `trading/market_analysis`;
- удаление моделей `Kline` и `KlineBatch`;
- прямое переключение `trading/market_analysis` на `CandlesAcquisitionService`;
- изменение архитектуры других Market Data feeds;
- изменение runtime-компонентов Acquisition.
---
## 4. Изменённые production-файлы
### 4.1. `src/integrations/exchange/service.py`
Метод:
```text
ExchangeService.get_klines()
```
сохранён как публичный compatibility-контракт.
Внутренний путь получения данных изменён.
До Build 047:
```text
ExchangeService.get_klines()
прямой REST-запрос /api/v1/klines
_parse_klines_payload()
_extract_klines_items()
_parse_kline_item()
Kline
KlineBatch
```
После Build 047:
```text
ExchangeService.get_klines()
_load_candles_via_acquisition()
DzengiCandlesDocumentSource
DzengiCandlesDocumentHandler
CandlesFeed
CandlesFeedRegistry
CandlesAcquisitionService
Candle
_kline_from_candle()
Kline
KlineBatch
```
---
## 5. Канонический путь получения свечей
После Build 047 единственный production-путь прямого обращения к endpoint:
```text
/api/v1/klines
```
расположен в:
```text
src/market_data/acquisition/adapters/dzengi/rest.py
```
Это соответствует архитектурному разделению ответственности:
```text
Exchange-specific transport
Market Data Acquisition
Canonical Candle
Legacy compatibility boundary
Kline / KlineBatch
```
`ExchangeService` больше не владеет транспортной логикой получения OHLCV-данных от Dzengi.
---
## 6. Добавленные внутренние методы
### 6.1. `_load_candles_via_acquisition()`
Добавлен внутренний compatibility-helper:
```text
ExchangeService._load_candles_via_acquisition()
```
Его ответственность:
1. создать `DzengiCandlesDocumentSource`;
2. создать `DzengiCandlesDocumentHandler`;
3. создать `CandlesFeed`;
4. зарегистрировать feed в `CandlesFeedRegistry`;
5. создать `CandlesAcquisitionService`;
6. вызвать канонический `load_candles()`;
7. вернуть immutable-набор моделей `Candle`.
Метод является внутренней границей между legacy `ExchangeService` и канонической подсистемой Market Data Acquisition.
### 6.2. `_kline_from_candle()`
Добавлен внутренний адаптер:
```text
ExchangeService._kline_from_candle()
```
Преобразование выполняется по следующему правилу:
```text
Candle.symbol -> Kline.symbol
Candle.interval -> Kline.interval
Candle.open_time -> Kline.open_time
Candle.open_price -> Kline.open_price
Candle.high_price -> Kline.high_price
Candle.low_price -> Kline.low_price
Candle.close_price -> Kline.close_price
Candle.volume -> Kline.volume
```
Это позволяет сохранить существующий legacy-контракт без переноса legacy-моделей внутрь `market_data/acquisition`.
---
## 7. Удалённая legacy-логика
Из:
```text
src/integrations/exchange/service.py
```
удалены:
```text
_parse_klines_payload()
_extract_klines_items()
_parse_kline_item()
```
Также удалён прямой вызов:
```text
/api/v1/klines
```
из `ExchangeService`.
После Build 047 parsing, schema validation, value validation и mapping OHLCV-данных выполняются только канонической подсистемой:
```text
src/market_data/acquisition/
```
---
## 8. Сохранённая обратная совместимость
Публичный контракт:
```text
ExchangeService.get_klines(...)
```
сохранён.
Существующие production-потребители не изменялись:
```text
src/trading/market_analysis/service.py
src/trading/market_analysis/htf.py
```
Они продолжают работать через:
```text
ExchangeService.get_klines()
```
и получать:
```text
KlineBatch
```
Таким образом, Build 047 меняет внутренний источник данных, но не требует одновременного переписывания legacy-потребителей.
---
## 9. Добавленные тесты
Добавлен специализированный файл:
```text
tests/unit/integrations/exchange/test_service_klines.py
```
Тесты покрывают:
- использование `default_symbol`;
- нормализацию `limit`;
- нормализацию `interval`;
- отклонение неподдерживаемого interval;
- передачу `price_type`;
- обработку выключенной биржи;
- обработку неизвестного символа;
- передачу параметров в Acquisition pipeline;
- преобразование `Candle` в `Kline`;
- формирование `KlineBatch`;
- обработку пустого набора свечей;
- обработку исключений канонического Acquisition pipeline;
- построение полного composition pipeline;
- сохранение публичного legacy-контракта.
Результат targeted-проверки:
```text
26 passed in 0.17s
```
---
## 10. Полная регрессионная проверка
Выполнено:
```bash
python -m pytest -q
```
Результат:
```text
750 passed in 2.83s
```
Регрессий не обнаружено.
---
## 11. Контроль прямого использования `/api/v1/klines`
Команда:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
'"/api/v1/klines"' \
src
```
Результат:
```text
src/market_data/acquisition/adapters/dzengi/rest.py:17:_KLINES_PATH = "/api/v1/klines"
```
Вывод:
Прямое знание endpoint `/api/v1/klines` находится только в каноническом Dzengi REST-адаптере.
---
## 12. Контроль удаления legacy-парсеров
Команда:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
"_parse_klines_payload\|_extract_klines_items\|_parse_kline_item" \
src tests
```
Результат:
```text
пусто
```
Вывод:
Legacy-функции обработки kline payload полностью удалены.
---
## 13. Контроль оставшихся потребителей `ExchangeService.get_klines()`
Команда:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
"\.get_klines(" \
src tests
```
Production-потребители:
```text
src/trading/market_analysis/service.py
src/trading/market_analysis/htf.py
```
Дополнительно присутствуют специализированные вызовы в:
```text
tests/unit/integrations/exchange/test_service_klines.py
```
Вывод:
Публичный compatibility-контракт `ExchangeService.get_klines()` продолжает обслуживать существующие legacy-потребители.
---
## 14. Контроль интеграции с Acquisition pipeline
Команда:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
"CandlesAcquisitionService\|CandlesFeedRegistry\|DzengiCandlesDocumentSource" \
src/integrations/exchange/service.py
```
Результат подтверждает использование:
```text
DzengiCandlesDocumentSource
CandlesFeedRegistry
CandlesAcquisitionService
```
внутри compatibility boundary `ExchangeService`.
---
## 15. Контроль качества diff
Выполнено:
```bash
git diff --check
```
Результат:
```text
пусто
```
Whitespace-ошибок не обнаружено.
---
## 16. Архитектурный результат
После Build 047 путь OHLCV-данных имеет следующую структуру:
```text
Dzengi REST API
DzengiCandlesDocumentSource
schema validation
parser
value validation
mapper
Candle
CandlesFeed
CandlesFeedRegistry
CandlesAcquisitionService
ExchangeService compatibility boundary
Kline / KlineBatch
legacy trading consumers
```
Главный архитектурный результат:
```text
ExchangeService больше не получает и не разбирает
сырой ответ /api/v1/klines самостоятельно.
```
Владение получением и канонической обработкой OHLCV-данных теперь принадлежит:
```text
src/market_data/acquisition/
```
---
## 17. Состояние после Build 047
На момент завершения Build:
```text
Targeted tests: 26 passed
Full test suite: 750 passed
git diff --check: clean
```
Прямой endpoint:
```text
/api/v1/klines
```
остался только в:
```text
src/market_data/acquisition/adapters/dzengi/rest.py
```
Legacy parsing helpers:
```text
_parse_klines_payload
_extract_klines_items
_parse_kline_item
```
полностью отсутствуют.
Публичный контракт:
```text
ExchangeService.get_klines()
```
сохранён.
---
## 18. Итог Build
**Build 047 завершён успешно.**
Канонический Candles Feed интегрирован в существующий `ExchangeService.get_klines()` без изменения его публичного контракта.
Дублирующая legacy-логика получения и разбора `/api/v1/klines` удалена.
Существующие потребители продолжают работать без изменений.
Полная регрессионная проверка подтверждает отсутствие нарушений существующего поведения:
```text
750 passed
```