feat: add market data architecture and complete migration through build 039
This commit is contained in:
775
docs/migrations/build_004.md
Normal file
775
docs/migrations/build_004.md
Normal file
@@ -0,0 +1,775 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user