Files
dzentra_bot/docs/migrations/build_013.md

19 KiB
Raw Permalink Blame History

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 тестов.

Проверяются:

  1. полностью эквивалентные инструменты;
  2. эквивалентность float и Decimal;
  3. эквивалентность list и tuple для market_modes;
  4. несовпадение строкового поля;
  5. несовпадение числового поля;
  6. символ отсутствует в новой реализации;
  7. символ отсутствует в legacy-реализации;
  8. duplicate symbol в legacy;
  9. duplicate symbol в новой реализации;
  10. несколько расхождений одновременно;
  11. корректное формирование диагностического отчёта.

Результат:

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