Files
dzentra_bot/docs/migrations/build_033.md

22 KiB
Raw Blame History

Build 033 — Перенос MarketPriceCache на Quote Store

Engineering Build Record


Контроль документа

Свойство Значение
Документ Build 033 — Перенос MarketPriceCache на Quote Store
Тип документа Engineering Build Record
Статус Completed
Подсистема Market Data / Storage / Legacy Exchange Integration
Проект Dzentra
Язык Русский
Предыдущий этап Build 032 — Канонический Quote Store
Следующий этап Build 034 — Dzengi WebSocket quote parsing и адаптер

1. Назначение Build

Цель Build 033 — перевести legacy-компонент MarketPriceCache с собственного внутреннего хранилища котировок на канонический Quote Store, сохранив полную обратную совместимость с существующими потребителями.

До Build 033 MarketPriceCache самостоятельно владел runtime-состоянием котировок:

_prices: dict[tuple[str, str], MarketPriceSnapshot] = {}

Это создавало отдельный контур хранения рыночных цен параллельно с введённым в Build 032 каноническим Quote Store.

После Build 033 единственным владельцем состояния котировок, доступных через MarketPriceCache, становится канонический Quote Store.

Целевая переходная архитектура:

Legacy consumers
        │
        ▼
MarketPriceCache
compatibility facade
        │
        ▼
Canonical Quote
        │
        ▼
InMemoryQuoteStore

Сам MarketPriceCache сохраняется временно как compatibility facade до его окончательного удаления на Build 039.


2. Архитектурный контекст

Build 033 является частью последовательного перехода Quotes Feed на новую архитектуру Market Data Acquisition:

Build 026 — Аудит текущего контура Quotes Feed
Build 027 — Каноническая модель Quote и специализированные контракты
Build 028 — Dzengi REST quote models, parser и validation
Build 029 — Dzengi mapper и Quotes Handler
Build 030 — Quotes Feed и регистрация в Acquisition Service
Build 031 — Подключение нового REST Quotes Feed под legacy ExchangeService facade
Build 032 — Канонический Quote Store
Build 033 — Перенос MarketPriceCache на Quote Store
Build 034 — Dzengi WebSocket quote parsing и адаптер
Build 035 — Перевод market runtime на Quotes Feed
Build 036 — Перевод read-only и UI-потребителей
Build 037 — Перевод execution-потребителей
Build 038 — Удаление legacy TickerPrice и market snapshot dict layer
Build 039 — Удаление legacy quote parsing и MarketPriceCache
Build 040 — Финальная архитектурная проверка Quotes Feed

Build 033 не переводит непосредственных потребителей MarketPriceCache на новые API. Эта миграция выполняется последующими Build.

Задача текущего этапа — устранить независимое legacy-хранилище котировок без нарушения работы существующего бота.


3. Исходное состояние

До Build 033 класс:

src/integrations/exchange/market_cache.py

содержал собственное class-level хранилище:

class MarketPriceCache:
    _prices: dict[tuple[str, str], MarketPriceSnapshot] = {}

Ключ записи формировался из:

(runtime_key, symbol)

MarketPriceCache самостоятельно выполнял:

  • запись текущей цены;
  • хранение bid_price;
  • хранение ask_price;
  • хранение updated_at;
  • хранение фактического источника данных;
  • изоляцию по runtime_key;
  • вычисление возраста snapshot;
  • очистку записей по символу и runtime.

При этом после Build 032 уже существовал канонический:

InMemoryQuoteStore

работающий с моделью:

Quote

Таким образом, существовали два отдельных механизма хранения котировок:

MarketPriceCache
        │
        └── собственный dict[tuple[str, str], MarketPriceSnapshot]

Quote Store
        │
        └── каноническое хранилище Quote

Build 033 устранил это дублирование для контура MarketPriceCache.


4. Выполненные изменения

4.1. Изменённый исходный файл

Изменён:

src/integrations/exchange/market_cache.py

4.2. Добавленный тестовый файл

Добавлен:

tests/unit/integrations/exchange/test_market_cache.py

4.3. Файлы, не потребовавшие изменений

В рамках Build 033 не изменялись:

src/storage/quote_store.py
src/storage/exceptions.py
src/market_data/acquisition/models/quote.py
tests/unit/storage/test_quote_store.py
src/integrations/exchange/service.py
src/integrations/exchange/market_stream.py
src/integrations/exchange/market_data_runner.py

Это подтверждает сохранение существующих публичных контрактов и минимальный scope миграции.


5. Новая роль MarketPriceCache

После Build 033 MarketPriceCache больше не является самостоятельным владельцем runtime-состояния котировок.

Его новая роль:

Legacy compatibility facade

Он обеспечивает совместимость между существующими legacy-потребителями и канонической моделью хранения котировок.

Логика записи:

Legacy caller
    │
    ▼
MarketPriceCache.set_price(...)
    │
    ▼
Canonical Quote
    │
    ▼
QuoteStoreProtocol.set(...)

Логика чтения:

Legacy caller
    │
    ▼
MarketPriceCache.get_price(...)
    │
    ▼
QuoteStoreProtocol.get(...)
    │
    ▼
Canonical Quote
    │
    ▼
MarketPriceSnapshot
    │
    ▼
Legacy caller

Таким образом, MarketPriceSnapshot остаётся только временной compatibility model.


6. Устранение собственного хранилища MarketPriceCache

До Build 033:

_prices: dict[tuple[str, str], MarketPriceSnapshot] = {}

После Build 033 MarketPriceCache использует канонический контракт:

QuoteStoreProtocol

и реализацию:

InMemoryQuoteStore

Собственное независимое хранилище _prices устранено.

Это является главным архитектурным результатом Build 033.


7. Преобразование legacy-входа в канонический Quote

Публичный legacy-контракт записи сохранён:

MarketPriceCache.set_price(
    symbol=...,
    price=...,
    bid_price=...,
    ask_price=...,
    updated_at=...,
    source=...,
    runtime_key=...,
)

Внутри compatibility facade эти данные преобразуются в каноническую модель:

Quote

Основное соответствие полей:

Legacy MarketPriceCache Канонический Quote
symbol symbol
price last_price
bid_price bid_price
ask_price ask_price
updated_at каноническое timestamp-представление
source source
время получения received_at

На legacy-границе сохраняется использование float.

Внутри канонической модели используются точные числовые значения Decimal.

Таким образом, преобразование имеет вид:

Legacy float values
        │
        ▼
MarketPriceCache
        │
        ▼
Decimal values
        │
        ▼
Canonical Quote

8. Чтение через MarketPriceSnapshot

Существующие потребители ожидают от:

MarketPriceCache.get_price(...)

объект:

MarketPriceSnapshot

Поэтому Build 033 не удаляет эту модель.

При чтении выполняется обратное compatibility-преобразование:

QuoteStore
    │
    ▼
Quote
    │
    ▼
MarketPriceSnapshot

Сохраняются legacy-поля:

symbol
price
bid_price
ask_price
updated_at
source
runtime_key

Также сохранены методы:

age_seconds()
has_bid_ask()

Благодаря этому существующие потребители не потребовали изменений.


9. Сохранение семантики свежести

Legacy-потребители используют:

cached_price.age_seconds()

для определения возраста котировки.

Build 033 сохраняет этот публичный контракт.

Возраст snapshot определяется на основе канонической информации о времени получения котировки.

Таким образом, freshness-семантика больше не требует отдельного независимого хранилища состояния внутри MarketPriceCache.

Существующие вызовы:

cached_price.age_seconds()

продолжают работать без изменений.


10. Сохранение семантики bid/ask

Legacy-модель предоставляет:

has_bid_ask()

Этот контракт сохранён.

Он продолжает использоваться существующими execution-потребителями для проверки наличия корректных положительных значений:

bid_price
ask_price

Build 033 не требует изменения существующих потребителей этой проверки.


11. Изоляция runtime_key

Сохранена существующая изоляция котировок по:

runtime_key

Например:

auto
debug_auto
default

Котировки одного инструмента в разных runtime остаются независимыми.

Концептуальный ключ хранения:

source_name
    +
runtime_key
    +
symbol

Это позволяет одновременно хранить:

BTC/USD_LEVERAGE + auto
BTC/USD_LEVERAGE + debug_auto
BTC/USD_LEVERAGE + default

как независимые runtime-записи.


12. Нормализация runtime_key и symbol

Сохранено существующее поведение нормализации.

Символ нормализуется в uppercase:

btc/usd_leverage
        ↓
BTC/USD_LEVERAGE

runtime_key нормализуется в lowercase:

AUTO
        ↓
auto

Это сохраняет прежнюю семантику MarketPriceCache.


13. Разделение source_name и Quote.source

Build 033 сохраняет архитектурное различие между:

source_name

и:

Quote.source

source_name определяет namespace хранения.

Quote.source определяет фактическое происхождение котировки.

Например:

Storage namespace:
legacy-market-price-cache

Actual quote source:
ws_depth:auto

или:

Storage namespace:
legacy-market-price-cache

Actual quote source:
market-polling

Это предотвращает смешивание:

  • идентичности storage namespace;
  • provenance рыночных данных.

14. Сохранение семантики clear()

Полностью сохранены существующие варианты очистки.

Полная очистка facade namespace

MarketPriceCache.clear()

Очищает все записи, принадлежащие MarketPriceCache.

Очистка символа во всех runtime

MarketPriceCache.clear("BTC/USD_LEVERAGE")

Очищает указанный символ во всех runtime внутри namespace facade.

Очистка runtime по всем символам

MarketPriceCache.clear(runtime_key="auto")

Очищает все символы указанного runtime.

Точечная очистка

MarketPriceCache.clear(
    "BTC/USD_LEVERAGE",
    runtime_key="auto",
)

Очищает только конкретную запись.


15. Изоляция от других владельцев Quote Store

Критически важное требование Build 033:

MarketPriceCache.clear()

не должен удалять котировки, записанные другими владельцами или источниками в канонический Quote Store.

Поэтому операции facade ограничиваются собственным storage namespace.

Архитектурно:

Quote Store
├── legacy-market-price-cache
│   ├── auto
│   ├── debug_auto
│   └── default
│
└── other-source
    └── ...

Очистка:

MarketPriceCache.clear()

затрагивает только:

legacy-market-price-cache

и не изменяет данные других namespace.


16. Обратная совместимость

Build 033 не изменил публичные вызовы:

MarketPriceCache.set_price(...)
MarketPriceCache.get_price(...)
MarketPriceCache.clear(...)

Не изменены существующие production-потребители:

src/integrations/exchange/service.py
src/integrations/exchange/market_stream.py
src/integrations/exchange/market_data_runner.py

Также сохранены legacy-контракты:

MarketPriceSnapshot.age_seconds()
MarketPriceSnapshot.has_bid_ask()

Это позволило выполнить архитектурную миграцию без изменения поведения работающего бота.


17. Тестовое покрытие

Добавлен специализированный тестовый файл:

tests/unit/integrations/exchange/test_market_cache.py

Тестами проверяются:

  • соответствие MarketPriceCache каноническому QuoteStoreProtocol;
  • запись канонического Quote;
  • чтение через legacy MarketPriceSnapshot;
  • сохранение symbol;
  • сохранение price;
  • сохранение bid_price;
  • сохранение ask_price;
  • сохранение source;
  • сохранение runtime_key;
  • нормализация символа;
  • нормализация runtime_key;
  • изоляция разных runtime;
  • изоляция разных символов;
  • замена предыдущей котировки новой;
  • полная очистка facade namespace;
  • очистка по символу;
  • очистка по runtime;
  • точечная очистка;
  • вычисление возраста snapshot;
  • legacy-проверка has_bid_ask();
  • преобразование timestamp;
  • защита внешних записей другого source_name от очистки через MarketPriceCache.

18. Проверка компиляции

Выполнена команда:

python -m py_compile \
  src/integrations/exchange/market_cache.py \
  tests/unit/integrations/exchange/test_market_cache.py

Результат:

SUCCESS

Ошибок компиляции нет.


19. Специализированные тесты

Выполнена команда:

python -m pytest \
  tests/unit/integrations/exchange/test_market_cache.py \
  tests/unit/storage/test_quote_store.py \
  -q

Результат:

76 passed in 0.04s

Все специализированные тесты успешно пройдены.


20. Регрессионная проверка потребителей

Выполнена команда:

python -m pytest \
  tests/unit/integrations/exchange/test_service_quotes_facade.py \
  tests/unit/integrations/exchange/test_service_symbol_runtime_status.py \
  tests/unit/integrations/exchange/test_market_stream.py \
  tests/unit/integrations/exchange/test_market_data_runner.py \
  -q

Результат:

47 passed in 0.13s

Регрессионный контур существующих потребителей полностью сохранён.


21. Полная регрессионная проверка проекта

Выполнена команда:

python -m pytest -q

Результат:

578 passed in 0.28s

Все тесты проекта успешно пройдены.

Регрессий не обнаружено.


22. Архитектурный результат

До Build 033:

Legacy consumers
        │
        ▼
MarketPriceCache
        │
        ▼
Private _prices dict
        │
        ▼
MarketPriceSnapshot

Параллельно существовал:

Canonical Quote
        │
        ▼
Quote Store

После Build 033:

Legacy consumers
        │
        ▼
MarketPriceCache
compatibility facade
        │
        ▼
Canonical Quote
        │
        ▼
Quote Store

При чтении:

Quote Store
        │
        ▼
Canonical Quote
        │
        ▼
MarketPriceSnapshot
compatibility model
        │
        ▼
Legacy consumer

Таким образом, независимое legacy-хранилище котировок устранено.


23. Что намеренно не входит в Build 033

Build 033 не выполняет:

  • удаление MarketPriceCache;
  • удаление MarketPriceSnapshot;
  • перевод WebSocket parsing на новый Dzengi quote adapter;
  • перевод MarketDataRunner на Quotes Feed;
  • перевод UI-потребителей на канонический Quote;
  • перевод execution-потребителей на канонический Quote;
  • удаление TickerPrice;
  • удаление legacy market snapshot dict layer;
  • удаление legacy quote parsing.

Эти изменения выполняются последующими этапами утверждённого плана.


24. Условия завершения

Build 033 считается завершённым, поскольку выполнены все обязательные условия:

  • MarketPriceCache больше не владеет собственным _prices dict.
  • Канонический Quote Store используется для хранения котировок facade.
  • set_price() преобразует legacy-вход в канонический Quote.
  • get_price() возвращает совместимый MarketPriceSnapshot.
  • Сохранён контракт age_seconds().
  • Сохранён контракт has_bid_ask().
  • Сохранена изоляция по runtime_key.
  • Сохранена нормализация символа.
  • Сохранена семантика clear().
  • Очистка facade не затрагивает другие storage namespace.
  • Production-потребители не потребовали изменений.
  • Специализированные тесты успешно пройдены.
  • Регрессионные тесты потребителей успешно пройдены.
  • Полный набор тестов проекта успешно пройден.
  • Обратная совместимость работающего бота сохранена.

25. Статус Build

Build 033 — Completed.

Канонический Quote Store теперь является владельцем состояния котировок, доступных через legacy MarketPriceCache.

MarketPriceCache сохранён только как временный compatibility facade для существующих потребителей.

Следующий этап:

Build 034 — Dzengi WebSocket quote parsing и адаптер