feat: add market data architecture and complete migration through build 039

This commit is contained in:
2026-07-14 09:58:16 +03:00
parent 26deb861bc
commit a996f2f797
443 changed files with 80452 additions and 1335 deletions

View File

@@ -0,0 +1,599 @@
# Build 025 — Удаление legacy `ExchangeSymbol` compatibility layer
**Статус:** ✅ Завершён
**Дата:** 2026-07-12
**Подсистема:** Instrument Reference / Exchange Integration
**Этап:** Завершение миграции на каноническую модель `Instrument`
---
# Цель Build
Полностью удалить временный compatibility layer, использовавшийся во время поэтапного перехода от legacy-модели:
```text
ExchangeSymbol
```
к канонической модели:
```text
Instrument
```
После завершения Build все production-потребители работают через единый канонический контур:
```text
Instrument Acquisition
Instrument Store
get_instruments()
validate_symbol()
Instrument
```
---
# Предпосылки
На предыдущих этапах были выполнены:
- создание канонической модели `Instrument`;
- построение Instrument Acquisition pipeline;
- создание `InstrumentStoreProtocol`;
- реализация `InMemoryInstrumentStore`;
- перенос кэша справочника инструментов в Storage;
- добавление `ExchangeService.get_instruments()`;
- перевод `validate_symbol()` на канонический справочник;
- перевод Telegram UI и runtime-потребителей на `Instrument`;
- удаление неиспользуемого legacy Market Handler.
После этого legacy-контур сохранялся только как временная проекция:
```text
Instrument
compatibility.py
ExchangeSymbol
get_exchange_symbols()
```
Реальных production-потребителей этого контура больше не осталось.
---
# Предварительный аудит
Выполнен поиск production-зависимостей:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
-E "ExchangeSymbol|get_exchange_symbols|map_instrument_to_exchange_symbol|map_instruments_to_exchange_symbols|_exchange_symbols_projection_cache" \
src
```
Установлено, что все найденные элементы находились только внутри самого legacy-контура:
```text
src/market_data/acquisition/compatibility.py
src/integrations/exchange/models.py
src/integrations/exchange/service.py
```
Канонические production-потребители уже использовали:
```text
get_instruments()
validate_symbol()
Instrument
```
---
# Аудит legacy parser helpers
Проверены методы:
```text
_extract_exchange_symbols_raw()
_parse_exchange_symbol()
_parse_exchange_symbol_status()
_parse_market_modes()
_extract_filter_value()
```
Выполнено:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
-E "_extract_exchange_symbols_raw|_parse_exchange_symbol\(|_parse_exchange_symbol_status|_parse_market_modes|_extract_filter_value" \
src tests
```
Подтверждено, что эти методы использовались только устаревшим equivalence-тестом и больше не требовались production-коду.
---
# Подготовка тестового контура
Перед удалением production compatibility layer были обновлены канонические тесты.
## Обновлён файл
```text
tests/unit/integrations/exchange/test_service_instruments.py
```
Из него удалены тесты legacy-проекции:
```text
test_get_exchange_symbols_uses_get_instruments
test_get_exchange_symbols_maps_canonical_instruments
test_get_exchange_symbols_projection_cache_preserves_identity
```
Сохранены все тесты канонического поведения:
- отключённая биржа;
- Instrument Store hit;
- Instrument Store miss;
- загрузка через acquisition;
- сохранение результата в Store;
- повторное использование Store;
- поддержка пустого immutable-набора;
- общий Store между экземплярами `ExchangeService`;
- обработка acquisition errors;
- логирование ошибок;
- отсутствие заполнения Store при ошибке.
---
## Обновлён файл
```text
tests/unit/integrations/exchange/test_service_validate_symbol.py
```
Legacy-проверка через monkeypatch метода:
```text
get_exchange_symbols()
```
заменена проверкой прямого использования:
```text
get_instruments()
```
Дополнительно добавлен отрицательный архитектурный тест:
```python
def test_exchange_service_has_no_legacy_get_exchange_symbols() -> None:
assert not hasattr(
ExchangeService,
"get_exchange_symbols",
)
```
---
## Обновлён файл
```text
tests/unit/telegram/ui/test_currency_ui.py
```
Legacy-проверка отсутствия вызова `get_exchange_symbols()` заменена прямой проверкой использования канонического:
```text
get_instruments()
```
---
# Проверка подготовительного пакета
Выполнена компиляция:
```bash
python -m py_compile \
tests/unit/integrations/exchange/test_service_instruments.py \
tests/unit/integrations/exchange/test_service_validate_symbol.py \
tests/unit/telegram/ui/test_currency_ui.py
```
Результат:
```text
Ошибок нет.
```
Выполнены целевые тесты:
```bash
python -m pytest \
tests/unit/integrations/exchange/test_service_instruments.py \
tests/unit/integrations/exchange/test_service_validate_symbol.py \
tests/unit/telegram/ui/test_currency_ui.py \
-q
```
Результат:
```text
49 passed
```
---
# Удалённые migration-only файлы
Полностью удалены:
```text
src/market_data/acquisition/compatibility.py
tests/unit/market_data/acquisition/test_compatibility.py
tests/unit/market_data/acquisition/test_equivalence_comparator.py
tests/unit/integrations/exchange/test_service_exchange_symbols.py
tests/integration/market_data/acquisition/test_instrument_reference_equivalence.py
tests/support/instrument_reference_equivalence.py
```
Эти файлы обслуживали только временный compatibility/equivalence-контур и завершили свою миграционную задачу.
---
# Изменения в `models.py`
Из файла:
```text
src/integrations/exchange/models.py
```
полностью удалена legacy-модель:
```python
@dataclass(slots=True)
class ExchangeSymbol:
...
```
Модель:
```python
SymbolValidationResult
```
сохраняет канонический контракт:
```python
symbol_info: Instrument | None
```
Импорт `Instrument` выполняется через:
```python
TYPE_CHECKING
```
что исключает runtime-cycle и сохраняет корректную типизацию.
---
# Изменения в `service.py`
Из файла:
```text
src/integrations/exchange/service.py
```
удалены следующие элементы.
## Legacy imports
Удалены:
```python
ExchangeSymbol
```
и:
```python
from src.market_data.acquisition.compatibility import (
map_instruments_to_exchange_symbols,
)
```
---
## Legacy projection cache
Удалено поле:
```python
_exchange_symbols_projection_cache
```
Теперь `ExchangeService` хранит только канонический Store:
```python
_instrument_store: InstrumentStoreProtocol = InMemoryInstrumentStore()
```
---
## Legacy API
Полностью удалён метод:
```python
get_exchange_symbols()
```
Единственным публичным API справочника инструментов остаётся:
```python
get_instruments()
```
---
## Legacy parser helpers
Удалены методы:
```text
_extract_exchange_symbols_raw()
_parse_exchange_symbol()
_parse_exchange_symbol_status()
_parse_market_modes()
_extract_filter_value()
```
Их функции полностью заменены новой pipeline:
```text
Dzengi REST document
Dzengi parser
validation
mapper
Instrument
```
---
## Сохранённый helper
Метод:
```python
_safe_str()
```
сохранён, так как продолжает использоваться обработкой trading fee payload.
---
# Финальный production-контур
После удаления compatibility layer работа со справочником инструментов выполняется так:
```text
DzengiInstrumentDocumentSource
DzengiInstrumentDocumentHandler
InstrumentFeed
InstrumentFeedRegistry
InstrumentAcquisitionService
tuple[Instrument, ...]
Instrument Store
ExchangeService.get_instruments()
```
Проверка пользовательского символа выполняется через:
```text
validate_symbol()
SymbolValidationResult
symbol_info: Instrument | None
```
---
# Архитектурные отрицательные тесты
Добавлены проверки физического отсутствия legacy API.
## Отсутствие legacy-метода
```python
def test_exchange_service_has_no_legacy_get_exchange_symbols() -> None:
assert not hasattr(
ExchangeService,
"get_exchange_symbols",
)
```
## Отсутствие legacy projection cache
```python
def test_exchange_service_has_no_legacy_projection_cache() -> None:
assert not hasattr(
ExchangeService,
"_exchange_symbols_projection_cache",
)
```
---
# Финальный аудит
Выполнено:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
-E "ExchangeSymbol|get_exchange_symbols|map_instrument_to_exchange_symbol|map_instruments_to_exchange_symbols|_exchange_symbols_projection_cache|market_data\.acquisition\.compatibility" \
src tests
```
Остались только ожидаемые упоминания в отрицательных архитектурных тестах:
```text
test_exchange_service_has_no_legacy_get_exchange_symbols
test_exchange_service_has_no_legacy_projection_cache
```
Других production- или test-зависимостей не обнаружено.
---
# Проверка компиляции
Выполнено:
```bash
python -m py_compile \
src/integrations/exchange/models.py \
src/integrations/exchange/service.py \
tests/unit/integrations/exchange/test_service_instruments.py \
tests/unit/integrations/exchange/test_service_validate_symbol.py \
tests/unit/telegram/ui/test_currency_ui.py
```
Результат:
```text
Ошибок нет.
```
---
# Целевой Regression Suite
Выполнено:
```bash
python -m pytest \
tests/unit/integrations/exchange/test_service_instruments.py \
tests/unit/integrations/exchange/test_service_validate_symbol.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 \
tests/unit/telegram/ui/test_currency_ui.py \
-q
```
Результат:
```text
94 passed
```
---
# Полный Regression Suite
Выполнено:
```bash
python -m pytest -q
```
Результат:
```text
423 passed
```
Снижение общего числа тестов относительно предыдущего Build является ожидаемым, поскольку были удалены временные compatibility- и equivalence-тесты вместе с соответствующим legacy-кодом.
---
# Архитектурный итог
До Build 025:
```text
Instrument
├── canonical consumers
└── compatibility mapper
ExchangeSymbol
legacy projection cache
```
После Build 025:
```text
Instrument
Instrument Store
canonical consumers
```
В проекте больше не существует:
```text
ExchangeSymbol
compatibility.py
get_exchange_symbols()
_exchange_symbols_projection_cache
Instrument → ExchangeSymbol mapping
legacy exchangeInfo parser helpers
migration equivalence framework
```
---
# Итог Build
Build 025 полностью завершён.
Подтверждено:
- все production-потребители переведены на `Instrument`;
- удалена legacy-модель `ExchangeSymbol`;
- удалён временный compatibility mapper;
- удалён legacy-метод `get_exchange_symbols()`;
- удалён projection cache;
- удалены неиспользуемые parser helpers;
- удалены migration-only и equivalence-тесты;
- сохранены и усилены канонические тесты Instrument Store;
- добавлены архитектурные тесты отсутствия legacy API;
- компиляция проходит без ошибок;
- целевой Regression Suite успешно пройден — **94 passed**;
- полный Regression Suite успешно пройден — **423 passed**.
**Статус Build:** ✅ Завершён.