22 KiB
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больше не владеет собственным_pricesdict.- Канонический
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 и адаптер