20 KiB
Build 039 — Завершение миграции consumers рыночной цены на каноническую модель Quote
Статус: Завершён
Проект: Dzentra
Подсистема: Market Data Acquisition / Exchange Integration / Trading Consumers
Тип изменения: Миграция legacy price/snapshot consumers
Язык документа: Русский
1. Цель Build 039
Цель Build 039 — завершить миграцию существующих consumers рыночной цены и рыночных snapshot-моделей с legacy-контрактов на каноническую модель:
src.market_data.acquisition.models.quote.Quote
и соответствующий новый pipeline получения, хранения и использования котировок.
Build 039 должен устранить использование следующих legacy-сущностей и методов:
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 в проекте одновременно существовали:
- новая каноническая модель котировки:
Quote
-
новый Market Data Acquisition pipeline;
-
новый Quote Store;
-
новый cache-контракт на основе
Quote; -
legacy-модели:
TickerPrice
MarketPriceSnapshot
- legacy-методы получения цены и snapshot:
get_price()
get_market_snapshot()
get_fresh_market_snapshot()
refresh_price_cache()
refresh_market_snapshot_cache()
_get_real_price()
- consumers, которые продолжали зависеть от старых контрактов.
Такое состояние создавало несколько параллельных представлений одной и той же рыночной информации и препятствовало завершению миграции Market Data Acquisition.
3. Архитектурный принцип Build 039
После Build 039 каноническим представлением текущей рыночной котировки является:
Quote
из:
src.market_data.acquisition.models.quote
Архитектурный поток:
Dzengi API / Stream
↓
Market Data Acquisition
↓
Quote
↓
Quote Store / Market Price Cache
↓
ExchangeService
↓
Trading / Telegram / Diagnostics consumers
Legacy-модели TickerPrice и MarketPriceSnapshot больше не должны использоваться в src и tests.
4. Каноническая модель Quote
Каноническая модель расположена в:
app/src/market_data/acquisition/models/quote.py
Она является единым представлением текущей котировки инструмента и содержит данные, необходимые downstream-consumers:
symbol
last_price
bid_price
ask_price
exchange_timestamp
received_at
source
Использование нескольких параллельных price/snapshot-моделей для одной и той же задачи после Build 039 не допускается.
5. Изменённые области проекта
В рамках Build 039 были затронуты следующие основные области.
5.1. Exchange Integration
Основные файлы:
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
Основные файлы:
app/src/storage/quote_store.py
Quote Store используется как канонический storage-контракт для текущих котировок.
Store работает с моделью:
Quote
и не должен зависеть от:
TickerPrice
MarketPriceSnapshot
5.3. Telegram UI
Основные файлы:
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
Основные файлы:
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
Основной файл:
app/src/trading/debug/execution.py
Debug execution переведён на новый quote-контракт.
Legacy-вызов:
get_fresh_market_snapshot()
удалён.
5.6. Trading Diagnostics
Основной файл:
app/src/trading/diagnostics/snapshot.py
Diagnostics consumer переведён на актуальное представление рыночной котировки.
5.7. Trading Strategies
Основные файлы:
app/src/trading/strategies/scalp.py
app/src/trading/strategies/trend.py
Стратегии переведены с legacy snapshot API на каноническую модель Quote.
После миграции стратегии не должны использовать:
get_market_snapshot()
6. ExecutionPriceSnapshot
В рамках Build 039 необходимо различать две сущности:
Quote
и:
ExecutionPriceSnapshot
Quote является канонической моделью текущей рыночной котировки.
ExecutionPriceSnapshot является специализированным downstream-контрактом execution layer и может существовать отдельно, поскольку представляет данные в форме, непосредственно необходимой исполнению торговых операций.
Таким образом:
Quote
↓
ExecutionPriceSnapshot
не является дублированием legacy-модели MarketPriceSnapshot.
ExecutionPriceSnapshot сохраняется как специализированная execution-модель.
7. Runtime status и stale market data
Во время Build 039 особое внимание было уделено методу:
ExchangeService.get_symbol_runtime_status()
Для открытого рынка выполняется проверка актуальности рыночной котировки.
Актуальный поток:
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:
age_seconds > 60
При возрасте ровно:
60.0
рынок остаётся в статусе OPEN.
При возрасте:
61.0
возвращается:
ExchangeStatusCode.BREAK
reason = "market_data_stale"
raw_status = "STALE_MARKET_DATA"
8. Формат времени stale market data
Для stale market status используется exchange timestamp котировки.
Преобразование выполняется следующим образом:
exchange_timestamp_ms = (
int(quote.exchange_timestamp.timestamp() * 1000)
if quote.exchange_timestamp is not None
else None
)
После этого timestamp передаётся в:
self._format_exchange_time(exchange_timestamp_ms)
Это необходимо для сохранения существующего формата пользовательского сообщения.
Пример:
10.07.2026 12:00:00
Недопустимо передавать объект datetime непосредственно в функцию, ожидающую числовой timestamp в миллисекундах.
9. Сохранение legacy-поведения форматирования
Во время реализации Build 039 было выявлено критически важное правило миграции.
Две функции, решающие похожую задачу, не являются автоматически эквивалентными.
Например:
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}с"
и:
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 использовались и актуализировались следующие основные тесты:
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 некоторые тесты всё ещё содержали искусственные методы-заглушки вида:
def get_market_snapshot(self, *_: object, **__: object) -> object:
raise AssertionError("legacy get_market_snapshot() must not be used")
и:
def get_fresh_market_snapshot(self, *_: object, **__: object) -> object:
raise AssertionError("legacy get_fresh_market_snapshot() must not be used")
После завершения миграции эти заглушки были удалены из тестов, поскольку финальный архитектурный grep должен подтверждать полное отсутствие legacy API во всём дереве:
src
tests
Это касается файлов:
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-файлов:
python -m py_compile \
src/integrations/exchange/service.py \
tests/unit/integrations/exchange/test_service_symbol_runtime_status.py
Проверка завершилась успешно.
13. Целевая проверка runtime status
Выполнена команда:
python -m pytest \
tests/unit/integrations/exchange/test_service_symbol_runtime_status.py \
-q
Финальный результат:
34 passed in 1.44s
14. Полный regression suite
После завершения всех изменений выполнен полный набор тестов:
python -m pytest -q
Финальный результат:
606 passed in 1.63s
Все тесты успешно пройдены.
15. Финальный архитектурный grep
Выполнена команда:
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
Финальный результат:
<пусто>
Это подтверждает отсутствие legacy price/snapshot API в:
src
tests
16. Критерии завершения Build 039
Build 039 считается завершённым, поскольку выполнены все критерии:
TickerPriceудалён изsrcиtests;MarketPriceSnapshotудалён изsrcиtests;get_price()удалён изsrcиtests;get_market_snapshot()удалён изsrcиtests;get_fresh_market_snapshot()удалён изsrcиtests;refresh_price_cache()удалён изsrcиtests;refresh_market_snapshot_cache()удалён изsrcиtests;_get_real_price()удалён изsrcиtests;- trading consumers используют новый quote-контракт;
- Telegram consumers используют новый quote-контракт;
- diagnostics consumers используют новый quote-контракт;
- стратегии используют новый quote-контракт;
- runtime status использует каноническую модель
Quote; - stale market data detection сохранена;
- формат exchange timestamp восстановлен;
- legacy UI-поведение сохранено;
- целевые тесты проходят;
- полный regression suite проходит;
- финальный архитектурный grep пуст.
17. Итоговое состояние после Build 039
После завершения Build 039 архитектура текущих котировок приведена к следующему состоянию:
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.
Перед заменой необходимо сравнивать:
тип входных данных
тип возвращаемого значения
формат результата
точность
единицы измерения
граничные условия
fallback-поведение
exception semantics
временные зоны
формат времени
UI-представление
Любое намеренное изменение поведения должно быть явно зафиксировано в документации соответствующего Build и покрыто тестами.
19. Статус Build
Build 039: COMPLETED
Regression suite: 606 passed
Legacy grep: empty
Canonical quote model: Quote
Build 039 полностью завершён.