build 046: integrate canonical candles feed into acquisition service

This commit is contained in:
2026-07-15 13:23:35 +03:00
parent 0e86cca19d
commit a10a760166
3 changed files with 928 additions and 19 deletions

View File

@@ -0,0 +1,515 @@
# Build 046 — Интеграция канонического Candles Feed в сервисный слой Market Data Acquisition
## Статус
**Завершён**
---
## Цель Build
Интегрировать канонический `Candles Feed` в существующий сервисный слой подсистемы:
```text
src/market_data/acquisition/
```
без переключения legacy-потребителей `ExchangeService.get_klines()` и без изменения существующего поведения работающего торгового бота.
Build продолжает поэтапную миграцию получения OHLCV-данных из legacy-подсистемы:
```text
src/integrations/exchange/
```
в каноническую подсистему:
```text
src/market_data/acquisition/
```
---
## Исходное состояние
До начала Build 046 канонический OHLCV-контур уже включал:
```text
Candle
DzengiCandlesDocumentSource
schema validation
parser
value validation
mapper
DzengiCandlesDocumentHandler
CandlesFeed
CandlesFeedRegistry
```
Однако сервисный слой `Market Data Acquisition` ещё не предоставлял application-level сервис для получения канонического набора свечей.
В файле:
```text
src/market_data/acquisition/service.py
```
существовали отдельные сервисы для:
```text
InstrumentAcquisitionService
QuoteAcquisitionService
```
При этом `CandlesAcquisitionService` отсутствовал.
---
## Принятое архитектурное решение
В рамках Build 046 не создавался новый объединяющий класс `MarketDataAcquisitionService`.
Существующая архитектура сервисного слоя использует отдельный application-level сервис для каждого типа рыночных данных:
```text
InstrumentAcquisitionService
QuoteAcquisitionService
```
Поэтому для OHLCV-данных добавлен отдельный:
```text
CandlesAcquisitionService
```
Это сохраняет существующий архитектурный паттерн и не требует изменения уже работающих контрактов Instrument Reference Data и Quotes Feed.
---
## Границы Build
В рамках Build 046 изменены только:
```text
src/market_data/acquisition/service.py
tests/unit/market_data/acquisition/test_service.py
docs/migrations/build_046.md
```
Не изменялись:
```text
src/market_data/acquisition/registry.py
src/market_data/acquisition/protocol.py
src/market_data/acquisition/feeds/candles_feed.py
src/market_data/acquisition/handlers/candles_handler.py
src/market_data/acquisition/adapters/dzengi/
src/integrations/exchange/service.py
src/integrations/exchange/models.py
src/trading/market_analysis/
```
---
## Реализованные изменения
### 1. Добавлен `CandlesAcquisitionService`
В файл:
```text
src/market_data/acquisition/service.py
```
добавлен класс:
```python
class CandlesAcquisitionService:
def __init__(
self,
*,
registry: CandlesFeedRegistry,
) -> None:
self._registry = registry
def load_candles(
self,
source_name: str,
symbol: str,
*,
interval: str,
limit: int,
price_type: str,
) -> tuple[Candle, ...]:
feed = self._registry.get(source_name)
return feed.load_candles(
symbol,
interval=interval,
limit=limit,
price_type=price_type,
)
```
---
### 2. Добавлены зависимости сервисного слоя
В `service.py` добавлены импорты:
```python
from src.market_data.acquisition.models.candle import Candle
```
и:
```python
from src.market_data.acquisition.registry import CandlesFeedRegistry
```
---
### 3. Зафиксирована ответственность `CandlesAcquisitionService`
`CandlesAcquisitionService` выполняет только две операции:
1. получает зарегистрированный `Candles Feed` по имени источника;
2. делегирует ему загрузку свечей.
Каноническая цепочка:
```text
source_name
CandlesFeedRegistry.get()
CandlesFeedProtocol
load_candles()
tuple[Candle, ...]
```
Сервис не выполняет:
```text
нормализацию source_name
нормализацию symbol
валидацию interval
ограничение limit
нормализацию price_type
schema validation
parsing
value validation
mapping
REST-запросы
retry
кэширование
сортировку свечей
копирование результата
оборачивание исключений
```
Это сохраняет строгие границы ответственности между слоями.
---
## Контракт `CandlesAcquisitionService`
Метод:
```python
load_candles(
source_name: str,
symbol: str,
*,
interval: str,
limit: int,
price_type: str,
) -> tuple[Candle, ...]
```
гарантирует следующую последовательность:
```text
1. Получить Feed:
registry.get(source_name)
2. Передать Feed исходные параметры:
symbol
interval
limit
price_type
3. Вернуть результат Feed без преобразований.
```
Сервис не изменяет входные параметры.
Сервис не изменяет порядок свечей.
Сервис сохраняет identity возвращённого immutable `tuple`.
Сервис не выполняет автоматический retry при ошибке.
Исключения нижележащих слоёв пробрасываются без оборачивания.
---
## Добавленные тесты
В:
```text
tests/unit/market_data/acquisition/test_service.py
```
добавлено тестовое покрытие `CandlesAcquisitionService`.
Проверяются следующие свойства:
1. сервис возвращает результат зарегистрированного Feed;
2. сохраняется identity возвращённого `tuple`;
3. `source_name` передаётся в registry без изменений;
4. registry вызывается ровно один раз;
5. `symbol` передаётся в Feed без изменений;
6. `interval` передаётся без изменений;
7. `limit` передаётся без изменений;
8. `price_type` передаётся без изменений;
9. Feed вызывается ровно один раз;
10. пустой `tuple` возвращается без изменения;
11. порядок свечей сохраняется;
12. `CandleFeedRegistryError` пробрасывается без оборачивания;
13. после ошибки registry Feed не вызывается;
14. ошибки Feed пробрасываются без оборачивания;
15. после ошибки Feed автоматический retry не выполняется.
Для проверки ошибок Feed используются:
```text
CandleTransportError
CandleValueError
CandleMappingError
```
---
## Результаты проверок
### Компиляция
Выполнена команда:
```bash
python -m compileall \
src/market_data/acquisition/service.py \
tests/unit/market_data/acquisition/test_service.py
```
Результат:
```text
Compiling 'src/market_data/acquisition/service.py'...
Compiling 'tests/unit/market_data/acquisition/test_service.py'...
```
Ошибок компиляции нет.
---
### Targeted tests
Выполнена команда:
```bash
python -m pytest -q \
tests/unit/market_data/acquisition/test_service.py
```
Результат:
```text
40 passed in 0.03s
```
---
### Полный test suite
Выполнена команда:
```bash
python -m pytest -q
```
Результат:
```text
724 passed in 2.43s
```
Регрессий не обнаружено.
---
### Проверка Git diff
Выполнена команда:
```bash
git diff --check
```
Результат:
```text
пустой вывод
```
Whitespace errors отсутствуют.
---
## Архитектурный результат
После Build 046 канонический OHLCV-контур имеет следующую структуру:
```text
External Dzengi API
DzengiCandlesDocumentSource
raw transport document
validate_candles_schema()
parse_candles()
validate_candles_values()
map_candles()
DzengiCandlesDocumentHandler
CandlesFeed
CandlesFeedRegistry
CandlesAcquisitionService
tuple[Candle, ...]
```
Таким образом, канонический Candles Feed теперь имеет полный application-level путь от внешнего REST API до сервисного слоя `Market Data Acquisition`.
---
## Состояние legacy-контура
В рамках Build 046 legacy-метод:
```text
ExchangeService.get_klines()
```
не изменялся и не удалялся.
Legacy-потребители:
```text
src/trading/market_analysis/service.py
src/trading/market_analysis/htf.py
```
продолжают использовать существующий API.
Это соответствует основному правилу миграции Dzentra:
> Сначала строится и проверяется новый канонический путь, затем потребители переключаются на него поэтапно, и только после подтверждения полной совместимости удаляется legacy-код.
---
## Что намеренно не выполнялось
Build 046 не включает:
```text
переключение ExchangeService.get_klines()
переключение trading/market_analysis
удаление legacy KlineBatch
удаление legacy Kline
удаление legacy parser
удаление legacy endpoint /api/v1/klines
регистрацию production Candles Feed на composition root
изменение runtime
изменение scheduler
кэширование свечей
хранение свечей
проверку последовательности свечей
```
Эти задачи должны выполняться отдельными Build с собственными границами и проверками.
---
## Критерии завершения
Build 046 считается завершённым, поскольку:
- `CandlesAcquisitionService` реализован;
- сервис использует `CandlesFeedRegistry`;
- сервис возвращает канонический `tuple[Candle, ...]`;
- входные параметры передаются без изменений;
- результат не копируется и не сортируется;
- ошибки не оборачиваются;
- retry отсутствует;
- существующие Instrument и Quote сервисы не нарушены;
- targeted tests успешно проходят;
- полный test suite успешно проходит;
- `git diff --check` не обнаруживает ошибок;
- legacy-потребители не переключались;
- работающий торговый бот сохраняет обратную совместимость.
---
## Итог
**Build 046 завершён успешно.**
Канонический `Candles Feed` интегрирован в сервисный слой `Market Data Acquisition`.
Текущий завершённый путь:
```text
Dzengi REST API
DzengiCandlesDocumentSource
Schema Validation
Parser
Value Validation
Mapper
DzengiCandlesDocumentHandler
CandlesFeed
CandlesFeedRegistry
CandlesAcquisitionService
tuple[Candle, ...]
```
Следующий Build должен продолжить миграцию OHLCV-контура без преждевременного удаления legacy-реализации и без нарушения работы существующего торгового бота.