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