feat: add market data architecture and complete migration through build 039
This commit is contained in:
863
docs/migrations/build_020.md
Normal file
863
docs/migrations/build_020.md
Normal file
@@ -0,0 +1,863 @@
|
||||
# Build 020 — Перенос кэша инструментов в Storage Layer
|
||||
|
||||
**Engineering Migration Record**
|
||||
|
||||
---
|
||||
|
||||
## Контроль документа
|
||||
|
||||
| Свойство | Значение |
|
||||
|---|---|
|
||||
| Документ | Build 020 — Перенос кэша инструментов в Storage Layer |
|
||||
| Тип документа | Engineering Migration Record |
|
||||
| Статус | **Complete** |
|
||||
| Проект | Dzentra |
|
||||
| Подсистема | Instrument Reference Data |
|
||||
| Build | 020 |
|
||||
| Язык | Русский |
|
||||
| Целевой файл | `docs/migrations/instrument_reference_data/build_020.md` |
|
||||
|
||||
---
|
||||
|
||||
## 1. Цель Build 020
|
||||
|
||||
Цель Build 020 — удалить каноническое хранение справочных данных инструментов из legacy-кэша `ExchangeService._exchange_symbols_cache` и перенести его в специализированный Storage Layer.
|
||||
|
||||
До выполнения Build 020 `ExchangeService` одновременно отвечал за:
|
||||
|
||||
- получение справочных данных инструментов;
|
||||
- запуск acquisition pipeline;
|
||||
- преобразование канонических моделей `Instrument` в legacy-модели `ExchangeSymbol`;
|
||||
- хранение результата в собственном class-level cache.
|
||||
|
||||
Это создавало архитектурную зависимость канонических справочных данных от legacy exchange layer.
|
||||
|
||||
После Build 020 канонические данные инструментов хранятся в специализированном:
|
||||
|
||||
```text
|
||||
InMemoryInstrumentStore
|
||||
```
|
||||
|
||||
в виде:
|
||||
|
||||
```text
|
||||
tuple[Instrument, ...]
|
||||
```
|
||||
|
||||
Legacy-модели `ExchangeSymbol` больше не являются каноническим представлением справочных данных.
|
||||
|
||||
---
|
||||
|
||||
## 2. Архитектурное состояние до Build 020
|
||||
|
||||
До миграции `ExchangeService` содержал:
|
||||
|
||||
```python
|
||||
_exchange_symbols_cache: list[ExchangeSymbol] | None = None
|
||||
```
|
||||
|
||||
Метод:
|
||||
|
||||
```python
|
||||
get_exchange_symbols()
|
||||
```
|
||||
|
||||
работал по следующей схеме:
|
||||
|
||||
```text
|
||||
get_exchange_symbols()
|
||||
↓
|
||||
_exchange_symbols_cache
|
||||
↓ cache miss
|
||||
_load_exchange_symbols_via_acquisition()
|
||||
↓
|
||||
Instrument Acquisition Pipeline
|
||||
↓
|
||||
tuple[Instrument, ...]
|
||||
↓
|
||||
map_instruments_to_exchange_symbols()
|
||||
↓
|
||||
list[ExchangeSymbol]
|
||||
↓
|
||||
_exchange_symbols_cache
|
||||
```
|
||||
|
||||
Таким образом, результат новой Instrument Reference Data pipeline сразу преобразовывался в legacy-модель, и именно legacy-представление сохранялось как основной кэш.
|
||||
|
||||
---
|
||||
|
||||
## 3. Архитектурная проблема
|
||||
|
||||
Старый подход имел несколько принципиальных недостатков.
|
||||
|
||||
### 3.1. Legacy-модель использовалась как каноническое хранилище
|
||||
|
||||
Каноническая модель:
|
||||
|
||||
```python
|
||||
Instrument
|
||||
```
|
||||
|
||||
содержит полные справочные данные инструмента.
|
||||
|
||||
Legacy-модель:
|
||||
|
||||
```python
|
||||
ExchangeSymbol
|
||||
```
|
||||
|
||||
является сокращённой compatibility-проекцией и содержит только часть этих данных.
|
||||
|
||||
Хранение только `ExchangeSymbol` означало потерю полноценного канонического представления после завершения acquisition pipeline.
|
||||
|
||||
### 3.2. Exchange Layer владел состоянием справочных данных
|
||||
|
||||
Поле:
|
||||
|
||||
```python
|
||||
ExchangeService._exchange_symbols_cache
|
||||
```
|
||||
|
||||
делало `ExchangeService` владельцем справочных данных инструментов.
|
||||
|
||||
Это противоречило целевой архитектуре:
|
||||
|
||||
```text
|
||||
Acquisition Layer
|
||||
↓
|
||||
Canonical Instrument Models
|
||||
↓
|
||||
Storage Layer
|
||||
↓
|
||||
Consumers / Compatibility Projections
|
||||
```
|
||||
|
||||
### 3.3. Канонические данные и legacy-проекция были объединены
|
||||
|
||||
Результат acquisition pipeline немедленно преобразовывался:
|
||||
|
||||
```text
|
||||
Instrument
|
||||
↓
|
||||
ExchangeSymbol
|
||||
```
|
||||
|
||||
и сохранялся только после преобразования.
|
||||
|
||||
Это не позволяло независимо использовать полный `Instrument` в будущих подсистемах Dzentra.
|
||||
|
||||
---
|
||||
|
||||
## 4. Реализованное архитектурное решение
|
||||
|
||||
В Build 020 введено разделение между:
|
||||
|
||||
1. каноническим хранилищем инструментов;
|
||||
2. legacy compatibility projection cache.
|
||||
|
||||
Теперь `ExchangeService` использует:
|
||||
|
||||
```python
|
||||
_instrument_store: InstrumentStoreProtocol = InMemoryInstrumentStore()
|
||||
```
|
||||
|
||||
для хранения канонических моделей:
|
||||
|
||||
```python
|
||||
tuple[Instrument, ...]
|
||||
```
|
||||
|
||||
и отдельный:
|
||||
|
||||
```python
|
||||
_exchange_symbols_projection_cache: list[ExchangeSymbol] | None = None
|
||||
```
|
||||
|
||||
для временного кэширования legacy-проекции.
|
||||
|
||||
Итоговая схема:
|
||||
|
||||
```text
|
||||
DzengiInstrumentDocumentSource
|
||||
↓
|
||||
InstrumentFeed
|
||||
↓
|
||||
DzengiInstrumentDocumentHandler
|
||||
↓
|
||||
InstrumentAcquisitionService
|
||||
↓
|
||||
tuple[Instrument, ...]
|
||||
↓
|
||||
InMemoryInstrumentStore
|
||||
↓
|
||||
Canonical Instrument Reference Data
|
||||
↓
|
||||
map_instruments_to_exchange_symbols()
|
||||
↓
|
||||
list[ExchangeSymbol]
|
||||
↓
|
||||
Legacy Compatibility Projection Cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Каноническое хранилище
|
||||
|
||||
Канонические справочные данные теперь находятся в:
|
||||
|
||||
```python
|
||||
ExchangeService._instrument_store
|
||||
```
|
||||
|
||||
Тип зависимости:
|
||||
|
||||
```python
|
||||
InstrumentStoreProtocol
|
||||
```
|
||||
|
||||
Текущая реализация:
|
||||
|
||||
```python
|
||||
InMemoryInstrumentStore
|
||||
```
|
||||
|
||||
Store хранит:
|
||||
|
||||
```python
|
||||
tuple[Instrument, ...]
|
||||
```
|
||||
|
||||
Ключ источника:
|
||||
|
||||
```text
|
||||
dzengi
|
||||
```
|
||||
|
||||
Таким образом, канонические справочные данные больше не зависят от legacy-модели `ExchangeSymbol`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Новый production flow
|
||||
|
||||
Метод:
|
||||
|
||||
```python
|
||||
get_exchange_symbols()
|
||||
```
|
||||
|
||||
сохраняет прежний внешний контракт:
|
||||
|
||||
```python
|
||||
list[ExchangeSymbol]
|
||||
```
|
||||
|
||||
Это необходимо для сохранения работоспособности существующего бота во время поэтапной миграции.
|
||||
|
||||
Внутренний flow теперь выглядит следующим образом:
|
||||
|
||||
```text
|
||||
get_exchange_symbols()
|
||||
↓
|
||||
Проверка exchange_enabled
|
||||
↓
|
||||
Проверка _exchange_symbols_projection_cache
|
||||
↓ cache miss
|
||||
Чтение InstrumentStore
|
||||
↓ store miss
|
||||
_load_instruments_via_acquisition()
|
||||
↓
|
||||
tuple[Instrument, ...]
|
||||
↓
|
||||
Сохранение в InstrumentStore
|
||||
↓
|
||||
map_instruments_to_exchange_symbols()
|
||||
↓
|
||||
list[ExchangeSymbol]
|
||||
↓
|
||||
Сохранение в _exchange_symbols_projection_cache
|
||||
↓
|
||||
Возврат legacy-результата
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Разделение канонического кэша и compatibility projection cache
|
||||
|
||||
После Build 020 существуют два разных уровня состояния.
|
||||
|
||||
### 7.1. Каноническое состояние
|
||||
|
||||
```python
|
||||
_instrument_store
|
||||
```
|
||||
|
||||
Хранит:
|
||||
|
||||
```python
|
||||
tuple[Instrument, ...]
|
||||
```
|
||||
|
||||
Назначение:
|
||||
|
||||
- хранение полной справочной модели инструмента;
|
||||
- повторное использование канонических данных;
|
||||
- основа для будущих market intelligence consumers;
|
||||
- независимость от legacy exchange models.
|
||||
|
||||
### 7.2. Compatibility projection cache
|
||||
|
||||
```python
|
||||
_exchange_symbols_projection_cache
|
||||
```
|
||||
|
||||
Хранит:
|
||||
|
||||
```python
|
||||
list[ExchangeSymbol] | None
|
||||
```
|
||||
|
||||
Назначение:
|
||||
|
||||
- сохранить старый контракт `get_exchange_symbols()`;
|
||||
- не выполнять повторный compatibility mapping при каждом вызове;
|
||||
- обеспечить безопасную постепенную миграцию legacy-кода.
|
||||
|
||||
`_exchange_symbols_projection_cache` не является источником истины.
|
||||
|
||||
Источником истины является:
|
||||
|
||||
```text
|
||||
InstrumentStore
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Изменение acquisition loader
|
||||
|
||||
Старый метод:
|
||||
|
||||
```python
|
||||
_load_exchange_symbols_via_acquisition()
|
||||
```
|
||||
|
||||
удалён.
|
||||
|
||||
Он одновременно:
|
||||
|
||||
- запускал acquisition pipeline;
|
||||
- получал `Instrument`;
|
||||
- выполнял compatibility mapping;
|
||||
- возвращал `ExchangeSymbol`.
|
||||
|
||||
Вместо него используется:
|
||||
|
||||
```python
|
||||
_load_instruments_via_acquisition()
|
||||
```
|
||||
|
||||
Новый метод возвращает:
|
||||
|
||||
```python
|
||||
tuple[Instrument, ...]
|
||||
```
|
||||
|
||||
и не выполняет:
|
||||
|
||||
```python
|
||||
map_instruments_to_exchange_symbols()
|
||||
```
|
||||
|
||||
Это обеспечивает чистую архитектурную границу:
|
||||
|
||||
```text
|
||||
Acquisition Pipeline
|
||||
↓
|
||||
Canonical Instrument Models
|
||||
```
|
||||
|
||||
Compatibility mapping выполняется отдельно только там, где действительно требуется legacy-контракт.
|
||||
|
||||
---
|
||||
|
||||
## 9. Сохранение обратной совместимости
|
||||
|
||||
Build 020 не меняет публичный контракт:
|
||||
|
||||
```python
|
||||
ExchangeService.get_exchange_symbols()
|
||||
```
|
||||
|
||||
Он по-прежнему возвращает:
|
||||
|
||||
```python
|
||||
list[ExchangeSymbol]
|
||||
```
|
||||
|
||||
Благодаря этому продолжают работать существующие consumers, включая:
|
||||
|
||||
```text
|
||||
validate_symbol()
|
||||
currency_ui.py
|
||||
legacy exchange UI
|
||||
существующие unit tests
|
||||
```
|
||||
|
||||
Миграция выполнена без обязательного одновременного переписывания всех legacy consumers.
|
||||
|
||||
---
|
||||
|
||||
## 10. Поведение при отключённой бирже
|
||||
|
||||
При:
|
||||
|
||||
```python
|
||||
exchange_enabled = False
|
||||
```
|
||||
|
||||
метод:
|
||||
|
||||
```python
|
||||
get_exchange_symbols()
|
||||
```
|
||||
|
||||
возвращает:
|
||||
|
||||
```python
|
||||
[]
|
||||
```
|
||||
|
||||
При этом он не должен:
|
||||
|
||||
- читать `InstrumentStore`;
|
||||
- использовать существующий projection cache;
|
||||
- запускать acquisition pipeline;
|
||||
- обращаться к реальной бирже.
|
||||
|
||||
Это сохраняет прежнее поведение mock/disabled режима.
|
||||
|
||||
---
|
||||
|
||||
## 11. Поведение при cache hit
|
||||
|
||||
Если существует:
|
||||
|
||||
```python
|
||||
_exchange_symbols_projection_cache
|
||||
```
|
||||
|
||||
метод возвращает существующий объект legacy-проекции без:
|
||||
|
||||
- чтения acquisition source;
|
||||
- повторной обработки документа;
|
||||
- повторного compatibility mapping.
|
||||
|
||||
Если projection cache отсутствует, но канонические данные уже существуют в:
|
||||
|
||||
```python
|
||||
InstrumentStore
|
||||
```
|
||||
|
||||
то acquisition pipeline не запускается повторно.
|
||||
|
||||
Вместо этого выполняется:
|
||||
|
||||
```text
|
||||
InstrumentStore
|
||||
↓
|
||||
tuple[Instrument, ...]
|
||||
↓
|
||||
Compatibility Mapper
|
||||
↓
|
||||
list[ExchangeSymbol]
|
||||
```
|
||||
|
||||
Пустой канонический набор:
|
||||
|
||||
```python
|
||||
()
|
||||
```
|
||||
|
||||
также считается валидным cache hit и не должен ошибочно интерпретироваться как отсутствие данных.
|
||||
|
||||
---
|
||||
|
||||
## 12. Поведение при cache miss
|
||||
|
||||
Если одновременно отсутствуют:
|
||||
|
||||
```text
|
||||
_exchange_symbols_projection_cache
|
||||
InstrumentStore entry for "dzengi"
|
||||
```
|
||||
|
||||
выполняется полный production flow:
|
||||
|
||||
```text
|
||||
Instrument Source
|
||||
↓
|
||||
Instrument Handler
|
||||
↓
|
||||
Instrument Acquisition Service
|
||||
↓
|
||||
tuple[Instrument, ...]
|
||||
↓
|
||||
InstrumentStore.set(...)
|
||||
↓
|
||||
Compatibility Mapper
|
||||
↓
|
||||
list[ExchangeSymbol]
|
||||
```
|
||||
|
||||
После успешной загрузки:
|
||||
|
||||
- канонический `tuple[Instrument, ...]` сохраняется в `InstrumentStore`;
|
||||
- legacy-проекция создаётся отдельно;
|
||||
- legacy-проекция сохраняется в `_exchange_symbols_projection_cache`.
|
||||
|
||||
---
|
||||
|
||||
## 13. Поведение при ошибках
|
||||
|
||||
Если acquisition pipeline завершается ошибкой:
|
||||
|
||||
- ошибка логируется как exchange request error;
|
||||
- endpoint сохраняется как:
|
||||
|
||||
```text
|
||||
exchangeInfo
|
||||
```
|
||||
|
||||
- исходная ошибка становится причиной `ExchangeError`;
|
||||
- канонический store не должен получать частичные или некорректные данные;
|
||||
- projection cache не должен заполняться.
|
||||
|
||||
Таким образом, ошибка acquisition не создаёт ложное успешное состояние.
|
||||
|
||||
Если ошибка возникает на этапе compatibility mapping:
|
||||
|
||||
- канонические данные уже могут находиться в `InstrumentStore`;
|
||||
- projection cache не должен заполняться некорректным результатом;
|
||||
- следующий вызов может повторно построить legacy-проекцию из сохранённых канонических данных без повторного запуска acquisition pipeline.
|
||||
|
||||
---
|
||||
|
||||
## 14. Инварианты Build 020
|
||||
|
||||
После завершения Build 020 действуют следующие обязательные инварианты.
|
||||
|
||||
1. `ExchangeService._exchange_symbols_cache` отсутствует в production-коде.
|
||||
|
||||
2. Канонические справочные данные хранятся как:
|
||||
|
||||
```python
|
||||
tuple[Instrument, ...]
|
||||
```
|
||||
|
||||
3. Владельцем канонического состояния является:
|
||||
|
||||
```text
|
||||
InstrumentStore
|
||||
```
|
||||
|
||||
4. Текущей реализацией store является:
|
||||
|
||||
```python
|
||||
InMemoryInstrumentStore
|
||||
```
|
||||
|
||||
5. `ExchangeService` зависит от абстракции:
|
||||
|
||||
```python
|
||||
InstrumentStoreProtocol
|
||||
```
|
||||
|
||||
6. Старый loader:
|
||||
|
||||
```python
|
||||
_load_exchange_symbols_via_acquisition()
|
||||
```
|
||||
|
||||
отсутствует.
|
||||
|
||||
7. Новый loader:
|
||||
|
||||
```python
|
||||
_load_instruments_via_acquisition()
|
||||
```
|
||||
|
||||
возвращает только:
|
||||
|
||||
```python
|
||||
tuple[Instrument, ...]
|
||||
```
|
||||
|
||||
8. Новый loader не вызывает:
|
||||
|
||||
```python
|
||||
map_instruments_to_exchange_symbols()
|
||||
```
|
||||
|
||||
9. Compatibility mapping выполняется после получения канонических моделей из store или acquisition pipeline.
|
||||
|
||||
10. `_exchange_symbols_projection_cache` не является каноническим источником данных.
|
||||
|
||||
11. Публичный контракт:
|
||||
|
||||
```python
|
||||
get_exchange_symbols() -> list[ExchangeSymbol]
|
||||
```
|
||||
|
||||
сохранён для обратной совместимости.
|
||||
|
||||
12. `validate_symbol()` продолжает работать через `get_exchange_symbols()`.
|
||||
|
||||
13. При отключённой бирже store, projection cache и acquisition pipeline не используются.
|
||||
|
||||
14. Ошибка acquisition не заполняет канонический store.
|
||||
|
||||
15. Ошибка acquisition не заполняет projection cache.
|
||||
|
||||
16. Пустой `tuple[Instrument, ...]` является валидным сохранённым значением и должен отличаться от отсутствия записи в store.
|
||||
|
||||
---
|
||||
|
||||
## 15. Изменённые production-файлы
|
||||
|
||||
Основные изменения Build 020 выполнены в:
|
||||
|
||||
```text
|
||||
app/src/integrations/exchange/service.py
|
||||
```
|
||||
|
||||
Используются ранее подготовленные компоненты Storage Layer:
|
||||
|
||||
```text
|
||||
app/src/storage/instrument_store.py
|
||||
app/src/storage/exceptions.py
|
||||
```
|
||||
|
||||
Используется compatibility mapper:
|
||||
|
||||
```text
|
||||
app/src/market_data/acquisition/compatibility.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. Тестовое покрытие
|
||||
|
||||
Основные проверки миграции находятся в:
|
||||
|
||||
```text
|
||||
app/tests/unit/integrations/exchange/test_service_exchange_symbols.py
|
||||
```
|
||||
|
||||
Дополнительно проверена совместимость:
|
||||
|
||||
```text
|
||||
app/tests/unit/integrations/exchange/test_service_validate_symbol.py
|
||||
```
|
||||
|
||||
И отдельно сохраняется тестовое покрытие самого store:
|
||||
|
||||
```text
|
||||
app/tests/unit/storage/test_instrument_store.py
|
||||
```
|
||||
|
||||
Проверены следующие сценарии:
|
||||
|
||||
- отключённая биржа возвращает пустой список;
|
||||
- отключённая биржа не читает существующий `InstrumentStore`;
|
||||
- cache hit legacy-проекции не запускает acquisition pipeline;
|
||||
- store hit не запускает acquisition pipeline;
|
||||
- store miss запускает acquisition pipeline;
|
||||
- загруженные `Instrument` сохраняются в store;
|
||||
- пустой tuple корректно обрабатывается как cache hit;
|
||||
- compatibility mapping получает именно канонические `Instrument`;
|
||||
- legacy-проекция сохраняет прежний тип `list[ExchangeSymbol]`;
|
||||
- сохраняется порядок инструментов;
|
||||
- acquisition error оборачивается в `ExchangeError`;
|
||||
- acquisition error логируется с endpoint `exchangeInfo`;
|
||||
- acquisition error не заполняет store;
|
||||
- acquisition error не заполняет projection cache;
|
||||
- acquisition loader использует реальную processing pipeline;
|
||||
- acquisition loader использует registry key `dzengi`;
|
||||
- acquisition loader возвращает `tuple[Instrument, ...]`;
|
||||
- acquisition loader не вызывает compatibility mapper;
|
||||
- `validate_symbol()` сохраняет прежнее поведение.
|
||||
|
||||
---
|
||||
|
||||
## 17. Результаты проверок
|
||||
|
||||
Целевая проверка `get_exchange_symbols()`:
|
||||
|
||||
```text
|
||||
23 passed in 0.12s
|
||||
```
|
||||
|
||||
Целевая проверка `validate_symbol()`:
|
||||
|
||||
```text
|
||||
16 passed in 0.07s
|
||||
```
|
||||
|
||||
Проверка синтаксической компиляции:
|
||||
|
||||
```text
|
||||
py_compile — успешно
|
||||
```
|
||||
|
||||
Полный regression suite:
|
||||
|
||||
```text
|
||||
426 passed in 0.23s
|
||||
```
|
||||
|
||||
Финальная архитектурная проверка подтвердила:
|
||||
|
||||
```text
|
||||
старый _exchange_symbols_cache отсутствует в production-коде;
|
||||
старый _load_exchange_symbols_via_acquisition отсутствует;
|
||||
канонический store подключён через InstrumentStoreProtocol;
|
||||
текущая реализация store — InMemoryInstrumentStore;
|
||||
legacy projection cache отделён от канонического store;
|
||||
compatibility mapper не вызывается внутри acquisition loader;
|
||||
новый acquisition loader возвращает канонические Instrument.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 18. Архитектурный результат
|
||||
|
||||
До Build 020:
|
||||
|
||||
```text
|
||||
Exchange API
|
||||
↓
|
||||
Acquisition Pipeline
|
||||
↓
|
||||
Instrument
|
||||
↓
|
||||
Compatibility Mapper
|
||||
↓
|
||||
ExchangeSymbol
|
||||
↓
|
||||
ExchangeService class-level cache
|
||||
```
|
||||
|
||||
После Build 020:
|
||||
|
||||
```text
|
||||
Exchange API
|
||||
↓
|
||||
Acquisition Pipeline
|
||||
↓
|
||||
Instrument
|
||||
↓
|
||||
InstrumentStore
|
||||
↓
|
||||
Canonical Instrument Reference Data
|
||||
↓
|
||||
Compatibility Mapper
|
||||
↓
|
||||
ExchangeSymbol
|
||||
↓
|
||||
Temporary Legacy Projection Cache
|
||||
```
|
||||
|
||||
Ключевое изменение:
|
||||
|
||||
```text
|
||||
ExchangeSymbol больше не является канонически сохраняемой моделью Instrument Reference Data.
|
||||
```
|
||||
|
||||
Каноническим представлением теперь является:
|
||||
|
||||
```python
|
||||
Instrument
|
||||
```
|
||||
|
||||
а владельцем его состояния является:
|
||||
|
||||
```text
|
||||
Storage Layer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 19. Ограничения текущего этапа
|
||||
|
||||
Build 020 намеренно не выполняет полную миграцию всех consumers на каноническую модель `Instrument`.
|
||||
|
||||
На текущем этапе сохраняются:
|
||||
|
||||
```python
|
||||
get_exchange_symbols() -> list[ExchangeSymbol]
|
||||
```
|
||||
|
||||
и:
|
||||
|
||||
```python
|
||||
_exchange_symbols_projection_cache
|
||||
```
|
||||
|
||||
Они являются compatibility-механизмами переходного периода.
|
||||
|
||||
Также Build 020 не переносит в новый `InstrumentStore`:
|
||||
|
||||
- market price cache;
|
||||
- execution price cache;
|
||||
- balance snapshots;
|
||||
- journal storage;
|
||||
- другие runtime caches.
|
||||
|
||||
Build 020 касается исключительно хранения канонических Instrument Reference Data.
|
||||
|
||||
---
|
||||
|
||||
## 20. Критерии завершения
|
||||
|
||||
Build 020 считается завершённым, если одновременно выполняются следующие условия:
|
||||
|
||||
- [x] созданный ранее `InstrumentStore` используется production-кодом;
|
||||
- [x] канонические данные хранятся как `tuple[Instrument, ...]`;
|
||||
- [x] старый `_exchange_symbols_cache` удалён;
|
||||
- [x] старый `_load_exchange_symbols_via_acquisition()` удалён;
|
||||
- [x] новый `_load_instruments_via_acquisition()` возвращает канонические модели;
|
||||
- [x] acquisition loader не выполняет compatibility mapping;
|
||||
- [x] legacy API `get_exchange_symbols()` сохранён;
|
||||
- [x] `validate_symbol()` сохраняет прежнее поведение;
|
||||
- [x] ошибки acquisition не создают ложное состояние кэша;
|
||||
- [x] целевые тесты проходят;
|
||||
- [x] `py_compile` проходит;
|
||||
- [x] полный regression suite проходит;
|
||||
- [x] финальная архитектурная grep-проверка выполнена.
|
||||
|
||||
---
|
||||
|
||||
## 21. Итоговый статус
|
||||
|
||||
```text
|
||||
BUILD 020 — COMPLETE
|
||||
```
|
||||
|
||||
Build 020 завершает фактический перенос канонического кэша Instrument Reference Data из legacy `ExchangeService._exchange_symbols_cache` в специализированный Storage Layer.
|
||||
|
||||
Существующий бот продолжает работать через сохранённый compatibility-контракт:
|
||||
|
||||
```python
|
||||
get_exchange_symbols() -> list[ExchangeSymbol]
|
||||
```
|
||||
|
||||
При этом новая архитектура уже располагает полноценным каноническим хранилищем:
|
||||
|
||||
```text
|
||||
Instrument Acquisition Pipeline
|
||||
↓
|
||||
tuple[Instrument, ...]
|
||||
↓
|
||||
InstrumentStore
|
||||
```
|
||||
|
||||
Это создаёт основу для дальнейшего поэтапного перевода consumers с legacy `ExchangeSymbol` на каноническую модель `Instrument` без нарушения работоспособности существующего бота.
|
||||
Reference in New Issue
Block a user