22 KiB
Build 032 — Канонический Quote Store
Статус: Завершён
Результат: Успешно
Полная регрессия: 562 passed
1. Назначение Build
Цель Build 032 — создать каноническое оперативное хранилище текущих котировок Quote в Storage layer и подготовить архитектурную основу для последующего переноса legacy-механизма MarketPriceCache.
До выполнения Build 032 в проекте уже существовал канонический контур получения текущей котировки:
Dzengi REST API
↓
DzengiQuoteDocumentSource
↓
Dzengi quote parser
↓
Dzengi quote value validation
↓
Dzengi quote mapper
↓
QuotesHandler
↓
QuotesFeed
↓
QuoteAcquisitionService
↓
Quote
Однако канонического хранилища объектов Quote ещё не существовало.
Текущие runtime-котировки продолжал хранить legacy-компонент:
MarketPriceCache
Build 032 вводит новый независимый Storage-компонент:
canonical Quote
↓
InMemoryQuoteStore
При этом существующий production pipeline не переключается на новое хранилище в рамках данного Build.
2. Архитектурная граница Build
Build 032 ограничен созданием канонического Quote Store.
В рамках Build:
- добавлен контракт
QuoteStoreProtocol; - добавлена in-memory реализация
InMemoryQuoteStore; - добавлена специализированная ошибка
QuoteStoreError; - реализовано хранение канонических объектов
Quote; - реализована изоляция по источнику данных;
- реализована изоляция по runtime-контексту;
- реализована изоляция по торговому инструменту;
- реализована полная, выборочная и комбинированная очистка;
- добавлен полный набор unit-тестов.
В рамках Build не выполнялись:
- изменение
ExchangeService; - изменение
MarketPriceCache; - подключение
QuoteStoreкExchangeService; - перенос данных из
MarketPriceCache; - изменение
QuotesFeed; - изменение
QuoteAcquisitionService; - изменение WebSocket-контура;
- изменение market runtime;
- изменение UI-потребителей;
- изменение execution-потребителей;
- удаление legacy-компонентов.
Эти изменения относятся к последующим Build утверждённого плана миграции Quotes Feed.
3. Изменённые файлы
Изменён production-файл
src/storage/exceptions.py
Добавлена специализированная ошибка:
QuoteStoreError
Добавлен production-файл
src/storage/quote_store.py
Содержит:
QuoteStoreProtocol
InMemoryQuoteStore
Добавлен тестовый файл
tests/unit/storage/test_quote_store.py
4. Целевая архитектура
После Build 032 Storage layer содержит два специализированных канонических хранилища:
src/storage/
├── exceptions.py
├── instrument_store.py
└── quote_store.py
Архитектурно:
Instrument
↓
InMemoryInstrumentStore
и:
Quote
↓
InMemoryQuoteStore
InstrumentStore хранит канонический справочник инструментов.
QuoteStore хранит канонические текущие котировки.
5. Контракт Quote Store
Канонический контракт представлен протоколом:
QuoteStoreProtocol
Он определяет три основные операции:
get()
set()
clear()
Концептуальный контракт:
@runtime_checkable
class QuoteStoreProtocol(Protocol):
def get(
self,
source_name: str,
symbol: str,
*,
runtime_key: str = "default",
) -> Quote | None:
...
def set(
self,
source_name: str,
quote: Quote,
*,
runtime_key: str = "default",
) -> None:
...
def clear(
self,
source_name: str | None = None,
symbol: str | None = None,
*,
runtime_key: str | None = None,
) -> None:
...
Контракт не зависит от:
- Dzengi;
- REST;
- WebSocket;
ExchangeService;MarketPriceCache;- UI;
- Execution layer.
6. Модель хранения
Quote Store использует составной ключ:
source_name + runtime_key + symbol
Внутреннее представление:
tuple[str, str, str]
Примеры независимых записей:
("dzengi", "auto", "BTC/USD_LEVERAGE")
("dzengi", "debug_auto", "BTC/USD_LEVERAGE")
("secondary", "auto", "BTC/USD_LEVERAGE")
Все эти записи независимы друг от друга.
Такая модель позволяет одновременно хранить:
- котировки от разных поставщиков;
- котировки для разных runtime-контекстов;
- котировки разных инструментов.
7. Семантика source_name
source_name представляет namespace источника данных в Storage layer.
Применяются следующие правила:
внешние пробелы удаляются;
регистр сохраняется;
пустое значение запрещено.
Пример:
" dzengi " → "dzengi"
При этом:
"DZENGI" != "dzengi"
Такое поведение соответствует существующей семантике:
InstrumentRegistry
QuoteRegistry
InstrumentStore
Quote Store не требует равенства:
source_name == quote.source
Это принципиально позволяет использовать алиасы источников:
dzengi
dzengi-demo
dzengi-prod
dzengi-primary
при сохранении канонического происхождения самой модели в:
quote.source
8. Семантика runtime_key
runtime_key разделяет независимые runtime-контексты.
Примеры:
default
auto
debug_auto
Правила нормализации:
внешние пробелы удаляются;
значение приводится к lowercase;
пустое значение запрещено.
Пример:
" AUTO " → "auto"
Таким образом:
AUTO
Auto
auto
адресуют один runtime namespace:
auto
Эта семантика соответствует существующему поведению legacy MarketPriceCache.
9. Семантика symbol
Символ используется как третья часть ключа Quote Store.
Правила нормализации:
внешние пробелы удаляются;
значение приводится к uppercase;
пустое значение запрещено.
Пример:
" btc/usd_leverage "
преобразуется в ключ:
BTC/USD_LEVERAGE
Нормализация применяется только к ключу хранения.
Сам объект Quote не изменяется.
10. Семантика set()
Метод:
set()
сохраняет канонический объект Quote.
Основные гарантии:
- принимается объект
Quote; - используется
quote.symbol; - объект сохраняется без копирования;
Decimalне преобразуется вfloat;datetimeне преобразуется в строку;- timestamps не изменяются;
- существующая запись с тем же ключом заменяется;
- содержимое
Quoteне нормализуется повторно.
Пример:
store.set(
"dzengi",
quote,
runtime_key="auto",
)
Если для ключа:
("dzengi", "auto", "BTC/USD_LEVERAGE")
уже существует запись, она заменяется новой.
Quote Store сохраняет identity объекта:
store.get(
"dzengi",
"BTC/USD_LEVERAGE",
runtime_key="auto",
) is quote
11. Семантика get()
Метод:
get()
возвращает:
Quote | None
Если запись существует:
возвращается исходный сохранённый объект Quote
Если запись отсутствует:
None
Store не:
- создаёт копию;
- выполняет сетевой запрос;
- обращается к Acquisition layer;
- вычисляет freshness;
- выполняет fallback.
12. Семантика clear()
Метод:
clear()
поддерживает полную, выборочную и комбинированную очистку.
12.1. Полная очистка
store.clear()
Удаляет все сохранённые котировки.
12.2. Очистка по источнику
store.clear(
source_name="dzengi",
)
Удаляет все котировки указанного источника независимо от:
- символа;
- runtime-контекста.
12.3. Очистка по символу
store.clear(
symbol="BTC/USD_LEVERAGE",
)
Удаляет указанный символ у всех:
- источников;
- runtime-контекстов.
12.4. Очистка по runtime
store.clear(
runtime_key="auto",
)
Удаляет все котировки указанного runtime-контекста.
12.5. Точная очистка
store.clear(
source_name="dzengi",
symbol="BTC/USD_LEVERAGE",
runtime_key="auto",
)
Удаляет только одну конкретную запись.
12.6. Комбинированная очистка
Поддерживаются комбинации фильтров.
Например:
store.clear(
source_name="dzengi",
runtime_key="auto",
)
Удаляет все котировки источника dzengi только из runtime:
auto
Остальные записи сохраняются.
12.7. Идемпотентность
Очистка отсутствующей записи не является ошибкой.
Например:
store.clear(
source_name="unknown",
symbol="UNKNOWN",
runtime_key="unknown",
)
завершается без исключения.
13. Специализированная ошибка
В Storage layer добавлена ошибка:
QuoteStoreError
Иерархия:
Exception
↓
StorageError
↓
QuoteStoreError
Она используется для нарушений контракта Quote Store.
Примеры:
- пустой
source_name; - пустой
runtime_key; - пустой
symbol; - передача объекта неправильного типа.
Это позволяет отличать ошибки хранения котировок от:
- ошибок Acquisition;
- ошибок адаптера биржи;
- транспортных ошибок;
- ошибок Exchange facade;
- ошибок Execution layer.
14. Сохранение канонической модели
Quote Store хранит непосредственно:
Quote
Хранилище не создаёт промежуточные представления типа:
MarketPriceSnapshot
dict[str, object]
TickerPrice
Архитектурно:
Quote
↓
Quote Store
а не:
Quote
↓
legacy dict
↓
MarketPriceSnapshot
↓
Store
Это принципиально для дальнейшего устранения legacy quote representations.
15. Сохранение точности чисел
Числовые поля канонического Quote используют:
Decimal
Quote Store сохраняет их без преобразования.
Не выполняется:
Decimal → float
Таким образом сохраняются:
- точность котировок;
- исходная числовая семантика;
- единый канонический тип данных.
16. Сохранение временной семантики
Quote Store сохраняет временные поля модели без преобразования.
Не выполняется:
datetime → str
Store не:
- форматирует timestamps;
- переводит время в локальную строку;
- вычисляет возраст записи;
- определяет freshness.
Временная семантика остаётся частью канонической модели Quote.
17. Изоляция экземпляров
Разные экземпляры:
InMemoryQuoteStore()
имеют независимое состояние.
Пример:
first_store
↓
собственные записи
second_store
↓
собственные записи
Запись в одном экземпляре не появляется в другом.
Это отличает Quote Store от legacy MarketPriceCache, использующего class-level storage.
18. Ответственность Quote Store
Quote Store отвечает только за:
хранение уже созданных канонических Quote
Quote Store не отвечает за:
- получение данных;
- REST-запросы;
- WebSocket-соединения;
- parsing;
- schema validation;
- value validation;
- sequence validation;
- mapping;
- retry;
- reconnect;
- freshness;
- вычисление возраста;
- выбор REST или WebSocket;
- market status;
- execution pricing.
Эти обязанности принадлежат другим компонентам архитектуры.
19. Тестовое покрытие
Добавлен файл:
tests/unit/storage/test_quote_store.py
Проверены:
- соответствие
InMemoryQuoteStoreпротоколуQuoteStoreProtocol; - получение отсутствующей записи;
- сохранение
Quote; - получение сохранённого
Quote; - сохранение identity объекта;
- замена существующей записи;
- изоляция разных источников;
- изоляция разных runtime-контекстов;
- изоляция разных символов;
- нормализация внешних пробелов
source_name; - сохранение регистра
source_name; - lowercase-нормализация
runtime_key; - uppercase-нормализация символа;
- запрет пустого
source_name; - запрет пустого
runtime_key; - запрет пустого символа;
- запрет объекта неправильного типа;
- полная очистка;
- очистка по источнику;
- очистка по runtime;
- очистка по символу;
- точечная очистка;
- комбинированная очистка;
- идемпотентность очистки;
- независимость экземпляров Store;
- сохранение
Decimal; - сохранение
datetime; - наследование
QuoteStoreErrorотStorageError.
20. Результаты проверки
Проверка синтаксиса:
python -m py_compile \
src/storage/exceptions.py \
src/storage/quote_store.py \
tests/unit/storage/test_quote_store.py
Результат:
успешно
Специализированные тесты:
python -m pytest \
tests/unit/storage/test_quote_store.py \
tests/unit/storage/test_instrument_store.py \
-q
Результат:
92 passed in 0.05s
Полная регрессия:
python -m pytest -q
Результат:
562 passed in 0.28s
21. Изменение количества тестов
До Build 032:
502 passed
После Build 032:
562 passed
Добавлено:
60 тестов
Полная регрессия осталась зелёной.
22. Состояние архитектуры после Build 032
После завершения Build 032 существуют два параллельных контура.
Канонический REST Quotes Feed:
Dzengi REST API
↓
DzengiQuoteDocumentSource
↓
Dzengi quote parser
↓
Dzengi quote value validation
↓
Dzengi quote mapper
↓
QuotesHandler
↓
QuotesFeed
↓
QuoteAcquisitionService
↓
Quote
Каноническое хранилище:
Quote
↓
InMemoryQuoteStore
При этом legacy runtime-контур пока продолжает использовать:
MarketPriceCache
То есть на момент завершения Build 032:
QuoteAcquisitionService
↓
Quote
и
InMemoryQuoteStore
существуют как канонические компоненты, но production pipeline ещё не переключён на новый Store.
23. Что не изменилось
Build 032 не изменил поведение работающего бота.
Не изменялись:
src/integrations/exchange/service.py
src/integrations/exchange/market_cache.py
src/integrations/exchange/market_stream.py
src/integrations/exchange/market_data_runner.py
src/market_data/acquisition/service.py
Также не изменялись:
- Telegram UI;
- AutoTrade runtime;
- стратегии;
- diagnostics;
- debug runtime;
- execution pricing.
Это соответствует принятому принципу миграции:
сначала создать новый канонический компонент
↓
проверить его изолированно
↓
подключить под существующие facade-контракты
↓
перевести потребителей
↓
удалить legacy только после полного переключения
24. Итог Build 032
Build 032 завершён успешно.
Создан канонический Storage-компонент для текущих котировок:
QuoteStoreProtocol
↓
InMemoryQuoteStore
↓
Quote
Достигнуты следующие архитектурные свойства:
каноническая модель хранения
изоляция источников
изоляция runtime-контекстов
изоляция символов
сохранение Decimal
сохранение datetime
отсутствие зависимости от биржи
отсутствие зависимости от Acquisition
отсутствие зависимости от ExchangeService
отсутствие зависимости от legacy MarketPriceCache
полная тестовая изоляция
Build завершён с полной зелёной регрессией:
562 passed
25. Следующий этап
Следующий этап утверждённого плана:
Build 033 — Перенос MarketPriceCache на Quote Store
Его задача — начать интеграцию канонического QuoteStore в существующий runtime-контур котировок без нарушения обратной совместимости работающего бота.
Целевая переходная схема:
legacy consumers
↓
ExchangeService facade
↓
MarketPriceCache compatibility layer
↓
Quote Store
↓
canonical Quote
После Build 033 MarketPriceCache должен перестать быть самостоятельным владельцем quote state и стать временным compatibility layer над каноническим QuoteStore.