421 lines
8.3 KiB
Markdown
421 lines
8.3 KiB
Markdown
# 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 останется полностью независимым от бизнес-логики. |