739 lines
19 KiB
Markdown
739 lines
19 KiB
Markdown
# 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. |