build 039: complete Quotes Feed migration foundation

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

View File

@@ -0,0 +1,812 @@
# Dzentra — Instrument Reference Data Migration — Build 013
> Статус: Завершён
## Название
**Проверка эквивалентности старой и новой реализации**
---
## Цель
Доказать эквивалентность существующей legacy-реализации обработки `exchangeInfo` и новой подсистемы Instrument Reference Data до начала переключения production-потребителей.
Проверка должна подтвердить, что один и тот же исходный документ `exchangeInfo`, обработанный двумя независимыми путями, приводит к эквивалентным результатам в части полей, существующих одновременно в legacy-модели `ExchangeSymbol` и новой модели `Instrument`.
Build 013 не изменяет production-код и не переключает существующий runtime на новую реализацию.
---
## Исходная архитектура проверки
Один и тот же сохранённый документ используется обеими реализациями:
```text
один exchangeInfo document
├──→ legacy implementation
│ │
│ ├──→ _extract_exchange_symbols_raw()
│ │
│ └──→ _parse_exchange_symbol()
│ │
│ ↓
│ list[ExchangeSymbol]
└──→ new implementation
└──→ DzengiInstrumentDocumentHandler
├──→ schema validation
├──→ parser
├──→ value validation
└──→ mapper
tuple[Instrument, ...]
equivalence comparator
structured comparison report
```
Сетевые запросы в проверке не выполняются.
Обе реализации получают один и тот же сохранённый документ, что исключает влияние изменений данных биржи между двумя отдельными REST-запросами.
---
## Реализованные файлы
Созданы:
```text
app/tests/support/instrument_reference_equivalence.py
app/tests/unit/market_data/acquisition/
└── test_equivalence_comparator.py
app/tests/integration/market_data/acquisition/
└── test_instrument_reference_equivalence.py
```
Production-код не изменялся.
Не создавался production-модуль:
```text
app/src/market_data/acquisition/equivalence.py
```
Это принципиальное архитектурное решение: механизм проверки эквивалентности является временным миграционным инструментом и не должен создавать зависимость новой production-подсистемы от legacy-модели `ExchangeSymbol`.
---
## Проверяемые реализации
### Legacy implementation
Проверяется существующая логика:
```text
ExchangeService._extract_exchange_symbols_raw()
ExchangeService._parse_exchange_symbol()
```
Результат:
```text
list[ExchangeSymbol]
```
Для запуска legacy parsing logic используется:
```python
service = object.__new__(ExchangeService)
```
Это позволяет проверить существующие parsing helpers без запуска:
```text
ExchangeService.__init__()
load_settings()
JournalService()
REST request
exchange symbols cache
```
На корректном документе используемые parsing helpers не требуют `settings` или `journal`.
Legacy-логика не копируется и не переписывается внутри теста.
---
### New implementation
Проверяется существующий Handler:
```text
DzengiInstrumentDocumentHandler
```
Вызов:
```python
DzengiInstrumentDocumentHandler().handle_instrument_document(document)
```
Через него запускается новая pipeline:
```text
schema validation
parser
value validation
mapper
tuple[Instrument, ...]
```
Таким образом Build 013 проверяет реальную реализацию, созданную в предыдущих Build, а не её тестовую копию.
---
## Источник тестовых данных
Для integration-проверки используется сохранённый реальный sample:
```text
app/tools/dzengi_probe/runtime_samples/rest/exchangeInfo/all.json
```
Один и тот же JSON document передаётся обеим реализациям.
Это гарантирует корректность сравнения:
```text
same input
legacy implementation
new implementation
equivalence comparison
```
---
## Общие поля моделей
Legacy-модель:
```text
ExchangeSymbol
```
Новая модель:
```text
Instrument
```
Сравниваются только поля, существующие одновременно в обеих моделях:
```text
symbol
name
status
base_asset
quote_asset
market_modes
market_type
tick_size
step_size
min_qty
min_notional
```
Всего:
```text
11 общих полей
```
---
## Поля новой модели, не участвующие в проверке эквивалентности
Новая модель `Instrument` содержит дополнительные поля:
```text
asset_type
order_types
base_asset_precision
quote_asset_precision
tick_value
max_qty
country
sector
industry
trading_hours
```
Их отсутствие в legacy-модели `ExchangeSymbol` не является нарушением эквивалентности.
Эти поля являются расширением новой внутренней модели Instrument Reference Data.
---
## Каноническое сравнение числовых значений
Legacy implementation хранит числовые ограничения как:
```text
float | None
```
Новая модель хранит их как:
```text
Decimal | None
```
Для корректного сравнения обе стороны приводятся к общей канонической форме:
```python
Decimal(str(value))
```
Пример:
```text
legacy:
0.0001
new:
Decimal("0.0001")
canonical comparison:
Decimal("0.0001") == Decimal("0.0001")
```
Таким образом различие представления:
```text
float
Decimal
```
не считается различием значения.
Для сравнения не используется:
```text
math.isclose()
```
Поскольку биржевые ограничения являются точными справочными десятичными значениями, а не измерениями с допустимой погрешностью.
---
## Сравнение market_modes
Legacy-модель использует:
```python
list[str]
```
Новая модель использует:
```python
tuple[str, ...]
```
Обе стороны приводятся к:
```python
tuple(value)
```
Поэтому:
```text
["REGULAR"]
```
эквивалентно:
```text
("REGULAR",)
```
Тип контейнера не считается расхождением.
Порядок значений сохраняется и участвует в сравнении.
Например:
```text
("REGULAR", "CLOSE_ONLY")
```
не эквивалентно:
```text
("CLOSE_ONLY", "REGULAR")
```
---
## Сравнение строковых значений
Следующие поля сравниваются точно:
```text
symbol
name
status
base_asset
quote_asset
market_type
```
Не выполняется дополнительная нормализация:
```text
lower()
upper()
casefold()
alias mapping
```
Причина: Build 013 должен обнаруживать реальные различия интерпретации между двумя реализациями, а не скрывать их дополнительной логикой comparator.
---
## Проверка состава инструментов
До сравнения полей проверяются:
```text
дубликаты symbol в legacy implementation;
дубликаты symbol в new implementation;
symbol существует только в legacy;
symbol существует только в new.
```
После проверки дубликатов строятся индексы:
```text
symbol → ExchangeSymbol
symbol → Instrument
```
Для построения индексов используется сохранение первого встретившегося объекта.
Дубликаты при этом уже отдельно фиксируются как mismatch и не могут быть незаметно скрыты перезаписью значения в словаре.
---
## Модель расхождения
Каждое найденное расхождение представлено структурой:
```text
InstrumentReferenceMismatch
```
Поля:
```text
kind
symbol
field
legacy_value
new_value
message
```
Поддерживаемые категории:
```text
duplicate_legacy
duplicate_new
missing_in_legacy
missing_in_new
field_mismatch
```
Пример расхождения поля:
```text
kind: field_mismatch
symbol: ETH/EUR_LEVERAGE
field: min_notional
legacy: None
new: Decimal("2")
```
---
## Модель итогового отчёта
Результат сравнения представлен структурой:
```text
InstrumentReferenceEquivalenceReport
```
Поля:
```text
legacy_count
new_count
compared_count
mismatches
```
Также предоставляется свойство:
```text
is_equivalent
```
Логика:
```text
mismatches == ()
is_equivalent == True
```
При наличии хотя бы одного mismatch:
```text
is_equivalent == False
```
---
## Диагностический отчёт
Метод:
```text
InstrumentReferenceEquivalenceReport.format()
```
формирует человекочитаемый диагностический отчёт.
Успешный результат имеет вид:
```text
Instrument Reference Data equivalence report
legacy_count: 51
new_count: 51
compared_count: 51
mismatches: 0
is_equivalent: True
```
При обнаружении различий отчёт содержит для каждого mismatch:
```text
kind
symbol
field
legacy value
new value
message
```
Это позволяет анализировать все обнаруженные расхождения, а не только первое.
---
## Unit-тесты comparator
Создан файл:
```text
app/tests/unit/market_data/acquisition/test_equivalence_comparator.py
```
Реализовано 11 тестов.
Проверяются:
1. полностью эквивалентные инструменты;
2. эквивалентность `float` и `Decimal`;
3. эквивалентность `list` и `tuple` для `market_modes`;
4. несовпадение строкового поля;
5. несовпадение числового поля;
6. символ отсутствует в новой реализации;
7. символ отсутствует в legacy-реализации;
8. duplicate symbol в legacy;
9. duplicate symbol в новой реализации;
10. несколько расхождений одновременно;
11. корректное формирование диагностического отчёта.
Результат:
```text
11 passed in 0.02s
```
---
## Integration-проверка реального exchangeInfo sample
Создан файл:
```text
app/tests/integration/market_data/acquisition/
└── test_instrument_reference_equivalence.py
```
Проверяется полный путь:
```text
all.json
├──→ legacy parser
│ ↓
│ list[ExchangeSymbol]
└──→ DzengiInstrumentDocumentHandler
tuple[Instrument, ...]
compare_instrument_reference_data()
InstrumentReferenceEquivalenceReport
```
Основное утверждение:
```python
assert report.is_equivalent, report.format()
```
Результат:
```text
1 passed in 0.10s
```
Проверка подтвердила эквивалентность legacy- и новой реализации на одном и том же реальном `exchangeInfo` sample.
---
## Проверка синтаксиса
Выполнена команда:
```bash
python -m py_compile \
tests/support/instrument_reference_equivalence.py \
tests/unit/market_data/acquisition/test_equivalence_comparator.py \
tests/integration/market_data/acquisition/test_instrument_reference_equivalence.py
```
Результат:
```text
успешно
```
Ошибок синтаксиса не обнаружено.
---
## Полный регрессионный прогон
Выполнена команда:
```bash
python -m pytest -q
```
Результат:
```text
192 passed in 0.15s
```
Регрессий существующего проекта не обнаружено.
---
## Проверка отсутствия production-зависимостей
Выполнена команда:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
-E "compare_instrument_reference_data|InstrumentReferenceMismatch|InstrumentReferenceEquivalenceReport" \
src tests
```
Результат подтвердил, что все сущности механизма проверки эквивалентности находятся только в:
```text
tests/support/
tests/unit/
tests/integration/
```
В каталоге:
```text
src/
```
упоминаний нет.
Следовательно:
```text
production-код не зависит от comparator;
новая acquisition-подсистема не зависит от legacy-модели ради runtime;
ExchangeService не изменён;
Telegram UI не изменён;
AutoTrade не изменён;
trading runtime не изменён.
```
---
## Изменения production-кода
В рамках Build 013 production-код не изменялся.
Не изменены:
```text
app/src/integrations/exchange/service.py
app/src/integrations/exchange/models.py
app/src/market_data/acquisition/
app/src/telegram/
app/src/trading/
```
Не создавался:
```text
app/src/market_data/acquisition/equivalence.py
```
---
## Обратная совместимость
Полностью сохранены:
```text
ExchangeService.get_exchange_symbols()
ExchangeService.validate_symbol()
ExchangeService.get_symbol_runtime_status()
```
Также не изменены:
```text
сигнатуры существующих production-методов;
legacy imports;
формат runtime-ошибок;
Telegram UI;
автоторговля;
существующее runtime-поведение;
exchange symbols cache.
```
---
## Что Build 013 намеренно не делает
Build 013 не выполняет:
```text
изменение legacy parser;
изменение нового parser;
изменение mapper;
переключение get_exchange_symbols();
создание compatibility mapper Instrument → ExchangeSymbol;
перенос кэша;
изменение validate_symbol();
изменение get_symbol_runtime_status();
подключение InstrumentAcquisitionService к production runtime;
изменение Telegram UI;
изменение AutoTrade.
```
Эти изменения относятся к последующим Build утверждённого Migration Plan.
---
## Классификация изменений
| Изменение | Классификация |
|---|---|
| Comparator legacy/new | Миграционная проверка |
| Структурированный отчёт | Улучшение диагностируемости |
| Unit-тесты comparator | Обязательная проверка надёжности |
| Integration-тест на одном sample | Обязательная проверка эквивалентности |
| Новый production-модуль | Не создавался |
| Изменение legacy-кода | Отсутствует |
| Изменение новой acquisition pipeline | Отсутствует |
| Изменение runtime-поведения | Отсутствует |
---
## Итоговые проверки
| Проверка | Результат |
|---|---|
| Unit-тесты comparator | `11 passed in 0.02s` |
| Integration-тест реального sample | `1 passed in 0.10s` |
| `py_compile` | Успешно |
| Полный `pytest` | `192 passed in 0.15s` |
| Отсутствие production-интеграции comparator | Подтверждено |
---
## Условие завершения Build 013
Все условия выполнены:
```text
[✓] Comparator обнаруживает категории различий.
[✓] Legacy и new implementations получают один и тот же документ.
[✓] Legacy parser запускается без REST-запроса и кэша.
[✓] New pipeline запускается через реальный DzengiInstrumentDocumentHandler.
[✓] Дубликаты проверяются до сравнения полей.
[✓] Проверяются все 11 общих полей.
[✓] Реальный exchangeInfo sample проходит без mismatches.
[✓] Unit-тесты comparator проходят.
[✓] Integration-тест проходит.
[✓] Синтаксическая проверка проходит.
[✓] Полный набор тестов проекта проходит.
[✓] Production-код не изменён.
[✓] Обратная совместимость полностью сохранена.
```
---
## Результат
```text
BUILD 013 — COMPLETE
```
Проверка подтвердила эквивалентность существующей legacy-реализации и новой Instrument Reference Data pipeline на одном и том же реальном документе `exchangeInfo`.
Можно переходить к следующему этапу утверждённого Migration Plan:
```text
Build 014 — Compatibility mapper Instrument → ExchangeSymbol
```