diff --git a/docs/migrations/build_039.md b/docs/migrations/build_039.md new file mode 100644 index 0000000..00321b6 --- /dev/null +++ b/docs/migrations/build_039.md @@ -0,0 +1,718 @@ +# 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 полностью завершён. \ No newline at end of file diff --git a/docs/migrations/build_040.md b/docs/migrations/build_040.md new file mode 100644 index 0000000..7c055a8 --- /dev/null +++ b/docs/migrations/build_040.md @@ -0,0 +1,853 @@ +# Build 040 — Восстановление принудительного REST refresh котировки на канонической модели Quote + +**Статус:** Завершён +**Проект:** Dzentra +**Подсистема:** Market Data Acquisition / Exchange Integration / Market Data Runtime +**Тип изменения:** Восстановление runtime-контракта после миграции legacy price/snapshot API +**Язык документа:** Русский + +--- + +## 1. Цель Build 040 + +Цель Build 040 — восстановить принудительный REST refresh текущей рыночной котировки для `MarketDataRunner` после завершения Build 039 и удаления legacy price/snapshot API. + +В 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() +``` + +После завершения миграции был обнаружен один оставшийся активный consumer удалённого метода: + +```text +src/integrations/exchange/market_data_runner.py +``` + +который продолжал вызывать: + +```python +ExchangeService().refresh_market_snapshot_cache +``` + +Таким образом, целью Build 040 являлось не изменение runtime-архитектуры и не новый рефакторинг, а минимальное восстановление разорванного контракта на основе канонической модели: + +```text +src.market_data.acquisition.models.quote.Quote +``` + +При этом необходимо было сохранить существующую логику работы старого бота без изменений. + +--- + +## 2. Исходное состояние + +После Build 039 проект находился в следующем состоянии: + +1. все основные consumers текущей рыночной цены были переведены на `Quote`; + +2. legacy-модели: + +```text +TickerPrice +MarketPriceSnapshot +``` + +были удалены; + +3. legacy-методы получения и обновления price/snapshot были удалены; + +4. полный regression suite успешно проходил: + +```text +606 passed +``` + +5. контрольный grep по основным legacy-контрактам был пустым; + +6. при дополнительном анализе runtime-компонентов был обнаружен оставшийся вызов: + +```python +ExchangeService().refresh_market_snapshot_cache +``` + +в: + +```text +app/src/integrations/exchange/market_data_runner.py +``` + +Метод уже отсутствовал в `ExchangeService`, поэтому существовал разорванный runtime-контракт, который не проявлялся в предыдущем полном наборе тестов. + +--- + +## 3. Архитектурный принцип Build 040 + +Build 040 сохраняет утверждённый архитектурный поток: + +```text +Exchange / Market Data Sources + ↓ +Market Data Acquisition + ↓ +Canonical Quote + ↓ +Quote Store / Market Price Cache + ↓ +ExchangeService + ↓ +MarketDataRunner / Trading Consumers +``` + +Канонической моделью текущей котировки остаётся: + +```python +Quote +``` + +из: + +```text +src.market_data.acquisition.models.quote +``` + +Build 040 не вводит новую параллельную price/snapshot-модель и не восстанавливает удалённые legacy-контракты. + +Вместо этого добавляется минимальный публичный compatibility-метод: + +```python +refresh_quote_cache() +``` + +который использует существующий канонический acquisition pipeline. + +--- + +## 4. Главное требование сохранения поведения + +Основным требованием Build 040 являлось: + +> Не менять логику работы текущих файлов и существующего runtime. + +Поэтому перед реализацией была восстановлена фактическая старая реализация удалённого метода: + +```python +refresh_market_snapshot_cache() +``` + +из истории Git. + +Это позволило определить реальный контракт старого метода без предположений. + +Старая реализация выполняла: + +```text +нормализация runtime_key + ↓ +принудительное получение свежего REST snapshot + ↓ +проверка ценовых полей + ↓ +запись результата в MarketPriceCache + ↓ +возврат свежего snapshot +``` + +Критически важно: + +- cache перед REST-запросом не читался; +- всегда выполнялось принудительное получение свежих данных; +- результат сохранялся в cache соответствующего runtime; +- возвращался свежий результат; +- ошибки не подавлялись самим refresh-методом; +- mock-режим сохранялся. + +--- + +## 5. Восстановление старого контракта через Git + +Для определения фактического поведения удалённого метода была использована история Git. + +Команда: + +```bash +git log -S"def refresh_market_snapshot_cache" \ + --oneline \ + -- src/integrations/exchange/service.py +``` + +Результат: + +```text +a996f2f feat: add market data architecture and complete migration through build 039 +e97dcd3 07.4.3.16 — Production Execution Pricing Layer +``` + +Старая реализация была извлечена командой: + +```bash +git show a996f2f^:app/src/integrations/exchange/service.py \ + | grep -n -A45 -B10 "def refresh_market_snapshot_cache" +``` + +Восстановленный контракт подтвердил, что новый метод должен быть принудительным refresh-механизмом и не должен использовать обычную cache-first логику `get_quote()`. + +--- + +## 6. Почему get_quote() не подходит для REST fallback + +Существующий метод: + +```python +ExchangeService.get_quote() +``` + +реализует cache-first поведение: + +```text +валидация символа + ↓ +чтение MarketPriceCache + ↓ +если Quote свежий + ↓ +возврат cached Quote + ↓ +иначе REST acquisition +``` + +Такое поведение корректно для обычных consumers, но не подходит для: + +```python +MarketDataRunner._rest_fallback_once() +``` + +После отказа WebSocket runner должен проверить именно доступность REST-механизма. + +Если использовать `get_quote()`, возможна ситуация: + +```text +WebSocket отключился + ↓ +в cache остаётся свежий WebSocket Quote + ↓ +get_quote() возвращает cached Quote + ↓ +REST-запрос фактически не выполняется + ↓ +runner ошибочно устанавливает REST state = AVAILABLE +``` + +Поэтому Build 040 вводит отдельный принудительный refresh-контракт. + +--- + +## 7. Новый публичный контракт refresh_quote_cache() + +В `ExchangeService` добавлен метод: + +```python +def refresh_quote_cache( + self, + symbol: str | None = None, + *, + runtime_key: str | None = None, +) -> Quote: +``` + +Его ответственность: + +```text +нормализовать runtime_key + ↓ +определить symbol + ↓ +если exchange disabled + получить mock Quote +иначе + валидировать symbol + ↓ + принудительно получить свежий Quote через REST acquisition + ↓ +сохранить тот же Quote в MarketPriceCache + ↓ +вернуть тот же Quote +``` + +Метод не читает cache перед получением свежей котировки. + +--- + +## 8. Сохранение mock-поведения + +Старый метод: + +```python +refresh_market_snapshot_cache() +``` + +через: + +```python +get_fresh_market_snapshot() +``` + +поддерживал режим: + +```text +exchange_enabled = False +``` + +В этом режиме создавались mock-данные, которые затем сохранялись в cache. + +Новый метод сохраняет эквивалентное поведение через: + +```python +mock_quote(symbol_to_use) +``` + +При выключенной бирже: + +- REST acquisition не выполняется; +- validation через exchange reference data не требуется; +- создаётся канонический `Quote`; +- тот же объект сохраняется в `MarketPriceCache`; +- тот же объект возвращается consumer. + +--- + +## 9. Принудительное получение свежего Quote + +В real-режиме новый метод использует: + +```python +self._get_fresh_quote( + validation.normalized_symbol, +) +``` + +Этот метод вызывает существующий канонический pipeline: + +```text +DzengiQuoteDocumentSource + ↓ +DzengiQuoteDocumentHandler + ↓ +QuotesFeed + ↓ +QuoteFeedRegistry + ↓ +QuoteAcquisitionService + ↓ +Quote +``` + +Таким образом, Build 040 не добавляет новый способ получения котировок и не дублирует Market Data Acquisition. + +--- + +## 10. Почему QuoteAcquisitionService не изменялся + +В ходе анализа было подтверждено, что: + +```python +QuoteAcquisitionService +``` + +намеренно отвечает только за получение канонической модели через зарегистрированный feed. + +Он не должен выполнять: + +- runtime isolation; +- cache management; +- fallback orchestration; +- validation торгового runtime; +- выбор `runtime_key`; +- compatibility-поведение старого бота. + +Поэтому Build 040 не изменяет: + +```text +app/src/market_data/acquisition/service.py +app/src/market_data/acquisition/feeds/quotes_feed.py +``` + +Compatibility и runtime semantics остаются в: + +```text +ExchangeService +``` + +--- + +## 11. Изменение MarketDataRunner + +В: + +```text +app/src/integrations/exchange/market_data_runner.py +``` + +был изменён только один runtime-вызов. + +До Build 040: + +```python +ExchangeService().refresh_market_snapshot_cache +``` + +После Build 040: + +```python +ExchangeService().refresh_quote_cache +``` + +При этом не изменялись: + +- `asyncio.to_thread`; +- передаваемый symbol; +- `runtime_key`; +- `last_rest_state`; +- `last_rest_error_key`; +- WebSocket-first поведение; +- cooldown; +- retry-логика; +- journal events; +- event titles; +- lifecycle runner; +- exception handling. + +--- + +## 12. Runtime isolation + +Новый метод сохраняет существующее разделение runtime-контекстов через: + +```python +runtime_key +``` + +Перед использованием ключ нормализуется существующим методом: + +```python +self._runtime_key(runtime_key) +``` + +Котировка сохраняется через: + +```python +MarketPriceCache.set_quote( + quote, + runtime_key=normalized_runtime_key, +) +``` + +Это сохраняет независимость runtime-контекстов, включая: + +```text +auto +debug_auto +default +``` + +и другие существующие значения, если они используются текущим runtime. + +--- + +## 13. Сохранение identity канонического Quote + +Каноническая модель: + +```python +Quote +``` + +является immutable-моделью: + +```python +@dataclass(frozen=True, slots=True) +``` + +Build 040 не создаёт копию полученного объекта. + +Последовательность: + +```text +fresh Quote + ↓ +MarketPriceCache.set_quote(quote) + ↓ +return quote +``` + +В cache сохраняется тот же экземпляр, который возвращается из метода. + +Это зафиксировано тестами. + +--- + +## 14. Изменённые production-файлы + +В рамках Build 040 изменены только два production-файла: + +```text +app/src/integrations/exchange/service.py +app/src/integrations/exchange/market_data_runner.py +``` + +### 14.1. service.py + +Добавлен новый публичный метод: + +```python +refresh_quote_cache() +``` + +Существующие методы не подвергались сопутствующему рефакторингу. + +В частности, не изменялись: + +```text +get_quote() +get_execution_snapshot() +_get_fresh_quote() +_load_quote_via_acquisition() +``` + +### 14.2. market_data_runner.py + +Изменена только ссылка на удалённый legacy refresh-метод. + +Остальная логика runner сохранена. + +--- + +## 15. Изменённые тестовые файлы + +В рамках Build 040 изменены: + +```text +app/tests/unit/integrations/exchange/test_service_quote.py +app/tests/unit/integrations/exchange/test_market_data_runner.py +``` + +Добавлены тесты нового refresh-контракта и REST fallback runner. + +--- + +## 16. Тесты refresh_quote_cache() + +Тестами зафиксировано следующее поведение: + +- принудительное получение свежего `Quote`; +- отсутствие cache-first поведения; +- замена существующей cache-записи новым объектом; +- сохранение того же экземпляра `Quote`; +- использование правильного `runtime_key`; +- нормализация runtime key; +- использование default runtime key; +- сохранение `ExchangeError` для невалидного символа; +- отсутствие REST acquisition при невалидном символе; +- сохранение exception semantics acquisition pipeline; +- отсутствие обновления cache при ошибке; +- сохранение mock-поведения. + +--- + +## 17. Тесты REST fallback MarketDataRunner + +Тестами зафиксировано поведение: + +```python +MarketDataRunner._rest_fallback_once() +``` + +При успешном refresh: + +```text +refresh_quote_cache() вызывается + ↓ +передаётся текущий symbol + ↓ +передаётся context.runtime_key + ↓ +last_rest_state = AVAILABLE + ↓ +last_rest_error_key = None +``` + +При ошибке сохраняется существующее поведение runner: + +```text +last_rest_state = UNAVAILABLE +``` + +и существующая обработка ошибки не изменяется. + +--- + +## 18. Целевая проверка Build 040 + +Выполнена команда: + +```bash +python -m pytest -q \ + tests/unit/integrations/exchange/test_service_quote.py \ + tests/unit/integrations/exchange/test_market_data_runner.py +``` + +Финальный результат: + +```text +18 passed in 0.11s +``` + +Все целевые тесты Build 040 успешно пройдены. + +--- + +## 19. Связанные регрессионные тесты + +Выполнена команда: + +```bash +python -m pytest -q \ + tests/unit/integrations/exchange/test_service_execution_quote.py \ + tests/unit/integrations/exchange/test_service_symbol_runtime_status.py \ + tests/unit/integrations/exchange/test_market_cache.py \ + tests/unit/integrations/exchange/test_market_stream.py \ + tests/unit/storage/test_quote_store.py +``` + +Финальный результат: + +```text +107 passed in 1.81s +``` + +Связанные компоненты работают без регрессий. + +--- + +## 20. Полный regression suite + +После завершения Build 040 выполнен полный набор тестов: + +```bash +python -m pytest -q +``` + +Финальный результат: + +```text +614 passed in 2.03s +``` + +Все тесты успешно пройдены. + +По сравнению с Build 039 количество тестов увеличилось: + +```text +606 → 614 +``` + +Добавлено: + +```text +8 тестов +``` + +--- + +## 21. Финальный архитектурный grep + +Выполнена команда: + +```bash +grep -RIn \ + --exclude-dir="__pycache__" \ + --exclude="*.pyc" \ + -E "refresh_market_snapshot_cache|refresh_price_cache|MarketPriceSnapshot|TickerPrice" \ + src tests +``` + +Финальный результат: + +```text +<пусто> +``` + +Это подтверждает отсутствие следующих legacy-контрактов: + +```text +refresh_market_snapshot_cache +refresh_price_cache +MarketPriceSnapshot +TickerPrice +``` + +в: + +```text +src +tests +``` + +--- + +## 22. Что намеренно не изменялось + +В Build 040 намеренно не изменялись: + +```text +Market Data Acquisition architecture +Quote model +QuoteAcquisitionService +QuotesFeed +Quote Store +MarketPriceCache contract +get_quote() +get_execution_snapshot() +WebSocket adapter +market_stream.py +MarketDataRunner lifecycle +MarketDataRunner retry logic +runtime state transitions +journal events +event titles +Telegram UI +trading strategies +execution logic +diagnostics logic +``` + +Также не выполнялись: + +- переносы файлов; +- переименования каталогов; +- изменение утверждённой структуры проекта; +- добавление новых abstraction layers; +- добавление override-параметров «на будущее»; +- удаление дополнительных legacy-компонентов вне границ Build 040. + +--- + +## 23. Критерии завершения Build 040 + +Build 040 считается завершённым, поскольку выполнены все критерии: + +- [x] найден оставшийся активный вызов удалённого `refresh_market_snapshot_cache`; +- [x] фактический старый контракт восстановлен из Git; +- [x] новый контракт реализован на канонической модели `Quote`; +- [x] добавлен `refresh_quote_cache()`; +- [x] новый метод не использует cache-first поведение; +- [x] fresh Quote принудительно получается через существующий acquisition pipeline; +- [x] runtime isolation сохранена; +- [x] mock-поведение сохранено; +- [x] exception semantics сохранена; +- [x] тот же экземпляр `Quote` сохраняется и возвращается; +- [x] `MarketDataRunner` переключён на новый метод; +- [x] WebSocket-first логика не изменена; +- [x] lifecycle runner не изменён; +- [x] целевые тесты проходят; +- [x] связанные регрессионные тесты проходят; +- [x] полный regression suite проходит; +- [x] финальный legacy grep пуст. + +--- + +## 24. Итоговое состояние после Build 040 + +После завершения Build 040 runtime-поток получения котировки имеет следующий вид: + +```text +WebSocket available + ↓ +DzengiWebSocketQuoteAdapter + ↓ +Quote + ↓ +MarketPriceCache +``` + +При отказе WebSocket: + +```text +WebSocket unavailable + ↓ +MarketDataRunner._rest_fallback_once() + ↓ +ExchangeService.refresh_quote_cache() + ↓ +Quote Acquisition Pipeline + ↓ +Fresh REST Quote + ↓ +MarketPriceCache + ↓ +REST state = AVAILABLE +``` + +При ошибке REST refresh: + +```text +REST acquisition error + ↓ +ExchangeError + ↓ +MarketDataRunner existing error handling + ↓ +REST state = UNAVAILABLE +``` + +Таким образом, Build 040 восстанавливает разорванный fallback-контракт, не изменяя существующую runtime-логику. + +--- + +## 25. Зафиксированный принцип для следующих Builds + +Build 040 подтверждает принцип, зафиксированный после Build 039: + +> При миграции нельзя считать новую функцию эквивалентной старой только потому, что они решают похожую задачу. + +Перед заменой legacy-компонента необходимо восстановить и сравнить фактическое поведение: + +```text +порядок операций +чтение cache +запись cache +принудительность refresh +тип возвращаемого значения +identity результата +runtime isolation +mock-поведение +fallback-поведение +exception semantics +side effects +граничные условия +``` + +Если старый контракт доступен в истории Git, его необходимо использовать как источник фактического поведения вместо предположений. + +--- + +## 26. Статус Build + +```text +Build 040: COMPLETED +Target tests: 18 passed +Related regression tests: 107 passed +Full regression suite: 614 passed +Legacy grep: empty +Canonical quote model: Quote +REST fallback contract: refresh_quote_cache() +``` + +Build 040 полностью завершён. diff --git a/docs/migrations/build_041.md b/docs/migrations/build_041.md new file mode 100644 index 0000000..3ccf461 --- /dev/null +++ b/docs/migrations/build_041.md @@ -0,0 +1,514 @@ +# Build 041 — Аудит и удаление остатков legacy quote parsing + +**Статус:** завершён +**Подсистема:** Market Data Acquisition / Quotes Feed +**Проект:** Dzentra +**Тип изменения:** архитектурная миграция без изменения рабочего поведения +**Основное требование:** не изменять логику работы текущих файлов и наблюдаемое поведение бота + +--- + +## 1. Цель Build + +Цель Build 041 — выполнить аудит оставшихся production-зависимостей от legacy-разбора котировок и удалить подтверждённо неиспользуемую дублирующую логику WebSocket quote parsing из legacy integration layer. + +Build продолжает миграцию Quotes Feed после: + +- Build 039 — удаления legacy `TickerPrice` и market snapshot dict layer; +- Build 040 — восстановления и фиксации REST fallback через канонический Quotes acquisition pipeline. + +Основной архитектурный принцип Build 041: + +~~~text +Raw Dzengi WebSocket payload + ↓ +DzengiWebSocketQuoteAdapter + ↓ +schema validation + ↓ +parser + ↓ +value validation + ↓ +mapper + ↓ +canonical Quote + ↓ +runtime consumers +~~~ + +Production-код вне `market_data/acquisition` не должен самостоятельно интерпретировать структуру WebSocket payload для получения `bid_price` и `ask_price`. + +--- + +## 2. Основное ограничение + +Главное требование Build 041: + +> **Не менять логику работы текущих файлов.** + +В рамках Build запрещалось: + +- изменять торговую логику; +- изменять алгоритмы принятия решений; +- изменять execution pricing semantics; +- изменять формат канонической модели `Quote`; +- изменять REST Quotes Feed; +- изменять WebSocket transport; +- изменять алгоритм переподключения WebSocket; +- изменять runtime lifecycle; +- изменять журналирование; +- изменять UI; +- изменять обработку торговых сигналов; +- изменять формат runtime-состояния; +- удалять код без доказанного отсутствия production-зависимостей. + +Допускалось только удаление legacy quote parsing, если одновременно подтверждены следующие условия: + +1. production-код уже использует канонический `DzengiWebSocketQuoteAdapter`; +2. удаляемая функция больше не вызывается; +3. соответствующая логика уже реализована в `market_data/acquisition`; +4. полный набор тестов подтверждает отсутствие регрессий. + +--- + +## 3. Предварительный аудит + +Перед изменениями был выполнен аудит всех известных признаков legacy quote parsing. + +Проверялись: + +- прямое чтение `lastPrice`; +- прямое чтение `bidPrice`; +- прямое чтение `askPrice`; +- прямое чтение `closeTime`; +- прямое чтение `eventTime`; +- прямое чтение `bids`; +- прямое чтение `asks`; +- функции извлечения best bid / best ask; +- старые market snapshot helpers; +- старые ticker price helpers; +- прямое создание `Quote`; +- REST endpoint `/api/v1/ticker/24hr`; +- все production-потребители `DzengiWebSocketQuoteAdapter`; +- все production-потребители `QuotesFeed`; +- все production-потребители `QuoteAcquisitionService`. + +Аудит показал, что корректный разбор REST-котировок уже сосредоточен в: + +~~~text +src/market_data/acquisition/ +├── adapters/dzengi/models.py +├── adapters/dzengi/parser.py +├── adapters/dzengi/mapper.py +├── adapters/dzengi/rest.py +├── adapters/dzengi/websocket.py +├── validation/schema.py +├── validation/values.py +├── handlers/quotes_handler.py +├── feeds/quotes_feed.py +└── service.py +~~~ + +При этом в legacy integration layer оставались отдельные helpers, самостоятельно интерпретировавшие структуру WebSocket payload. + +--- + +## 4. Обнаруженные legacy helpers + +Аудит выявил следующие legacy-функции. + +### `src/integrations/exchange/market_stream.py` + +~~~python +def _extract_depth_prices( + event: JsonDict, +) -> tuple[float | None, float | None]: + ... +~~~ + +~~~python +def _extract_first_price( + value: object, +) -> float | None: + ... +~~~ + +Эти функции самостоятельно разбирали: + +~~~text +bids +asks +bidPrice +askPrice +~~~ + +и тем самым дублировали ответственность канонического Dzengi WebSocket quote adapter. + +### `src/integrations/exchange/market_data_runner.py` + +~~~python +def _extract_best_price( + ... +) -> float | None: + ... +~~~ + +Функция также содержала legacy-логику извлечения цены непосредственно из сырой структуры WebSocket payload. + +--- + +## 5. Подтверждение канонического пути + +До удаления legacy helpers было подтверждено, что рабочий runtime уже использует: + +~~~python +DzengiWebSocketQuoteAdapter +~~~ + +Канонический путь обработки WebSocket-котировки: + +~~~text +ExchangeWebSocketClient + ↓ +raw WebSocket message + ↓ +DzengiWebSocketQuoteAdapter.map_message() + ↓ +validate_dzengi_websocket_quote_schema() + ↓ +parse_dzengi_websocket_quote() + ↓ +validate_dzengi_websocket_quote_values() + ↓ +map_dzengi_websocket_quote_to_quote() + ↓ +Quote +~~~ + +Таким образом, production runtime получает уже каноническую модель: + +~~~python +Quote +~~~ + +и не должен повторно разбирать сырой Dzengi payload. + +--- + +## 6. Выполненные изменения + +В Build 041 удалена подтверждённо избыточная legacy quote parsing logic. + +Изменения затронули: + +~~~text +app/src/integrations/exchange/market_stream.py +app/src/integrations/exchange/market_data_runner.py +app/tests/unit/integrations/exchange/test_market_stream.py +app/tests/unit/integrations/exchange/test_market_data_runner.py +~~~ + +Удалены legacy helpers: + +~~~text +_extract_depth_prices +_extract_first_price +_extract_best_price +_extract_market_event +~~~ + +Также удалены остаточные прямые обращения вида: + +~~~text +first.get("bidPrice") +first.get("askPrice") +~~~ + +в проверяемом legacy integration-контуре. + +--- + +## 7. Что не изменялось + +Build 041 не изменял: + +### REST Quotes Feed + +Не изменялись: + +~~~text +DzengiQuoteDocumentSource +DzengiQuoteDocumentHandler +QuotesFeed +QuoteAcquisitionService +~~~ + +Не изменялся endpoint: + +~~~text +/api/v1/ticker/24hr +~~~ + +Не изменялся REST fallback, восстановленный и зафиксированный в Build 040. + +### Каноническая модель Quote + +Не изменялись: + +~~~text +symbol +last_price +bid_price +ask_price +source +exchange_timestamp +received_at +~~~ + +### WebSocket acquisition pipeline + +Не изменялись: + +~~~text +DzengiWebSocketQuoteAdapter +validate_dzengi_websocket_quote_schema +parse_dzengi_websocket_quote +validate_dzengi_websocket_quote_values +map_dzengi_websocket_quote_to_quote +~~~ + +### Runtime semantics + +Не изменялись: + +- выбор торгового инструмента; +- запуск и остановка market runtime; +- reconnect logic; +- обработка WebSocket-соединения; +- вычисление spread; +- формирование runtime-состояния; +- execution pricing; +- стратегия; +- автоторговля; +- Telegram UI; +- журнал. + +--- + +## 8. Архитектурный результат + +До Build 041 часть legacy integration layer всё ещё содержала собственную интерпретацию WebSocket payload: + +~~~text +Raw WebSocket payload + ├──→ legacy parser in market_stream + ├──→ legacy parser in market_data_runner + └──→ canonical DzengiWebSocketQuoteAdapter +~~~ + +После Build 041: + +~~~text +Raw WebSocket payload + ↓ +DzengiWebSocketQuoteAdapter + ↓ +Quote + ↓ +market_stream / market_data_runner +~~~ + +Таким образом: + +- разбор Dzengi WebSocket payload имеет единственный канонический путь; +- transport-specific parsing находится в `market_data/acquisition`; +- integration layer работает с канонической моделью `Quote`; +- дублирующая legacy parsing logic удалена; +- runtime-поведение сохранено. + +--- + +## 9. Проверки + +После изменений выполнены целевые тесты integration layer: + +~~~bash +python -m pytest -q \ + tests/unit/integrations/exchange/test_market_data_runner.py \ + tests/unit/integrations/exchange/test_market_stream.py +~~~ + +Результат: + +~~~text +15 passed in 0.10s +~~~ + +Выполнены целевые тесты канонического WebSocket quote acquisition pipeline: + +~~~bash +python -m pytest -q \ + tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_parser.py \ + tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_mapper.py \ + tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_adapter.py \ + tests/unit/market_data/acquisition/validation/test_websocket_quote_schema.py \ + tests/unit/market_data/acquisition/validation/test_websocket_quote_values.py +~~~ + +Результат: + +~~~text +24 passed in 0.02s +~~~ + +Выполнен полный набор тестов проекта: + +~~~bash +python -m pytest -q +~~~ + +Результат: + +~~~text +614 passed in 1.77s +~~~ + +--- + +## 10. Финальный grep-контроль + +После удаления legacy quote parsing выполнена проверка: + +~~~bash +grep -RIn \ + --exclude-dir="__pycache__" \ + --exclude="*.pyc" \ + -E "_extract_depth_prices|_extract_first_price|_extract_best_price|_extract_market_event|first\.get\(\"bidPrice\"\)|first\.get\(\"askPrice\"\)" \ + src tests +~~~ + +Результат: + +~~~text +совпадений нет +~~~ + +Это подтверждает отсутствие проверяемых legacy helpers и прямого parsing `bidPrice` / `askPrice` через старый integration-код. + +--- + +## 11. Сохранённые допустимые transport-specific поля + +После Build 041 в проекте по-прежнему существуют обращения к: + +~~~text +lastPrice +bidPrice +askPrice +closeTime +eventTime +bids +asks +~~~ + +Их наличие само по себе не является legacy-зависимостью. + +Они допустимы внутри канонического Dzengi acquisition adapter: + +~~~text +market_data/acquisition/adapters/dzengi/ +market_data/acquisition/validation/ +~~~ + +поскольку именно этот слой отвечает за: + +- знание transport-specific формата Dzengi; +- structural validation; +- parsing; +- value validation; +- mapping в каноническую модель `Quote`. + +Также такие поля допустимы в unit-тестах соответствующего transport adapter. + +--- + +## 12. Критерии завершения Build + +Build 041 считается завершённым, поскольку выполнены все критерии: + +- [x] проведён аудит legacy quote parsing; +- [x] подтверждён единственный канонический WebSocket parsing pipeline; +- [x] удалён `_extract_depth_prices`; +- [x] удалён `_extract_first_price`; +- [x] удалён `_extract_best_price`; +- [x] удалён `_extract_market_event`; +- [x] удалены проверяемые прямые обращения к `bidPrice` и `askPrice` в legacy integration layer; +- [x] REST Quotes Feed не изменён; +- [x] REST fallback Build 040 сохранён; +- [x] каноническая модель `Quote` не изменена; +- [x] торговая логика не изменена; +- [x] runtime semantics не изменены; +- [x] целевые integration-тесты проходят; +- [x] целевые acquisition-тесты проходят; +- [x] полный набор из 614 тестов проходит; +- [x] финальный grep не обнаруживает удалённые legacy helpers. + +--- + +## 13. Commit + +Build зафиксирован коммитом: + +~~~text +24a4da9 build 041: remove legacy websocket quote parsing +~~~ + +Предыдущий Build: + +~~~text +e68ed7f build 040: restore quote REST fallback after build 039 +~~~ + +--- + +## 14. Итог + +Build 041 завершает удаление подтверждённых остатков legacy WebSocket quote parsing из integration layer. + +Итоговая архитектура: + +~~~text +Dzengi REST ticker/24hr + ↓ +DzengiQuoteDocumentSource + ↓ +DzengiQuoteDocumentHandler + ↓ +QuotesFeed + ↓ +QuoteAcquisitionService + ↓ +Quote + +Dzengi WebSocket message + ↓ +DzengiWebSocketQuoteAdapter + ↓ +Quote + +Quote + ↓ +runtime / execution / strategy / UI consumers +~~~ + +После Build 041: + +- REST и WebSocket transport-specific parsing сосредоточен в `market_data/acquisition`; +- runtime получает каноническую модель `Quote`; +- legacy WebSocket parsing helpers удалены; +- дублирующая интерпретация raw payload устранена; +- рабочая логика текущих файлов сохранена; +- полный test suite подтверждает отсутствие обнаруженных регрессий. + +Build 041 завершён. \ No newline at end of file diff --git a/docs/migrations/build_042.md b/docs/migrations/build_042.md new file mode 100644 index 0000000..6cf1570 --- /dev/null +++ b/docs/migrations/build_042.md @@ -0,0 +1,287 @@ +# Build 042 — Удаление MarketPriceCache + +## Статус + +**Завершён** + +--- + +## 1. Цель Build 042 + +Целью Build 042 было провести аудит зависимостей legacy-компонента `MarketPriceCache`, определить безопасный способ его удаления и устранить compatibility facade без нарушения работы существующего Quotes Feed, REST fallback, WebSocket runtime и потребителей котировок. + +Основное ограничение миграции: + +> Legacy-компонент может быть удалён только после подтверждения, что его поведение полностью покрывается целевым `QuoteStore` и что все runtime-потребители переведены без изменения существующих контрактов. + +--- + +## 2. Исходное состояние + +До Build 042 в проекте существовал legacy-компонент: + +```text +src/integrations/exchange/market_cache.py +``` + +с классом: + +```text +MarketPriceCache +``` + +`MarketPriceCache` использовался как compatibility facade над: + +```text +src/storage/quote_store.py +``` + +и внутренне содержал общий экземпляр `InMemoryQuoteStore`. + +Основными потребителями были: + +```text +src/integrations/exchange/service.py +src/integrations/exchange/market_stream.py +src/integrations/exchange/market_data_runner.py +``` + +--- + +## 3. Результаты аудита + +Аудит подтвердил, что `MarketPriceCache` не содержал самостоятельной бизнес-логики хранения котировок. Его обязанности ограничивались делегированием операций `set`, `get` и `clear` в `QuoteStore`, нормализацией `runtime_key`, использованием фиксированного `source_name` и предоставлением глобального shared store через class-level `_store`. + +Фактическое хранение данных уже выполнялось через: + +```text +src/storage/quote_store.py +``` + +Основные контракты: + +```text +QuoteStoreProtocol +InMemoryQuoteStore +``` + +Таким образом, `MarketPriceCache` являлся промежуточным compatibility facade и не был необходим как самостоятельный архитектурный слой. + +--- + +## 4. Критические семантики, сохранённые при удалении + +### 4.1. Общий shared Quote Store + +Все основные производители и потребители котировок продолжают работать с одним общим экземпляром `QuoteStore`. + +```text +WebSocket / REST + ↓ + Quote model + ↓ + shared QuoteStore + ↓ + ExchangeService / runtime consumers +``` + +### 4.2. Изоляция по runtime_key + +Сохранена изоляция котировок между runtime-контекстами, включая: + +```text +auto +debug_auto +default +``` + +Ключ хранения остаётся логически эквивалентным: + +```text +(source_name, runtime_key, symbol) +``` + +### 4.3. Нормализация runtime_key + +Сохранена нормализация: + +```text +" AUTO " → "auto" +" Debug_Auto " → "debug_auto" +``` + +### 4.4. Нормализация symbol + +Сохранена нормализация символов при операциях чтения, записи и очистки. + +```text +" btc/usd_leverage " → "BTC/USD_LEVERAGE" +``` + +### 4.5. Семантика clear() + +Сохранена возможность точечной и общей очистки `QuoteStore` по `source_name`, `runtime_key`, `symbol`, комбинации фильтров и полной очистке store. + +### 4.6. REST fallback + +Удаление `MarketPriceCache` не нарушило восстановленный в Build 040 REST fallback. Если свежая котировка отсутствует в shared `QuoteStore`, `ExchangeService` сохраняет возможность загрузить котировку через новый Quotes Feed acquisition pipeline и сохранить полученный `Quote` в общий store. + +--- + +## 5. Выполненные изменения + +В рамках Build 042: + +- удалён compatibility facade `MarketPriceCache`; +- удалён файл `src/integrations/exchange/market_cache.py`; +- удалён legacy-набор тестов `tests/unit/integrations/exchange/test_market_cache.py`; +- `ExchangeService` переведён на прямую работу с shared `QuoteStore`; +- `MarketDataRunner` переведён на прямую работу с shared `QuoteStore`; +- `market_stream` переведён на прямую работу с shared `QuoteStore`; +- сохранена runtime isolation; +- сохранена нормализация символов и runtime keys; +- сохранена семантика очистки; +- сохранён REST fallback; +- сохранены существующие публичные контракты получения котировок. + +--- + +## 6. Архитектурный результат + +До Build 042: + +```text +WebSocket / REST + ↓ + Quote + ↓ + MarketPriceCache + ↓ + InMemoryQuoteStore +``` + +После Build 042: + +```text +WebSocket / REST + ↓ + Quote + ↓ + shared QuoteStore + ↓ + ExchangeService / runtime consumers +``` + +Удалён лишний промежуточный слой `MarketPriceCache`. `QuoteStore` теперь является непосредственным storage-контрактом для канонических моделей `Quote`. + +--- + +## 7. Проверка тестами + +### Quote Store + +```text +python -m pytest -q tests/unit/storage/test_quote_store.py +``` + +Результат: + +```text +64 passed in 0.04s +``` + +### ExchangeService quote contracts + +```text +python -m pytest -q tests/unit/integrations/exchange/test_service_quote.py tests/unit/integrations/exchange/test_service_execution_quote.py +``` + +Результат: + +```text +11 passed in 0.11s +``` + +### Market runtime + +```text +python -m pytest -q tests/unit/integrations/exchange/test_market_data_runner.py tests/unit/integrations/exchange/test_market_stream.py +``` + +Результат: + +```text +15 passed in 0.09s +``` + +### Полный regression suite + +```text +python -m pytest -q +``` + +Результат: + +```text +613 passed in 2.37s +``` + +Уменьшение общего количества тестов с `614` до `613` является ожидаемым результатом удаления legacy-файла: + +```text +tests/unit/integrations/exchange/test_market_cache.py +``` + +--- + +## 8. Контроль отсутствия legacy-зависимостей + +Выполнена финальная проверка: + +```text +grep -RIn --exclude-dir="__pycache__" --exclude="*.pyc" -E "\bMarketPriceCache\b|integrations\.exchange\.market_cache|market_cache" src tests +``` + +Результат: + +```text +пусто +``` + +Это подтверждает отсутствие оставшихся ссылок на `MarketPriceCache`, `src.integrations.exchange.market_cache` и `market_cache`. + +--- + +## 9. Итог Build 042 + +Build 042 завершён успешно. + +Подтверждено: + +- legacy `MarketPriceCache` полностью удалён; +- прямые зависимости от `market_cache.py` отсутствуют; +- shared `QuoteStore` стал непосредственным storage-механизмом котировок; +- WebSocket и REST пути используют каноническую модель `Quote`; +- runtime isolation сохранена; +- REST fallback сохранён; +- полный regression suite проходит без ошибок; +- архитектура готова к финальной проверке Quotes Feed. + +--- + +## 10. Следующий этап + +```text +Build 043 — Финальная архитектурная проверка Quotes Feed +``` + +Цель Build 043: + +- проверить целостность всей архитектурной цепочки Quotes Feed; +- подтвердить отсутствие legacy quote parsing; +- подтвердить отсутствие legacy quote cache facade; +- проверить REST и WebSocket пути получения котировок; +- проверить единый canonical `Quote`; +- проверить Feed → Handler → Adapter → Validation → Mapper → Storage → Consumer flow; +- проверить границы ответственности модулей; +- подтвердить готовность Quotes Feed к завершению миграции. diff --git a/docs/migrations/build_043.md b/docs/migrations/build_043.md new file mode 100644 index 0000000..aab185c --- /dev/null +++ b/docs/migrations/build_043.md @@ -0,0 +1,76 @@ +# Build 043 --- Финальная архитектурная проверка Quotes Feed + +## Контроль документа + + Поле Значение + ---------- --------------------------------------------- + Build 043 + Название Final Quotes Feed Architecture Verification + Статус Completed + Дата 2026-07-14 + +------------------------------------------------------------------------ + +## Цель + +Подтвердить завершение миграции подсистемы Quotes Feed на новую +архитектуру Market Data Acquisition и убедиться в отсутствии +legacy-компонентов. + +## Выполненные проверки + +- Полностью удалены `MarketPriceCache`, `TickerPrice`, + `MarketPriceSnapshot` и legacy WebSocket parsing. +- Все REST- и WebSocket-сообщения проходят цепочку: + `Schema → Parser → Value Validation → Mapper → Quote`. +- Единственная production-модель котировки --- `Quote`. +- Единственная точка получения котировок потребителями --- + `ExchangeService.get_quote()`. +- Единственная точка хранения котировок --- общий `QuoteStore`. +- REST endpoint `/api/v1/ticker/24hr` используется только внутри + Dzengi REST adapter. +- Raw-поля (`lastPrice`, `bidPrice`, `askPrice`, `closeTime`, `bids`, + `asks`) изолированы внутри Acquisition Layer. + +## Архитектурная схема + +``` text +Dzengi REST / WebSocket + ↓ +Schema Validation + ↓ +Parser + ↓ +Value Validation + ↓ +Mapper + ↓ +Quote + ↓ +QuoteStore + ↓ +ExchangeService.get_quote() + ↓ +Trading / Telegram / Diagnostics +``` + +## Результаты + +- Legacy зависимости отсутствуют. +- Нарушений архитектурных границ не обнаружено. +- Consumers работают только через публичный API. +- Полный регрессионный набор успешно пройден. + +## Проверка качества + +- Полный запуск тестов: + - **613 passed** +- Дополнительные grep-проверки подтвердили отсутствие legacy-кода. + +## Итог + +Build 043 завершён успешно. + +Миграция подсистемы **Quotes Feed** считается завершённой. Подсистема +соответствует утверждённой архитектуре Dzentra Market Data Acquisition и +готова к переходу к следующему этапу миграции.