# Dzentra — Instrument Reference Data Migration ## Build 004 — Dzengi exchangeInfo Parser **Статус:** Завершён **Подсистема:** Market Data Acquisition **Компонент:** Dzengi Adapter / exchangeInfo Parser **Проект:** Dzentra **Язык:** Русский **Python:** 3.12 --- ## 1. Цель Build 004 Цель Build 004 — реализовать parser для ответа Dzengi `exchangeInfo`, который преобразует уже структурно проверенный документ из Build 003 в типизированные транспортные модели Dzengi, созданные в Build 002. Целевая цепочка после завершения Build 004: ```text Raw JSON document ↓ validate_exchange_info_schema() ↓ ValidatedExchangeInfoDocument ↓ parse_exchange_info() ↓ DzengiExchangeInfoResponse ``` Build 004 не подключает новую реализацию к существующему `ExchangeService` и не изменяет поведение работающего бота. --- ## 2. Почему Build 004 выполняется именно сейчас До начала Build 004 уже были завершены необходимые предыдущие этапы. ### Build 001 — внутренняя модель Instrument Создана внутренняя типизированная модель: ```text Instrument ``` Она представляет инструмент внутри новой архитектуры Dzentra и не зависит от формата конкретной биржи. ### Build 002 — транспортные модели Dzengi Созданы типизированные модели сырого ответа `exchangeInfo`: ```text DzengiExchangeInfoResponse DzengiExchangeInfoPayload DzengiExchangeInfoSymbol DzengiRateLimit DzengiInstrumentFilter DzengiLotSizeFilter DzengiMinNotionalFilter DzengiUnknownFilter ``` ### Build 003 — структурная валидация exchangeInfo Создан слой проверки структуры сырого JSON-документа: ```text validate_exchange_info_schema() ``` Его результат: ```text ValidatedExchangeInfoDocument ``` Таким образом, только после Build 001–003 стало безопасно реализовать parser, не смешивая: - проверку структуры JSON; - разбор транспортного ответа; - предметное преобразование в `Instrument`; - сетевой REST-доступ; - кэширование; - legacy-совместимость. --- ## 3. Реализованная архитектурная цепочка На текущем этапе действует следующая архитектура: ```text Raw Dzengi JSON ↓ Schema Validation ↓ ValidatedExchangeInfoDocument ↓ Dzengi Parser ↓ DzengiExchangeInfoResponse ``` Полная целевая цепочка миграции пока ещё не завершена: ```text Dzengi REST API ↓ Dzengi REST Adapter ↓ Schema Validation ↓ Parser ↓ Dzengi Raw Models ↓ Mapper ↓ Instrument ↓ Instrument Handler ↓ Instrument Feed ↓ Market Data Acquisition Service ↓ Compatibility Layer ↓ ExchangeService ↓ Existing Consumers ``` Build 004 реализует только участок: ```text ValidatedExchangeInfoDocument ↓ Parser ↓ Dzengi Raw Models ``` --- ## 4. Файлы Build 004 ### Реализован ```text app/src/market_data/acquisition/adapters/dzengi/parser.py ``` ### Использованы существующие файлы ```text app/src/market_data/acquisition/adapters/dzengi/models.py app/src/market_data/acquisition/validation/schema.py app/src/market_data/acquisition/exceptions.py ``` ### Добавлены тесты ```text app/tests/unit/market_data/acquisition/adapters/dzengi/test_parser.py ``` --- ## 5. Публичный контракт parser Основной публичный вход Build 004: ```python def parse_exchange_info( document: ValidatedExchangeInfoDocument, ) -> DzengiExchangeInfoResponse: ... ``` Parser намеренно не принимает произвольный сырой `dict`. Корректная последовательность вызовов: ```python validated = validate_exchange_info_schema(raw_document) response = parse_exchange_info(validated) ``` Это обеспечивает явное разделение ответственности между Build 003 и Build 004. --- ## 6. Разделение ответственности ### Build 003 — Schema Validation Отвечает за структурную корректность документа: - корневой объект должен быть JSON-объектом; - `payload`, если используется wrapped-формат, должен быть объектом; - `symbols` должен быть массивом; - каждый элемент `symbols` должен быть объектом; - `filters` должен быть массивом; - другие структурные ограничения проверяются до parser. Build 003 не создаёт транспортные модели Dzengi. ### Build 004 — Parser Отвечает за преобразование структурно проверенного документа в: ```text DzengiExchangeInfoResponse ``` и вложенные типизированные транспортные модели. Parser не отвечает за: - HTTP-запросы; - кэширование; - предметную модель `Instrument`; - нормализацию символа для бизнес-логики; - runtime-статус инструмента; - UI; - legacy-совместимость. --- ## 7. Поддерживаемые форматы exchangeInfo Архитектура поддерживает два формата ответа. ### Unwrapped ```json { "timezone": "UTC", "serverTime": 1783537921471, "rateLimits": [], "exchangeFilters": [], "symbols": [] } ``` После Build 003: ```text is_wrapped: False status: None correlation_id: None ``` После Build 004 документ преобразуется в: ```text DzengiExchangeInfoResponse └── payload: DzengiExchangeInfoPayload ``` ### Wrapped ```json { "status": "OK", "correlationId": "2", "payload": { "timezone": "UTC", "serverTime": 1783537921471, "rateLimits": [], "exchangeFilters": [], "symbols": [] } } ``` После Build 003: ```text is_wrapped: True status: OK correlation_id: 2 ``` После Build 004 метаданные оболочки сохраняются в: ```text DzengiExchangeInfoResponse.status DzengiExchangeInfoResponse.correlation_id ``` --- ## 8. Разбор инструмента Каждый элемент массива `symbols` преобразуется в: ```text DzengiExchangeInfoSymbol ``` Поддерживаются следующие поля: ```text symbol name status asset_type base_asset base_asset_precision quote_asset quote_asset_id quote_precision order_types filters market_modes market_type country sector industry trading_hours tick_size tick_value trading_fee exchange_fee long_rate short_rate swap_charge_interval min_sl_gap max_sl_gap min_tp_gap max_tp_gap ``` На этом этапе поля сохраняют транспортную семантику Dzengi и ещё не преобразуются в предметную модель `Instrument`. --- ## 9. Разбор filters Parser преобразует известные типы фильтров в отдельные типизированные модели. ### LOT_SIZE Преобразуется в: ```text DzengiLotSizeFilter ``` Поля: ```text filter_type min_qty max_qty step_size ``` Пример: ```text DzengiLotSizeFilter( filter_type='LOT_SIZE', min_qty='0.0001', max_qty='1000', step_size='0.0001', ) ``` ### MIN_NOTIONAL Преобразуется в: ```text DzengiMinNotionalFilter ``` Поле: ```text min_notional ``` ### Неизвестные фильтры Неизвестный `filterType` не отбрасывается автоматически. Он преобразуется в: ```text DzengiUnknownFilter ``` Это позволяет сохранить неизвестные скалярные поля транспортного ответа без добавления неподтверждённой предметной семантики. --- ## 10. Числовые значения Build 004 сохраняет важное разделение между транспортным и предметным слоями. Например, значения фильтра: ```json { "minQty": "0.0001", "maxQty": "1000", "stepSize": "0.0001" } ``` в транспортной модели остаются: ```text min_qty='0.0001' max_qty='1000' step_size='0.0001' ``` Parser не выполняет преждевременное преобразование этих значений в `float`. Преобразование в точный предметный числовой тип должно выполняться на следующем архитектурном этапе при построении `Instrument`. Это позволяет избежать потери точности и сохраняет исходную семантику ответа Dzengi. --- ## 11. Обработка ошибок Для ошибок parser используется отдельное исключение: ```text InstrumentReferenceParseError ``` Оно объявлено в: ```text app/src/market_data/acquisition/exceptions.py ``` Иерархия: ```text MarketDataAcquisitionError └── InstrumentReferenceParseError ``` `InstrumentReferenceParseError` используется только: - в `parser.py`; - в unit-тестах parser. На момент завершения Build 004 это исключение не используется: - `ExchangeService`; - Telegram UI; - runtime-кодом; - автоторговлей; - существующими production-потребителями. --- ## 12. Классификация изменений | Изменение | Классификация | Влияние на поведение | |---|---|---| | Реализация `exchangeInfo` parser | Обязательное архитектурное изменение | Нет | | Типизированное преобразование symbols | Обязательное архитектурное изменение | Нет | | Типизированный разбор известных filters | Обязательное архитектурное изменение | Нет | | Сохранение неизвестных filters | Улучшение надёжности | Нет | | Отдельное `InstrumentReferenceParseError` | Улучшение надёжности | Нет | | Сохранение числовых строк без преобразования в `float` | Улучшение надёжности | Нет | | Поддержка wrapped и unwrapped форматов | Улучшение надёжности | Нет | Изменений поведения работающего бота в Build 004 нет. --- ## 13. Обратная совместимость Build 004 не изменяет существующие публичные интерфейсы. Без изменений продолжают работать: ```text ExchangeService.get_exchange_symbols() ExchangeService.validate_symbol() ExchangeService.get_symbol_runtime_status() ExchangeService.get_symbol_market_status() ``` Также без изменений остаются: ```text ExchangeSymbol SymbolValidationResult normalize_symbol() symbol_candidates() ``` Новая реализация parser пока не подключена к существующему `ExchangeService`. Следовательно: - Telegram UI не изменён; - автоторговля не изменена; - runtime-проверки символа не изменены; - существующий кэш инструментов не изменён; - старые импорты продолжают работать; - production-поведение бота сохранено. --- ## 14. Выполненные проверки ### Проверка 1 — unit-тесты parser Команда: ```bash python -m pytest \ tests/unit/market_data/acquisition/adapters/dzengi/test_parser.py \ -q ``` Результат: ```text 19 passed in 0.02s ``` Статус: ```text PASS ``` --- ### Проверка 2 — синтаксическая компиляция Команда: ```bash python -m py_compile \ src/market_data/acquisition/exceptions.py \ src/market_data/acquisition/adapters/dzengi/parser.py \ tests/unit/market_data/acquisition/adapters/dzengi/test_parser.py ``` Результат: ```text Команда завершена без ошибок. ``` Статус: ```text PASS ``` --- ### Проверка 3 — ручная цепочка schema → parser Была проверена цепочка: ```text raw dict ↓ validate_exchange_info_schema() ↓ ValidatedExchangeInfoDocument ↓ parse_exchange_info() ↓ DzengiExchangeInfoResponse ``` Полученный результат: ```text Symbol: BTC/USD_LEVERAGE Status: TRADING Asset type: CRYPTOCURRENCY Base asset: BTC Quote asset: USD Tick size: 0.05 Filters: (DzengiLotSizeFilter(filter_type='LOT_SIZE', min_qty='0.0001', max_qty='1000', step_size='0.0001'),) ``` Статус: ```text PASS ``` --- ### Проверка 4 — полный набор тестов проекта Команда: ```bash python -m pytest -q ``` Результат: ```text 50 passed in 0.05s ``` Статус: ```text PASS ``` --- ### Проверка 5 — контроль зависимостей parser Команда: ```bash grep -RIn \ --exclude-dir="__pycache__" \ --exclude="*.pyc" \ -E "parse_exchange_info|_parse_exchange_info|DzengiExchangeInfoParseError" \ src tests ``` Подтверждено: - `parse_exchange_info()` определён в новом `parser.py`; - используется только unit-тестами нового parser; - не используется существующим `ExchangeService`; - не используется UI; - не используется runtime; - не используется автоторговлей. Статус: ```text PASS ``` --- ### Проверка 6 — контроль использования parse-ошибки Команда: ```bash grep -RIn \ --exclude-dir="__pycache__" \ --exclude="*.pyc" \ -E "InstrumentReferenceParseError" \ src tests ``` Подтверждено: - исключение объявлено в `exceptions.py`; - используется parser; - используется unit-тестами parser; - не подключено к production-потребителям. Статус: ```text PASS ``` --- ## 15. Итог Build 004 Build 004 успешно завершён. Реализован типизированный parser: ```text ValidatedExchangeInfoDocument ↓ parse_exchange_info() ↓ DzengiExchangeInfoResponse ``` Подтверждено: ```text Build 001 — Instrument domain model PASS Build 002 — Dzengi raw transport models PASS Build 003 — exchangeInfo schema validation PASS Build 004 — Dzengi exchangeInfo parser PASS ``` Общий результат тестов после завершения Build 004: ```text 50 passed in 0.05s ``` Работающий бот не затронут. --- ## 16. Условие завершения Build 004 Build 004 считается завершённым, поскольку выполнены все условия: - parser реализован; - parser принимает только `ValidatedExchangeInfoDocument`; - parser возвращает `DzengiExchangeInfoResponse`; - основные поля инструмента сохраняются; - известные filters типизированы; - неизвестные filters сохраняются; - ошибки parser имеют отдельный тип; - unit-тесты проходят; - полный набор тестов проходит; - production-потребители не изменены; - обратная совместимость сохранена. --- ## 17. Следующий этап Следующий этап миграции: ```text Build 005 — Проверка значений Instrument Reference Data ``` Его задача — преобразовать: ```text DzengiExchangeInfoSymbol ↓ Mapper ↓ Instrument ``` При этом Build 005 должен: - использовать модель `Instrument`, созданную в Build 001; - использовать транспортную модель `DzengiExchangeInfoSymbol` из Build 002; - не выполнять HTTP-запросы; - не заниматься кэшированием; - не подключаться к `ExchangeService`; - не менять production-поведение бота; - явно определить правила преобразования числовых значений в `Decimal`; - явно определить соответствие полей Dzengi полям внутренней модели `Instrument`; - отдельно классифицировать любые потенциальные изменения поведения. До написания кода Build 005 необходимо сначала проанализировать существующие модели и контракты, относящиеся к преобразованию `DzengiExchangeInfoSymbol` в `Instrument`.