# 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 ```