# Build 002 — Raw Models Dzengi exchangeInfo

**Проект:** Dzentra  
**Подсистема:** Market Data Acquisition  
**Миграция:** Instrument Reference Data  
**Статус:** ✅ Завершён  
**Дата:** 2026-07-10

---

# Цель Build

Создать транспортные модели (Raw Models), полностью описывающие ответ REST API Dzengi `exchangeInfo`.

На данном этапе запрещалось:

- изменять ExchangeService;
- изменять ExchangeSymbol;
- изменять SymbolValidationResult;
- изменять runtime;
- изменять Telegram UI;
- выполнять parser JSON;
- выполнять mapper в предметную модель Instrument.

Build создаёт исключительно транспортный контракт между REST API и будущим parser.

---

# Причина выполнения Build

После Build 001 уже существует независимая предметная модель:

```text
Instrument
```

Следующим архитектурным слоем является транспортная модель адаптера.

До начала Build 002 существующая реализация выглядела следующим образом:

```text
REST
    ↓
dict
    ↓
ExchangeService
    ↓
ExchangeSymbol
```

Parser и mapper были объединены внутри `ExchangeService`.

Это нарушало принцип разделения ответственности.

После Build 002 появилась отдельная транспортная модель:

```text
REST
    ↓
Raw Models
```

которая станет входом для parser в следующем Build.

---

# Проанализированные материалы

Перед реализацией были полностью проанализированы:

```text
app/tools/dzengi_probe/runtime_samples/rest/exchangeInfo/all.json
docs/market_intelligence/information/dzengi_openapi.json
```

Также были повторно использованы результаты анализа:

```text
app/src/integrations/exchange/service.py
app/src/integrations/exchange/models.py
```

и результаты grep по использованию ExchangeSymbol.

---

# Основные выводы анализа

Подтверждено:

реальный ответ API значительно богаче текущей модели ExchangeSymbol.

В ответе присутствуют:

- symbol
- name
- status
- assetType
- baseAsset
- baseAssetPrecision
- quoteAsset
- quoteAssetId
- quotePrecision
- orderTypes
- marketModes
- marketType
- country
- sector
- industry
- tradingHours
- tickSize
- tickValue
- tradingFee
- exchangeFee
- longRate
- shortRate
- swapChargeInterval
- minSLGap
- maxSLGap
- minTPGap
- maxTPGap
- filters
- rateLimits
- exchangeFilters

Часть этих данных ранее полностью терялась.

---

# Принятые архитектурные решения

## 1. Raw Models полностью отделены от предметной модели

Созданы транспортные модели.

Они:

- ничего не вычисляют;
- ничего не нормализуют;
- ничего не валидируют.

Они только описывают транспортный контракт.

---

## 2. Поддержка двух форматов ответа API

Поддерживаются оба варианта:

### Wrapped

```text
status
correlationId
payload
```

и

### Unwrapped

```text
timezone
serverTime
symbols
```

Parser следующего Build сможет привести оба формата к единому контракту.

Классификация:

**Улучшение надёжности.**

---

## 3. Decimal сознательно не используется

Raw Models сохраняют транспортный тип.

Например:

```json
"stepSize": "0.001"
```

остаётся строкой.

Преобразование в Decimal является обязанностью mapper.

---

## 4. Filters представлены отдельной иерархией

Созданы:

```text
DzengiLotSizeFilter
DzengiMinNotionalFilter
DzengiUnknownFilter
```

Неизвестные фильтры не приводят к ошибке.

Они сохраняются для дальнейшего анализа.

Классификация:

**Улучшение надёжности.**

---

## 5. Все коллекции являются immutable

Используются:

```python
tuple
```

вместо

```python
list
```

Причина:

Raw Models являются снимком транспортного ответа.

Их нельзя изменять после создания.

---

## 6. Все Raw Models являются immutable

Все dataclass объявлены как:

```python
@dataclass(frozen=True, slots=True)
```

Это гарантирует неизменяемость транспортного контракта.

---

# Созданные файлы

Создан:

```text
app/src/market_data/acquisition/adapters/dzengi/models.py
```

Создан:

```text
app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py
```

Другие production-файлы не изменялись.

---

# Проверки

Выполнены все проверки Build.

---

## Unit Test

Статус:

✅ Passed

```
6 passed
```

---

## Импорт полной транспортной модели

Проверено:

- импортируются все классы;
- создаётся полная иерархия объектов;
- вложенные dataclass работают корректно.

Статус:

✅ Passed

---

## Компиляция

Проверено:

```text
python -m py_compile
```

Статус:

✅ Passed

---

## Проверка отсутствия использования в production

Подтверждено:

Raw Models используются только:

```text
src/market_data/acquisition/adapters/dzengi/models.py

tests/unit/market_data/acquisition/adapters/dzengi/test_models.py
```

ExchangeService их не использует.

Parser их ещё не использует.

Mapper их ещё не использует.

Статус:

✅ Passed

---

## Полный набор тестов

Выполнено:

```text
python -m pytest -q
```

Результат:

```text
11 passed
```

Статус:

✅ Passed

---

# Архитектурный результат

После Build 002 структура подсистемы стала выглядеть следующим образом:

```text
Dzengi REST API
        │
        ▼
Raw Models
```

Следующие слои пока отсутствуют:

```text
Parser
Mapper
Instrument
Handler
Feed
Service
```

Именно это соответствует утверждённому плану миграции.

---

# Обратная совместимость

Полностью сохранена.

Не изменены:

- ExchangeService
- ExchangeSymbol
- SymbolValidationResult
- validate_symbol()
- normalize_symbol()
- symbol_candidates()
- Runtime
- Telegram UI

Новые Raw Models пока не подключены к работающему боту.

---

# Итог Build

Build 002 завершён успешно.

Получен полноценный транспортный контракт REST API Dzengi.

Создан фундамент для следующих этапов миграции.

Работающий бот не изменил своего поведения.

---

# Следующий Build

## Build 003 — Структурная валидация exchangeInfo

Следующий этап реализует parser, который будет преобразовывать сырой JSON REST API в созданные транспортные модели.

На Build 003 по-прежнему:

- ExchangeService изменяться не будет;
- предметная модель Instrument использоваться не будет;
- parser останется полностью независимым от бизнес-логики.