Files
dzentra_bot/docs/migrations/build_013.md

812 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```