build 039: complete Quotes Feed migration foundation

This commit is contained in:
2026-07-14 09:58:16 +03:00
parent 26deb861bc
commit 7b62873832
443 changed files with 80452 additions and 1335 deletions

View File

@@ -0,0 +1,739 @@
# Build 036 — Перевод read-only и UI-потребителей
**Статус:** Завершён
**Результат:** Успешно
**Полная регрессия:** `614 passed`
---
## 1. Назначение Build
Цель Build 036 — перевести read-only и UI-потребителей рыночной котировки с legacy-представлений:
- `TickerPrice`;
- `dict[str, object]` из `get_market_snapshot()`;
на каноническую внутреннюю модель:
`Quote`
Build продолжает миграцию подсистемы рыночных данных на целевую архитектуру:
Market Data
Market Intelligence
Decision
Execution
Exchange
В рамках Build 036 изменяется исключительно read-only контур.
Execution pricing, торговые стратегии, signal runtime и execution quality не переводятся и не изменяются.
---
## 2. Исходное состояние
До Build 036 read-only и UI-потребители получали текущую рыночную котировку через несколько legacy-интерфейсов.
Основные варианты:
UI / diagnostics
ExchangeService.get_price()
TickerPrice
или:
UI / diagnostics
ExchangeService.get_market_snapshot()
dict[str, object]
При этом после предыдущих Build каноническая модель `Quote` уже существовала и использовалась внутри новой инфраструктуры:
REST Quotes Feed
Quote
WebSocket quote adapter
Quote
MarketPriceCache
Quote Store
Quote
Таким образом, read-only потребители продолжали зависеть от compatibility-представлений, несмотря на наличие канонической модели котировки.
---
## 3. Целевое состояние
После Build 036 read-only и UI-потребители получают канонический объект:
`Quote`
через публичный facade:
`ExchangeService.get_quote()`
Целевая цепочка чтения:
read-only / UI consumer
ExchangeService.get_quote()
MarketPriceCache.get_quote()
Quote Store
canonical Quote
При отсутствии свежей котировки используется REST fallback:
ExchangeService.get_quote()
REST Quotes Feed
canonical Quote
MarketPriceCache.set_quote()
Quote Store
canonical Quote
---
## 4. Архитектурный принцип
Read-only и UI-потребители не обращаются напрямую к:
`Quote Store`
Доступ выполняется через:
`ExchangeService.get_quote()`
Это позволяет сохранить единый facade, отвечающий за:
- выбор default symbol;
- нормализацию и валидацию символа;
- нормализацию `runtime_key`;
- поддержку mock mode;
- чтение канонической котировки из cache/store;
- проверку свежести;
- REST fallback;
- преобразование внутренних ошибок в `ExchangeError`.
Целевая граница:
UI / diagnostics
ExchangeService
MarketPriceCache
Quote Store
UI не должен самостоятельно знать:
- структуру ключей Quote Store;
- `source_name`;
- правила `runtime_key`;
- freshness policy;
- правила REST fallback;
- внутреннюю обработку ошибок Acquisition Layer.
---
## 5. Изменённые production-файлы
В рамках Build 036 изменены:
src/integrations/exchange/market_cache.py
src/integrations/exchange/service.py
src/telegram/ui/currency_ui.py
src/telegram/handlers/auto/ui.py
src/telegram/handlers/debug_auto/ui.py
src/trading/diagnostics/snapshot.py
---
## 6. Изменённые и добавленные тесты
Изменены:
tests/unit/integrations/exchange/test_market_cache.py
tests/unit/telegram/ui/test_currency_ui.py
Добавлен:
tests/unit/integrations/exchange/test_service_quote.py
После Build 036 общее количество тестов увеличилось:
после Build 035: 608 passed
после Build 036: 614 passed
Добавлено:
6 тестов
---
## 7. Изменения в MarketPriceCache
Файл:
`src/integrations/exchange/market_cache.py`
Добавлен канонический read API:
`MarketPriceCache.get_quote()`
Его назначение — вернуть непосредственно канонический объект `Quote`, сохранённый в Quote Store.
Цепочка:
MarketPriceCache.get_quote()
QuoteStore.get()
Quote | None
Метод:
- нормализует `symbol`;
- нормализует `runtime_key`;
- читает котировку из собственного namespace Quote Store;
- возвращает канонический `Quote`;
- не создаёт `MarketPriceSnapshot`;
- не выполняет преобразование `Decimal` в `float`;
- сохраняет канонический объект котировки.
Legacy API:
`MarketPriceCache.get_price()`
сохранён для compatibility-потребителей, которые ещё не переведены на `Quote`.
---
## 8. Изменения в ExchangeService
Файл:
`src/integrations/exchange/service.py`
Добавлен публичный канонический read API:
`ExchangeService.get_quote()`
Метод сохраняет обязанности facade и отвечает за:
1. выбор default symbol;
2. поддержку mock mode;
3. валидацию символа;
4. нормализацию `runtime_key`;
5. чтение канонической котировки из MarketPriceCache;
6. проверку свежести;
7. REST fallback при cache miss или stale quote;
8. сохранение свежей котировки в Quote Store через MarketPriceCache;
9. возврат канонического `Quote`.
Целевая цепочка:
ExchangeService.get_quote()
validate_symbol()
MarketPriceCache.get_quote()
freshness check
Quote
При отсутствии свежей котировки:
ExchangeService.get_quote()
REST Quotes Feed
Quote
MarketPriceCache.set_quote()
Quote Store
---
## 9. Политика свежести
Для read-only и UI-потребителей сохранена существующая политика свежести рыночной котировки.
Возраст котировки определяется по:
`Quote.received_at`
Если сохранённая котировка достаточно свежая, возвращается существующий канонический объект.
Если котировка отсутствует или устарела, выполняется REST fallback через новый Quotes Feed.
Таким образом:
fresh cached Quote
вернуть Quote без REST-запроса
stale cached Quote
REST Quotes Feed
сохранить новый Quote
вернуть новый Quote
---
## 10. REST fallback
REST fallback использует новую каноническую цепочку Acquisition Layer:
Dzengi REST API
GET /api/v1/ticker/24hr
DzengiQuoteDocumentSource
DzengiQuoteDocumentHandler
schema validation
parsing
value validation
mapping
canonical Quote
Полученный объект сохраняется:
Quote
MarketPriceCache.set_quote()
Quote Store
Не используется лишний цикл преобразований:
Quote
dict
float
новый Quote
Канонический объект остаётся `Quote` на всём новом пути.
---
## 11. Mock mode
`ExchangeService.get_quote()` сохраняет поддержку режима:
`exchange_enabled = False`
В этом режиме потребителю также возвращается канонический:
`Quote`
Mock-котировка содержит:
- `symbol`;
- `last_price`;
- `bid_price`;
- `ask_price`;
- `exchange_timestamp`;
- `received_at`;
- `source`.
Таким образом, потребители `get_quote()` не должны знать, работает приложение с реальной биржей или в mock mode.
---
## 12. Обработка ошибок
Новый canonical read API сохраняет существующую границу ошибок ExchangeService.
Ошибки Acquisition Layer:
- не передаются напрямую UI-потребителям;
- логируются в контексте `ticker/24hr`;
- преобразуются во внешний `ExchangeError`;
- сохраняют исходную ошибку через `__cause__`.
Граница остаётся следующей:
Acquisition error
ExchangeService
ExchangeError
UI / diagnostics consumer
---
## 13. Перевод currency_ui.py
Файл:
`src/telegram/ui/currency_ui.py`
До Build 036 использовался legacy API:
`ExchangeService.get_price()`
Возвращаемая модель:
`TickerPrice`
Для расчёта использовалось:
`ticker.price`
После Build 036 используется:
`ExchangeService.get_quote()`
и каноническое поле:
`quote.last_price`
Целевая цепочка:
currency_ui
ExchangeService.get_quote()
Quote.last_price
Локальная логика расчёта стоимости баланса, обработка `ExchangeError` и кэширование рассчитанных цен сохранены.
---
## 14. Перевод auto/ui.py
Файл:
`src/telegram/handlers/auto/ui.py`
До Build 036 UI получал legacy market snapshot:
`ExchangeService.get_market_snapshot()`
и работал с:
`dict[str, object]`
Основные поля:
- `last_price`;
- `bid_price`;
- `ask_price`.
После Build 036 UI получает:
`Quote`
через:
`ExchangeService.get_quote()`
Используются канонические поля:
- `quote.last_price`;
- `quote.bid_price`;
- `quote.ask_price`.
В результате UI больше не зависит от legacy market snapshot dict для получения текущей рыночной котировки.
---
## 15. Перевод debug_auto/ui.py
Файл:
`src/telegram/handlers/debug_auto/ui.py`
В debug UI разделены две разные сущности:
1. текущая рыночная котировка;
2. execution snapshot.
После Build 036 market-секция использует:
`Quote`
и читает:
- `last_price`;
- `bid_price`;
- `ask_price`;
- `source`;
- `received_at`;
- `exchange_timestamp`.
Execution-секция продолжает использовать:
`ExecutionPriceSnapshot`
Это намеренное разделение.
Build 036 не изменяет execution semantics.
Целевая схема:
Debug UI
├── Market section
│ ↓
│ Quote
└── Execution section
ExecutionPriceSnapshot
---
## 16. Перевод trading/diagnostics/snapshot.py
Файл:
`src/trading/diagnostics/snapshot.py`
До Build 036 диагностика использовала:
`ExchangeService.get_market_snapshot()`
После Build 036 используется:
`ExchangeService.get_quote()`
Для выбора диагностической цены сохраняется существующая семантика:
BUY
quote.ask_price
SELL
quote.bid_price
другое состояние
quote.last_price
Build не изменяет торговые решения и используется только для read-only диагностики.
---
## 17. Сохранённые legacy API
Build 036 не удаляет:
- `ExchangeService.get_price()`;
- `ExchangeService.get_market_snapshot()`;
- `ExchangeService.get_execution_snapshot()`;
- `ExchangeService.get_fresh_market_snapshot()`;
- `MarketPriceCache.get_price()`.
Они сохраняются для ещё не переведённых compatibility-потребителей.
Удаление legacy API возможно только после полного перевода всех зависимых компонентов и отдельной проверки использования.
---
## 18. Что намеренно не изменялось
Build 036 не затрагивает:
src/trading/execution/pricing.py
src/trading/auto/signal_runtime.py
src/trading/auto/execution_quality.py
src/trading/strategies/trend.py
src/trading/strategies/scalp.py
src/trading/debug/execution.py
Эти компоненты относятся к:
- execution pricing;
- signal runtime;
- execution quality;
- strategy decisions;
- debug execution semantics.
Их перевод должен выполняться отдельно.
---
## 19. Что не входит в Build 036
В рамках Build 036 не выполнялись:
- перевод execution pricing;
- перевод signal runtime;
- перевод execution quality;
- перевод торговых стратегий;
- удаление `TickerPrice`;
- удаление `get_price()`;
- удаление `get_market_snapshot()`;
- удаление legacy market snapshot dict layer;
- удаление `ExecutionPriceSnapshot`;
- удаление `MarketPriceCache`;
- удаление legacy parsing helpers.
---
## 20. Проверка синтаксиса
Выполнена команда:
python -m py_compile \
src/integrations/exchange/market_cache.py \
src/integrations/exchange/service.py \
src/telegram/ui/currency_ui.py \
src/telegram/handlers/auto/ui.py \
src/telegram/handlers/debug_auto/ui.py \
src/trading/diagnostics/snapshot.py \
tests/unit/integrations/exchange/test_market_cache.py \
tests/unit/integrations/exchange/test_service_quote.py \
tests/unit/telegram/ui/test_currency_ui.py
Результат:
`Успешно`
Ошибок синтаксиса не обнаружено.
---
## 21. Специализированные тесты
Выполнена команда:
python -m pytest \
tests/unit/integrations/exchange/test_market_cache.py \
tests/unit/integrations/exchange/test_service_quote.py \
tests/unit/telegram/ui/test_currency_ui.py \
-q
Результат:
`44 passed in 0.29s`
---
## 22. Полная регрессия
Выполнена команда:
`python -m pytest -q`
Результат:
`614 passed in 0.27s`
Полная тестовая регрессия проекта успешно пройдена.
---
## 23. Итоговая архитектура после Build 036
После завершения Build 036 read-only и UI-контур использует следующую архитектуру:
┌──────────────────────────────┐
│ currency_ui │
│ auto UI │
│ debug UI market section │
│ trading diagnostics │
└──────────────┬───────────────┘
ExchangeService.get_quote()
MarketPriceCache.get_quote()
Quote Store
canonical Quote
При cache miss или stale quote:
ExchangeService.get_quote()
REST Quotes Feed
canonical Quote
MarketPriceCache.set_quote()
Quote Store
---
## 24. Результат Build
Build 036 успешно завершён.
Достигнуты следующие результаты:
- добавлен канонический read API `MarketPriceCache.get_quote()`;
- добавлен публичный facade `ExchangeService.get_quote()`;
- сохранены symbol validation, mock mode, freshness policy и REST fallback;
- `currency_ui.py` переведён с `TickerPrice` на `Quote`;
- `auto/ui.py` переведён с legacy market snapshot dict на `Quote`;
- market-секция debug UI переведена на `Quote`;
- execution-секция debug UI оставлена на `ExecutionPriceSnapshot`;
- trading diagnostics переведена на `Quote`;
- execution-контур не изменён;
- legacy API сохранены для последующей миграции;
- специализированные тесты успешно пройдены;
- полная регрессия успешно пройдена.
Итог:
До Build 036:
UI / diagnostics
TickerPrice / market snapshot dict
После Build 036:
UI / diagnostics
ExchangeService.get_quote()
canonical Quote
---
## 25. Следующий этап
Следующий этап миграции:
**Build 037 — Перевод execution-потребителей**
Его задача — отдельно проанализировать и перевести execution-sensitive потребителей канонической рыночной котировки без изменения торговой семантики и без преждевременного удаления legacy compatibility API.