Files
dzentra_bot/docs/migrations/build_004.md

775 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 001003 стало безопасно реализовать 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`.