Files
dzentra_bot/docs/migrations/build_039.md

718 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Build 039 — Завершение миграции consumers рыночной цены на каноническую модель Quote
**Статус:** Завершён
**Проект:** Dzentra
**Подсистема:** Market Data Acquisition / Exchange Integration / Trading Consumers
**Тип изменения:** Миграция legacy price/snapshot consumers
**Язык документа:** Русский
---
## 1. Цель Build 039
Цель Build 039 — завершить миграцию существующих consumers рыночной цены и рыночных snapshot-моделей с legacy-контрактов на каноническую модель:
```text
src.market_data.acquisition.models.quote.Quote
```
и соответствующий новый pipeline получения, хранения и использования котировок.
Build 039 должен устранить использование следующих legacy-сущностей и методов:
```text
TickerPrice
MarketPriceSnapshot
get_price(
get_market_snapshot(
get_fresh_market_snapshot(
refresh_price_cache(
refresh_market_snapshot_cache(
_get_real_price(
```
При этом необходимо сохранить:
- работоспособность существующего торгового бота;
- существующее пользовательское поведение;
- существующие UI-контракты;
- форматирование значений;
- форматирование возраста данных;
- форматирование времени;
- fallback-поведение;
- exception semantics;
- существующую торговую логику, не относящуюся непосредственно к миграции котировок.
---
## 2. Исходное состояние
До Build 039 в проекте одновременно существовали:
1. новая каноническая модель котировки:
```text
Quote
```
2. новый Market Data Acquisition pipeline;
3. новый Quote Store;
4. новый cache-контракт на основе `Quote`;
5. legacy-модели:
```text
TickerPrice
MarketPriceSnapshot
```
6. legacy-методы получения цены и snapshot:
```text
get_price()
get_market_snapshot()
get_fresh_market_snapshot()
refresh_price_cache()
refresh_market_snapshot_cache()
_get_real_price()
```
7. consumers, которые продолжали зависеть от старых контрактов.
Такое состояние создавало несколько параллельных представлений одной и той же рыночной информации и препятствовало завершению миграции Market Data Acquisition.
---
## 3. Архитектурный принцип Build 039
После Build 039 каноническим представлением текущей рыночной котировки является:
```python
Quote
```
из:
```text
src.market_data.acquisition.models.quote
```
Архитектурный поток:
```text
Dzengi API / Stream
Market Data Acquisition
Quote
Quote Store / Market Price Cache
ExchangeService
Trading / Telegram / Diagnostics consumers
```
Legacy-модели `TickerPrice` и `MarketPriceSnapshot` больше не должны использоваться в `src` и `tests`.
---
## 4. Каноническая модель Quote
Каноническая модель расположена в:
```text
app/src/market_data/acquisition/models/quote.py
```
Она является единым представлением текущей котировки инструмента и содержит данные, необходимые downstream-consumers:
```text
symbol
last_price
bid_price
ask_price
exchange_timestamp
received_at
source
```
Использование нескольких параллельных price/snapshot-моделей для одной и той же задачи после Build 039 не допускается.
---
## 5. Изменённые области проекта
В рамках Build 039 были затронуты следующие основные области.
### 5.1. Exchange Integration
Основные файлы:
```text
app/src/integrations/exchange/market_cache.py
app/src/integrations/exchange/market_data_runner.py
app/src/integrations/exchange/market_stream.py
app/src/integrations/exchange/mock_data.py
app/src/integrations/exchange/models.py
app/src/integrations/exchange/service.py
app/src/integrations/exchange/status.py
```
Основные изменения:
- переход cache на каноническую модель `Quote`;
- переход market data runner на `Quote`;
- переход market stream на `Quote`;
- переход mock-котировок на `Quote`;
- удаление legacy price/snapshot-моделей;
- перевод `ExchangeService` на канонические методы получения котировок;
- сохранение runtime-status поведения;
- сохранение legacy dict-контракта там, где он ещё необходим существующему UI.
---
### 5.2. Storage
Основные файлы:
```text
app/src/storage/quote_store.py
```
Quote Store используется как канонический storage-контракт для текущих котировок.
Store работает с моделью:
```text
Quote
```
и не должен зависеть от:
```text
TickerPrice
MarketPriceSnapshot
```
---
### 5.3. Telegram UI
Основные файлы:
```text
app/src/telegram/handlers/auto/ui.py
app/src/telegram/handlers/debug_auto/ui.py
app/src/telegram/ui/currency_ui.py
```
Consumers пользовательского интерфейса переведены на новые quote-контракты.
Критически важное требование:
> Миграция внутренней модели данных не должна сама по себе изменять пользовательское представление данных.
Поэтому при миграции должны сохраняться существующие форматы:
- цены;
- возраста данных;
- времени обновления;
- UI-строк;
- fallback-значений.
---
### 5.4. Trading Auto
Основные файлы:
```text
app/src/trading/auto/execution_quality.py
app/src/trading/auto/signal_runtime.py
```
Торговые consumers переведены с legacy market snapshot API на канонические quote-контракты.
Торговая логика, не относящаяся непосредственно к источнику котировки, не должна изменяться в рамках Build 039.
---
### 5.5. Trading Debug
Основной файл:
```text
app/src/trading/debug/execution.py
```
Debug execution переведён на новый quote-контракт.
Legacy-вызов:
```text
get_fresh_market_snapshot()
```
удалён.
---
### 5.6. Trading Diagnostics
Основной файл:
```text
app/src/trading/diagnostics/snapshot.py
```
Diagnostics consumer переведён на актуальное представление рыночной котировки.
---
### 5.7. Trading Strategies
Основные файлы:
```text
app/src/trading/strategies/scalp.py
app/src/trading/strategies/trend.py
```
Стратегии переведены с legacy snapshot API на каноническую модель `Quote`.
После миграции стратегии не должны использовать:
```text
get_market_snapshot()
```
---
## 6. ExecutionPriceSnapshot
В рамках Build 039 необходимо различать две сущности:
```text
Quote
```
и:
```text
ExecutionPriceSnapshot
```
`Quote` является канонической моделью текущей рыночной котировки.
`ExecutionPriceSnapshot` является специализированным downstream-контрактом execution layer и может существовать отдельно, поскольку представляет данные в форме, непосредственно необходимой исполнению торговых операций.
Таким образом:
```text
Quote
ExecutionPriceSnapshot
```
не является дублированием legacy-модели `MarketPriceSnapshot`.
`ExecutionPriceSnapshot` сохраняется как специализированная execution-модель.
---
## 7. Runtime status и stale market data
Во время Build 039 особое внимание было уделено методу:
```python
ExchangeService.get_symbol_runtime_status()
```
Для открытого рынка выполняется проверка актуальности рыночной котировки.
Актуальный поток:
```text
validate_symbol()
build_market_status_from_symbol_status()
если рынок открыт
_get_fresh_quote()
exchange_timestamp
_exchange_timestamp_age_seconds()
если возраст > 60 секунд
build_market_stale_status()
```
Порог stale market data:
```text
age_seconds > 60
```
При возрасте ровно:
```text
60.0
```
рынок остаётся в статусе `OPEN`.
При возрасте:
```text
61.0
```
возвращается:
```text
ExchangeStatusCode.BREAK
reason = "market_data_stale"
raw_status = "STALE_MARKET_DATA"
```
---
## 8. Формат времени stale market data
Для stale market status используется exchange timestamp котировки.
Преобразование выполняется следующим образом:
```python
exchange_timestamp_ms = (
int(quote.exchange_timestamp.timestamp() * 1000)
if quote.exchange_timestamp is not None
else None
)
```
После этого timestamp передаётся в:
```python
self._format_exchange_time(exchange_timestamp_ms)
```
Это необходимо для сохранения существующего формата пользовательского сообщения.
Пример:
```text
10.07.2026 12:00:00
```
Недопустимо передавать объект `datetime` непосредственно в функцию, ожидающую числовой timestamp в миллисекундах.
---
## 9. Сохранение legacy-поведения форматирования
Во время реализации Build 039 было выявлено критически важное правило миграции.
Две функции, решающие похожую задачу, не являются автоматически эквивалентными.
Например:
```python
def _format_age(value: object) -> str:
if value is None:
return ""
try:
age = max(0.0, float(value))
except (TypeError, ValueError):
return ""
if age < 1:
return f"{age:.2f}с"
if age < 10:
return f"{age:.1f}с"
total_seconds = int(age)
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
seconds = total_seconds % 60
if hours > 0:
return f"{hours}ч {minutes:02d}м"
if minutes > 0:
return f"{minutes}м {seconds:02d}с"
```
и:
```python
def _format_age(
value: NumericLike | None,
) -> str:
if value is None:
return ""
try:
age = max(0.0, float(value))
except (TypeError, ValueError):
return ""
if age < 1:
return "< 1 сек."
return f"{age:.1f} сек."
```
неэквивалентны.
Они различаются по:
- формату значений меньше секунды;
- единицам измерения;
- точности;
- представлению минут;
- представлению часов.
Следовательно, при миграции consumer нельзя заменять старую функцию новой только на основании сходства назначения.
Необходимо сравнивать фактические контракты поведения.
---
## 10. Тесты Build 039
В рамках Build 039 использовались и актуализировались следующие основные тесты:
```text
app/tests/unit/integrations/exchange/test_market_cache.py
app/tests/unit/integrations/exchange/test_market_data_runner.py
app/tests/unit/integrations/exchange/test_market_stream.py
app/tests/unit/integrations/exchange/test_service_execution_quote.py
app/tests/unit/integrations/exchange/test_service_quote.py
app/tests/unit/integrations/exchange/test_service_symbol_runtime_status.py
app/tests/unit/storage/test_quote_store.py
app/tests/unit/trading/auto/test_execution_quality.py
app/tests/unit/trading/auto/test_signal_runtime_quote.py
app/tests/unit/trading/debug/test_execution.py
app/tests/unit/trading/strategies/test_scalp_quote.py
app/tests/unit/trading/strategies/test_trend_quote.py
```
Тесты должны проверять не только отсутствие ошибок выполнения, но и отсутствие возврата к legacy API.
---
## 11. Исправление legacy-guard тестов
После переключения production consumers некоторые тесты всё ещё содержали искусственные методы-заглушки вида:
```python
def get_market_snapshot(self, *_: object, **__: object) -> object:
raise AssertionError("legacy get_market_snapshot() must not be used")
```
и:
```python
def get_fresh_market_snapshot(self, *_: object, **__: object) -> object:
raise AssertionError("legacy get_fresh_market_snapshot() must not be used")
```
После завершения миграции эти заглушки были удалены из тестов, поскольку финальный архитектурный grep должен подтверждать полное отсутствие legacy API во всём дереве:
```text
src
tests
```
Это касается файлов:
```text
tests/unit/trading/auto/test_execution_quality.py
tests/unit/trading/auto/test_signal_runtime_quote.py
tests/unit/trading/strategies/test_trend_quote.py
tests/unit/trading/strategies/test_scalp_quote.py
tests/unit/trading/debug/test_execution.py
```
---
## 12. Проверка синтаксиса
В ходе реализации выполнялась проверка изменённых Python-файлов:
```bash
python -m py_compile \
src/integrations/exchange/service.py \
tests/unit/integrations/exchange/test_service_symbol_runtime_status.py
```
Проверка завершилась успешно.
---
## 13. Целевая проверка runtime status
Выполнена команда:
```bash
python -m pytest \
tests/unit/integrations/exchange/test_service_symbol_runtime_status.py \
-q
```
Финальный результат:
```text
34 passed in 1.44s
```
---
## 14. Полный regression suite
После завершения всех изменений выполнен полный набор тестов:
```bash
python -m pytest -q
```
Финальный результат:
```text
606 passed in 1.63s
```
Все тесты успешно пройдены.
---
## 15. Финальный архитектурный grep
Выполнена команда:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
-E "TickerPrice|MarketPriceSnapshot|get_price\(|get_market_snapshot\(|get_fresh_market_snapshot\(|refresh_price_cache\(|refresh_market_snapshot_cache\(|_get_real_price\(" \
src tests
```
Финальный результат:
```text
<пусто>
```
Это подтверждает отсутствие legacy price/snapshot API в:
```text
src
tests
```
---
## 16. Критерии завершения Build 039
Build 039 считается завершённым, поскольку выполнены все критерии:
- [x] `TickerPrice` удалён из `src` и `tests`;
- [x] `MarketPriceSnapshot` удалён из `src` и `tests`;
- [x] `get_price()` удалён из `src` и `tests`;
- [x] `get_market_snapshot()` удалён из `src` и `tests`;
- [x] `get_fresh_market_snapshot()` удалён из `src` и `tests`;
- [x] `refresh_price_cache()` удалён из `src` и `tests`;
- [x] `refresh_market_snapshot_cache()` удалён из `src` и `tests`;
- [x] `_get_real_price()` удалён из `src` и `tests`;
- [x] trading consumers используют новый quote-контракт;
- [x] Telegram consumers используют новый quote-контракт;
- [x] diagnostics consumers используют новый quote-контракт;
- [x] стратегии используют новый quote-контракт;
- [x] runtime status использует каноническую модель `Quote`;
- [x] stale market data detection сохранена;
- [x] формат exchange timestamp восстановлен;
- [x] legacy UI-поведение сохранено;
- [x] целевые тесты проходят;
- [x] полный regression suite проходит;
- [x] финальный архитектурный grep пуст.
---
## 17. Итоговое состояние после Build 039
После завершения Build 039 архитектура текущих котировок приведена к следующему состоянию:
```text
Exchange / Market Data Sources
Market Data Acquisition
Canonical Quote
Quote Store / Market Price Cache
ExchangeService
┌─────────────────────────────────────────┐
│ Trading Auto │
│ Trading Strategies │
│ Trading Debug │
│ Trading Diagnostics │
│ Telegram UI │
│ Execution │
└─────────────────────────────────────────┘
```
Legacy price/snapshot API полностью удалён из production-кода и тестов.
Build 039 завершает миграцию consumers текущей рыночной цены на каноническую модель `Quote`.
---
## 18. Зафиксированный принцип для следующих Builds
Начиная с Build 039, при любой последующей миграции необходимо соблюдать следующее правило:
> Замена legacy-компонента новым каноническим компонентом не должна незаметно изменять существующее внешнее или внутреннее поведение consumers.
Перед заменой необходимо сравнивать:
```text
тип входных данных
тип возвращаемого значения
формат результата
точность
единицы измерения
граничные условия
fallback-поведение
exception semantics
временные зоны
формат времени
UI-представление
```
Любое намеренное изменение поведения должно быть явно зафиксировано в документации соответствующего Build и покрыто тестами.
---
## 19. Статус Build
```text
Build 039: COMPLETED
Regression suite: 606 passed
Legacy grep: empty
Canonical quote model: Quote
```
Build 039 полностью завершён.