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