feat: add market data architecture and complete migration through build 039
This commit is contained in:
421
docs/migrations/build_002.md
Normal file
421
docs/migrations/build_002.md
Normal file
@@ -0,0 +1,421 @@
|
||||
# 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 останется полностью независимым от бизнес-логики.
|
||||
Reference in New Issue
Block a user