692 lines
17 KiB
Markdown
692 lines
17 KiB
Markdown
# Build 045 — регистрация канонического Candles Feed
|
||
|
||
## Статус
|
||
|
||
**Завершён**
|
||
|
||
---
|
||
|
||
## Цель Build
|
||
|
||
Продолжить поэтапную миграцию **OHLCV Feed** в утверждённую архитектуру `Market Data Acquisition` после завершения фундаментального слоя Candles Feed в Build 044.
|
||
|
||
Build 045 добавляет реестр канонических источников свечей и завершает следующий минимальный архитектурный шаг:
|
||
|
||
```text
|
||
Candles Feed
|
||
↓
|
||
CandlesFeedRegistry
|
||
```
|
||
|
||
При этом:
|
||
|
||
- существующий runtime не переключается;
|
||
- `ExchangeService.get_klines()` не изменяется;
|
||
- legacy-путь получения свечей продолжает работать;
|
||
- новая архитектура остаётся изолированной от существующего торгового runtime;
|
||
- обратная совместимость полностью сохраняется.
|
||
|
||
---
|
||
|
||
## Исходное состояние
|
||
|
||
До Build 045 проект уже содержал фундамент канонического Candles Feed, созданный в Build 044:
|
||
|
||
```text
|
||
REST /api/v1/klines
|
||
↓
|
||
DzengiCandlesDocumentSource
|
||
↓
|
||
validate_candles_schema()
|
||
↓
|
||
parse_candles()
|
||
↓
|
||
validate_candles_values()
|
||
↓
|
||
map_candles()
|
||
↓
|
||
Candle
|
||
↓
|
||
DzengiCandlesDocumentHandler
|
||
↓
|
||
CandlesFeed
|
||
```
|
||
|
||
Также существовали:
|
||
|
||
- каноническая модель `Candle`;
|
||
- транспортная raw-модель Dzengi для свечей;
|
||
- REST document source;
|
||
- schema validation;
|
||
- parser;
|
||
- value validation;
|
||
- mapper;
|
||
- document handler;
|
||
- `CandlesFeed`;
|
||
- протоколы Candles Feed;
|
||
- специализированные исключения;
|
||
- unit-тесты фундаментального слоя.
|
||
|
||
Однако отсутствовал реестр, позволяющий регистрировать и выбирать конкретный `CandlesFeed` по имени источника.
|
||
|
||
---
|
||
|
||
## Объём изменений
|
||
|
||
Build 045 изменяет только следующие файлы:
|
||
|
||
```text
|
||
src/market_data/acquisition/exceptions.py
|
||
src/market_data/acquisition/registry.py
|
||
tests/unit/market_data/acquisition/test_registry.py
|
||
docs/migrations/build_045.md
|
||
```
|
||
|
||
Build не изменяет:
|
||
|
||
```text
|
||
src/market_data/acquisition/service.py
|
||
src/integrations/exchange/service.py
|
||
src/integrations/exchange/models.py
|
||
src/trading/
|
||
src/telegram/
|
||
```
|
||
|
||
---
|
||
|
||
## 1. Исключение CandleFeedRegistryError
|
||
|
||
В файл:
|
||
|
||
```text
|
||
src/market_data/acquisition/exceptions.py
|
||
```
|
||
|
||
добавлено специализированное исключение:
|
||
|
||
```python
|
||
class CandleFeedRegistryError(MarketDataAcquisitionError):
|
||
pass
|
||
```
|
||
|
||
Назначение исключения — изолировать ошибки регистрации и получения Candles Feed от других ошибок подсистемы `Market Data Acquisition`.
|
||
|
||
Иерархия:
|
||
|
||
```text
|
||
MarketDataAcquisitionError
|
||
↓
|
||
CandleFeedRegistryError
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Реестр CandlesFeedRegistry
|
||
|
||
В файл:
|
||
|
||
```text
|
||
src/market_data/acquisition/registry.py
|
||
```
|
||
|
||
добавлен класс:
|
||
|
||
```text
|
||
CandlesFeedRegistry
|
||
```
|
||
|
||
Реестр отвечает исключительно за регистрацию и получение реализаций:
|
||
|
||
```text
|
||
CandlesFeedProtocol
|
||
```
|
||
|
||
Реестр не выполняет:
|
||
|
||
- REST-запросы;
|
||
- schema validation;
|
||
- parsing;
|
||
- value validation;
|
||
- mapping;
|
||
- хранение свечей;
|
||
- управление runtime;
|
||
- переключение legacy-потребителей.
|
||
|
||
---
|
||
|
||
## 3. Контракт реестра
|
||
|
||
`CandlesFeedRegistry` предоставляет только операции регистрации и получения Candles Feed.
|
||
|
||
Публичный контракт:
|
||
|
||
```text
|
||
register()
|
||
get()
|
||
```
|
||
|
||
Внутренняя нормализация имени источника выполняется методом:
|
||
|
||
```text
|
||
_normalize_source_name()
|
||
```
|
||
|
||
Концептуально:
|
||
|
||
```text
|
||
source name
|
||
↓
|
||
CandlesFeedRegistry
|
||
↓
|
||
CandlesFeedProtocol
|
||
```
|
||
|
||
Пример:
|
||
|
||
```text
|
||
"dzengi"
|
||
↓
|
||
CandlesFeedRegistry
|
||
↓
|
||
CandlesFeed
|
||
```
|
||
|
||
Реестр принимает только объекты, соответствующие каноническому контракту:
|
||
|
||
```text
|
||
CandlesFeedProtocol
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Нормализация имени источника
|
||
|
||
Имена источников нормализуются перед использованием.
|
||
|
||
Поддерживается удаление внешних пробелов:
|
||
|
||
```text
|
||
"dzengi"
|
||
" dzengi "
|
||
```
|
||
|
||
После нормализации оба значения соответствуют одному ключу:
|
||
|
||
```text
|
||
dzengi
|
||
```
|
||
|
||
Пустые имена источников не допускаются.
|
||
|
||
Регистр символов сохраняется. Имена источников являются case-sensitive:
|
||
|
||
```text
|
||
"dzengi"
|
||
```
|
||
|
||
и:
|
||
|
||
```text
|
||
"DZENGI"
|
||
```
|
||
|
||
считаются разными именами источников.
|
||
|
||
---
|
||
|
||
## 5. Защита от некорректной регистрации
|
||
|
||
`CandlesFeedRegistry` отклоняет:
|
||
|
||
- пустое имя источника;
|
||
- имя, состоящее только из пробелов;
|
||
- объект, не соответствующий `CandlesFeedProtocol`;
|
||
- повторную регистрацию уже существующего нормализованного имени источника.
|
||
|
||
Ошибки представлены через:
|
||
|
||
```text
|
||
CandleFeedRegistryError
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Защита от повторной регистрации
|
||
|
||
Повторная регистрация одного и того же нормализованного имени источника запрещена.
|
||
|
||
Пример:
|
||
|
||
```text
|
||
register("dzengi", feed_a)
|
||
↓
|
||
dzengi → feed_a
|
||
```
|
||
|
||
Повторная регистрация:
|
||
|
||
```text
|
||
register("dzengi", feed_b)
|
||
↓
|
||
CandleFeedRegistryError
|
||
```
|
||
|
||
Исходный Feed при ошибке повторной регистрации не заменяется.
|
||
|
||
Для замены зарегистрированного Feed требуется отдельное явное изменение архитектурного контракта. В Build 045 такая возможность не добавлялась.
|
||
|
||
---
|
||
|
||
## 7. Получение зарегистрированного Feed
|
||
|
||
Зарегистрированный Candles Feed может быть получен по имени источника.
|
||
|
||
Концептуально:
|
||
|
||
```text
|
||
registry.get("dzengi")
|
||
↓
|
||
CandlesFeedProtocol
|
||
```
|
||
|
||
При успешном получении реестр возвращает тот же объект Feed, который был ранее зарегистрирован:
|
||
|
||
```text
|
||
feed = CandlesFeed(...)
|
||
|
||
registry.register("dzengi", feed)
|
||
|
||
registry.get("dzengi") is feed
|
||
↓
|
||
True
|
||
```
|
||
|
||
Попытка получить неизвестный источник приводит к:
|
||
|
||
```text
|
||
CandleFeedRegistryError
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Ограниченный публичный контракт
|
||
|
||
Публичный контракт `CandlesFeedRegistry` в Build 045 ограничен двумя операциями:
|
||
|
||
```text
|
||
register()
|
||
get()
|
||
```
|
||
|
||
В Build 045 намеренно не добавлены:
|
||
|
||
```text
|
||
contains()
|
||
remove()
|
||
replace()
|
||
clear()
|
||
```
|
||
|
||
Реестр не предоставляет управление жизненным циклом зарегистрированных Feed и не выполняет их запуск или остановку.
|
||
|
||
Такое ограничение соответствует принципу минимального безопасного Build: добавляется только функциональность, необходимая для последующей интеграции Candles Feed в сервисный слой.
|
||
|
||
---
|
||
|
||
## 9. Изоляция от Acquisition Service
|
||
|
||
В рамках Build 045 намеренно не добавлялись:
|
||
|
||
```text
|
||
CandlesAcquisitionService
|
||
```
|
||
|
||
или метод:
|
||
|
||
```text
|
||
load_candles()
|
||
```
|
||
|
||
в существующий:
|
||
|
||
```text
|
||
src/market_data/acquisition/service.py
|
||
```
|
||
|
||
Архитектурная проверка:
|
||
|
||
```bash
|
||
grep -RIn \
|
||
--exclude-dir="__pycache__" \
|
||
--exclude="*.pyc" \
|
||
"CandlesAcquisitionService\|load_candles" \
|
||
src/market_data/acquisition/service.py
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
пусто
|
||
```
|
||
|
||
Это подтверждает, что Build 045 ограничен добавлением реестра и не выполняет преждевременную интеграцию сервисного слоя.
|
||
|
||
---
|
||
|
||
## 10. Изоляция от legacy runtime
|
||
|
||
Build 045 не изменяет существующий runtime-путь получения свечей:
|
||
|
||
```text
|
||
Trading / Market Analysis
|
||
↓
|
||
ExchangeService.get_klines()
|
||
↓
|
||
legacy parsing
|
||
↓
|
||
KlineBatch
|
||
```
|
||
|
||
После Build 045 этот путь продолжает работать без изменений.
|
||
|
||
Новый канонический путь пока существует параллельно:
|
||
|
||
```text
|
||
DzengiCandlesDocumentSource
|
||
↓
|
||
DzengiCandlesDocumentHandler
|
||
↓
|
||
CandlesFeed
|
||
↓
|
||
CandlesFeedRegistry
|
||
```
|
||
|
||
Таким образом, в системе временно существуют два пути:
|
||
|
||
```text
|
||
Legacy runtime path
|
||
+
|
||
Canonical Candles Feed path
|
||
```
|
||
|
||
Это ожидаемое переходное состояние безопасной поэтапной миграции.
|
||
|
||
---
|
||
|
||
## 11. Unit-тесты
|
||
|
||
Расширен файл:
|
||
|
||
```text
|
||
tests/unit/market_data/acquisition/test_registry.py
|
||
```
|
||
|
||
Добавлено покрытие для:
|
||
|
||
- создания `CandlesFeedRegistry`;
|
||
- регистрации Candles Feed;
|
||
- получения зарегистрированного Feed;
|
||
- нормализации внешних пробелов имени источника;
|
||
- case-sensitive поведения имён источников;
|
||
- отклонения пустого имени;
|
||
- отклонения имени, состоящего только из пробелов;
|
||
- отклонения объекта, не соответствующего `CandlesFeedProtocol`;
|
||
- защиты от повторной регистрации;
|
||
- проверки, что повторная регистрация не заменяет исходный Feed;
|
||
- получения неизвестного источника;
|
||
- сохранения identity зарегистрированного Feed;
|
||
- отсутствия вызова `load_candles()` при регистрации Feed;
|
||
- отсутствия вызова `load_candles()` при получении Feed;
|
||
- соответствия зарегистрированного объекта `CandlesFeedProtocol`;
|
||
- корректной работы специализированного `CandleFeedRegistryError`.
|
||
|
||
---
|
||
|
||
## 12. Результаты targeted-тестов
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
python -m pytest -q \
|
||
tests/unit/market_data/acquisition/test_registry.py
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
61 passed in 0.04s
|
||
```
|
||
|
||
---
|
||
|
||
## 13. Результаты полного набора тестов
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
python -m pytest -q
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
707 passed in 2.87s
|
||
```
|
||
|
||
Все unit-тесты проекта проходят успешно.
|
||
|
||
---
|
||
|
||
## 14. Архитектурная проверка реестра
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
grep -RIn \
|
||
--exclude-dir="__pycache__" \
|
||
--exclude="*.pyc" \
|
||
"CandlesFeedRegistry\|CandleFeedRegistryError" \
|
||
src tests
|
||
```
|
||
|
||
Результат подтверждает, что новые сущности находятся только в ожидаемых областях:
|
||
|
||
```text
|
||
src/market_data/acquisition/registry.py
|
||
src/market_data/acquisition/exceptions.py
|
||
tests/unit/market_data/acquisition/test_registry.py
|
||
```
|
||
|
||
Неожиданных зависимостей не обнаружено.
|
||
|
||
---
|
||
|
||
## 15. Проверка отсутствия преждевременной сервисной интеграции
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
grep -RIn \
|
||
--exclude-dir="__pycache__" \
|
||
--exclude="*.pyc" \
|
||
"CandlesAcquisitionService\|load_candles" \
|
||
src/market_data/acquisition/service.py
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
пусто
|
||
```
|
||
|
||
Это подтверждает, что Build 045 не расширяет `MarketDataAcquisitionService` и не переключает runtime.
|
||
|
||
---
|
||
|
||
## 16. Проверка синтаксиса
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
python -m compileall \
|
||
src/market_data/acquisition/exceptions.py \
|
||
src/market_data/acquisition/registry.py \
|
||
tests/unit/market_data/acquisition/test_registry.py
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
успешно
|
||
```
|
||
|
||
Ошибок синтаксиса не обнаружено.
|
||
|
||
---
|
||
|
||
## 17. Проверка форматирования diff
|
||
|
||
Первоначальная проверка:
|
||
|
||
```bash
|
||
git diff --check
|
||
```
|
||
|
||
обнаружила одну лишнюю пустую строку в конце:
|
||
|
||
```text
|
||
app/tests/unit/market_data/acquisition/test_registry.py:691: new blank line at EOF.
|
||
```
|
||
|
||
После удаления лишней пустой строки необходимо повторно выполнить:
|
||
|
||
```bash
|
||
git diff --check
|
||
```
|
||
|
||
Финальный ожидаемый результат:
|
||
|
||
```text
|
||
пустой вывод
|
||
```
|
||
|
||
До создания commit эта проверка должна быть подтверждена.
|
||
|
||
---
|
||
|
||
## 18. Итоговая архитектура после Build 045
|
||
|
||
После завершения Build 045 канонический Candles Feed имеет следующую структуру:
|
||
|
||
```text
|
||
Dzengi REST /api/v1/klines
|
||
↓
|
||
DzengiCandlesDocumentSource
|
||
↓
|
||
validate_candles_schema()
|
||
↓
|
||
parse_candles()
|
||
↓
|
||
validate_candles_values()
|
||
↓
|
||
map_candles()
|
||
↓
|
||
Candle
|
||
↓
|
||
DzengiCandlesDocumentHandler
|
||
↓
|
||
CandlesFeed
|
||
↓
|
||
CandlesFeedRegistry
|
||
```
|
||
|
||
При этом существующий production/runtime-путь остаётся неизменным:
|
||
|
||
```text
|
||
Trading / Market Analysis
|
||
↓
|
||
ExchangeService.get_klines()
|
||
↓
|
||
legacy parsing
|
||
↓
|
||
KlineBatch
|
||
```
|
||
|
||
---
|
||
|
||
## 19. Что намеренно не входит в Build 045
|
||
|
||
Build 045 намеренно не выполняет:
|
||
|
||
- интеграцию Candles Feed в `MarketDataAcquisitionService`;
|
||
- изменение `ExchangeService.get_klines()`;
|
||
- изменение `Kline`;
|
||
- изменение `KlineBatch`;
|
||
- переключение `trading/market_analysis`;
|
||
- переключение HTF-анализа;
|
||
- удаление legacy parser свечей;
|
||
- добавление постоянного Candle Store;
|
||
- изменение runtime;
|
||
- изменение Telegram UI;
|
||
- изменение торговой логики.
|
||
|
||
Эти ограничения являются частью стратегии безопасной миграции.
|
||
|
||
---
|
||
|
||
## 20. Следующий Build
|
||
|
||
Следующий минимальный безопасный этап:
|
||
|
||
```text
|
||
Build 046 — интеграция канонического Candles Feed в MarketDataAcquisitionService
|
||
```
|
||
|
||
Предполагаемая цель:
|
||
|
||
```text
|
||
CandlesFeedRegistry
|
||
↓
|
||
MarketDataAcquisitionService
|
||
↓
|
||
load_candles(...)
|
||
↓
|
||
tuple[Candle, ...]
|
||
```
|
||
|
||
Build 046 должен:
|
||
|
||
- добавить поддержку `CandlesFeedRegistry` в сервисный слой `Market Data Acquisition`;
|
||
- добавить публичный метод загрузки свечей через канонический сервис;
|
||
- сохранить существующие Instrument Reference Data и Quotes Feed без изменений;
|
||
- не переключать `ExchangeService.get_klines()`;
|
||
- не удалять legacy `Kline` и `KlineBatch`;
|
||
- не изменять торговую логику;
|
||
- сохранить полную обратную совместимость.
|
||
|
||
---
|
||
|
||
## Итог
|
||
|
||
Build 045 завершает регистрацию канонического Candles Feed.
|
||
|
||
Достигнуто состояние:
|
||
|
||
```text
|
||
OHLCV Feed foundation
|
||
✓ Candle model
|
||
✓ REST document source
|
||
✓ schema validation
|
||
✓ parser
|
||
✓ value validation
|
||
✓ mapper
|
||
✓ document handler
|
||
✓ Candles Feed
|
||
✓ CandlesFeedRegistry
|
||
☐ Acquisition Service integration
|
||
☐ ExchangeService compatibility bridge
|
||
☐ Consumer migration
|
||
☐ Legacy Kline removal
|
||
```
|
||
|
||
Build 045 является небольшим изолированным архитектурным шагом и не изменяет поведение существующего рабочего торгового бота. |