# 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.