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