build 039: complete Quotes Feed migration foundation
This commit is contained in:
812
docs/migrations/build_013.md
Normal file
812
docs/migrations/build_013.md
Normal file
@@ -0,0 +1,812 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user