build 043: finalize Quotes Feed architecture verification

This commit is contained in:
2026-07-14 22:16:39 +03:00
parent 225f07bc4b
commit a6325cf4cf
5 changed files with 2448 additions and 0 deletions

View File

@@ -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 полностью завершён.

View File

@@ -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 полностью завершён.

View File

@@ -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 завершён.

View File

@@ -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 к завершению миграции.

View File

@@ -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 и
готова к переходу к следующему этапу миграции.