# 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`.