feat: add market data architecture and complete migration through build 039
This commit is contained in:
591
docs/migrations/build_028.md
Normal file
591
docs/migrations/build_028.md
Normal file
@@ -0,0 +1,591 @@
|
||||
# Build 028 — Dzengi REST Quote Models, Parser и Validation
|
||||
|
||||
## Статус
|
||||
|
||||
**Завершён**
|
||||
|
||||
---
|
||||
|
||||
## 1. Цель Build 028
|
||||
|
||||
Цель Build 028 — реализовать специализированный слой приёма, разбора и первичной проверки REST-ответа Dzengi для текущей рыночной котировки инструмента, не изменяя существующее поведение работающего бота и не подключая новую реализацию к production runtime до следующих этапов миграции.
|
||||
|
||||
Build является частью поэтапной миграции подсистемы:
|
||||
|
||||
**Quotes Feed**
|
||||
|
||||
в новую архитектуру:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/
|
||||
```
|
||||
|
||||
На данном этапе реализованы:
|
||||
|
||||
- модель сырого REST-ответа Dzengi;
|
||||
- parser REST-ответа `/api/v1/ticker/24hr`;
|
||||
- проверка структуры входящего payload;
|
||||
- проверка допустимости значений котировки;
|
||||
- специализированные ошибки обработки quote payload.
|
||||
|
||||
Подключение mapper, handler, feed, service, store и перевод runtime-потребителей в данный Build не входят.
|
||||
|
||||
---
|
||||
|
||||
## 2. Место Build 028 в плане миграции Quotes Feed
|
||||
|
||||
Утверждённая последовательность:
|
||||
|
||||
```text
|
||||
Build 026 — Аудит текущего контура Quotes Feed
|
||||
Build 027 — Каноническая модель Quote и специализированные контракты
|
||||
Build 028 — Dzengi REST quote models, parser и validation
|
||||
Build 029 — Dzengi mapper и Quotes Handler
|
||||
Build 030 — Quotes Feed и регистрация в Acquisition Service
|
||||
Build 031 — Подключение нового REST Quotes Feed под legacy ExchangeService facade
|
||||
Build 032 — Канонический Quote Store
|
||||
Build 033 — Перенос MarketPriceCache на Quote Store
|
||||
Build 034 — Dzengi WebSocket quote parsing и адаптер
|
||||
Build 035 — Перевод market runtime на Quotes Feed
|
||||
Build 036 — Перевод read-only и UI-потребителей
|
||||
Build 037 — Перевод execution-потребителей
|
||||
Build 038 — Удаление legacy TickerPrice и market snapshot dict layer
|
||||
Build 039 — Удаление legacy quote parsing и MarketPriceCache
|
||||
Build 040 — Финальная архитектурная проверка Quotes Feed
|
||||
```
|
||||
|
||||
Build 028 продолжает фундамент, созданный в Build 027.
|
||||
|
||||
Целевая цепочка после завершения следующих этапов:
|
||||
|
||||
```text
|
||||
Dzengi REST /api/v1/ticker/24hr
|
||||
↓
|
||||
adapters/dzengi/rest.py
|
||||
↓
|
||||
adapters/dzengi/parser.py
|
||||
↓
|
||||
adapters/dzengi/models.py
|
||||
↓
|
||||
validation/schema.py
|
||||
↓
|
||||
validation/values.py
|
||||
↓
|
||||
adapters/dzengi/mapper.py
|
||||
↓
|
||||
handlers/quotes_handler.py
|
||||
↓
|
||||
feeds/quotes_feed.py
|
||||
↓
|
||||
acquisition/service.py
|
||||
↓
|
||||
Quote Store
|
||||
↓
|
||||
потребители платформы
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Исходные данные
|
||||
|
||||
Для проектирования реализации использован реальный успешный ответ Dzengi:
|
||||
|
||||
```json
|
||||
{
|
||||
"askPrice": "64159.55",
|
||||
"bidPrice": "64159.45",
|
||||
"closeTime": 1783887270312,
|
||||
"highPrice": "64261.45",
|
||||
"lastPrice": "64159.45",
|
||||
"lastQty": "5.0",
|
||||
"lowPrice": "63590.7",
|
||||
"openPrice": "63785.75",
|
||||
"openTime": 1783814400000,
|
||||
"prevClosePrice": "63785.75",
|
||||
"priceChange": "368.85",
|
||||
"priceChangePercent": "0.57822",
|
||||
"quoteVolume": "616402.92146",
|
||||
"symbol": "BTC/USD_LEVERAGE",
|
||||
"volume": "9.6002",
|
||||
"weightedAvgPrice": "64159.50"
|
||||
}
|
||||
```
|
||||
|
||||
Для базовой модели текущей котировки используются поля:
|
||||
|
||||
```text
|
||||
symbol
|
||||
lastPrice
|
||||
bidPrice
|
||||
askPrice
|
||||
closeTime
|
||||
```
|
||||
|
||||
Остальные поля ответа `/api/v1/ticker/24hr` относятся к расширенной 24-часовой статистике рынка и не включаются в базовую модель `Quote`.
|
||||
|
||||
Это сохраняет правильное разделение ответственностей между:
|
||||
|
||||
- текущей котировкой;
|
||||
- рыночной статистикой;
|
||||
- OHLCV;
|
||||
- trades;
|
||||
- order book;
|
||||
- другими специализированными типами рыночных данных.
|
||||
|
||||
---
|
||||
|
||||
## 4. Реализованные компоненты
|
||||
|
||||
В рамках Build 028 изменены следующие файлы:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/adapters/dzengi/models.py
|
||||
src/market_data/acquisition/adapters/dzengi/parser.py
|
||||
src/market_data/acquisition/validation/schema.py
|
||||
src/market_data/acquisition/validation/values.py
|
||||
src/market_data/acquisition/exceptions.py
|
||||
```
|
||||
|
||||
Добавлены специализированные тесты:
|
||||
|
||||
```text
|
||||
tests/unit/market_data/acquisition/adapters/dzengi/test_quote_parser.py
|
||||
tests/unit/market_data/acquisition/validation/test_quote_schema.py
|
||||
tests/unit/market_data/acquisition/validation/test_quote_values.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Dzengi REST Quote Model
|
||||
|
||||
В файле:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/adapters/dzengi/models.py
|
||||
```
|
||||
|
||||
реализована модель сырой котировки Dzengi.
|
||||
|
||||
Её ответственность:
|
||||
|
||||
- представить уже разобранные поля ответа Dzengi;
|
||||
- сохранить биржевую семантику полей;
|
||||
- не зависеть от legacy-моделей `TickerPrice` и `MarketPriceSnapshot`;
|
||||
- не выполнять бизнес-интерпретацию;
|
||||
- не выполнять преобразование во внутреннюю каноническую модель `Quote`.
|
||||
|
||||
Архитектурная граница:
|
||||
|
||||
```text
|
||||
Dzengi API payload
|
||||
↓
|
||||
Dzengi REST quote model
|
||||
↓
|
||||
mapper
|
||||
↓
|
||||
canonical Quote
|
||||
```
|
||||
|
||||
Модель адаптера является специфичной для Dzengi и не должна использоваться напрямую верхними слоями платформы.
|
||||
|
||||
---
|
||||
|
||||
## 6. REST Quote Parser
|
||||
|
||||
В файле:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/adapters/dzengi/parser.py
|
||||
```
|
||||
|
||||
реализован специализированный parser REST-котировки.
|
||||
|
||||
Его ответственность:
|
||||
|
||||
1. принять необработанный ответ API;
|
||||
2. определить фактический quote payload;
|
||||
3. поддержать прямую структуру ответа;
|
||||
4. поддержать wrapped payload;
|
||||
5. проверить структуру через schema validation;
|
||||
6. извлечь необходимые поля;
|
||||
7. проверить значения через value validation;
|
||||
8. вернуть специализированную Dzengi quote model.
|
||||
|
||||
Поддерживаемые формы payload:
|
||||
|
||||
```text
|
||||
Прямой payload
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"symbol": "BTC/USD_LEVERAGE",
|
||||
"lastPrice": "64159.45",
|
||||
"bidPrice": "64159.45",
|
||||
"askPrice": "64159.55",
|
||||
"closeTime": 1783887270312
|
||||
}
|
||||
```
|
||||
|
||||
и wrapped payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"payload": {
|
||||
"symbol": "BTC/USD_LEVERAGE",
|
||||
"lastPrice": "64159.45",
|
||||
"bidPrice": "64159.45",
|
||||
"askPrice": "64159.55",
|
||||
"closeTime": 1783887270312
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Parser не создаёт канонический `Quote`. Это ответственность mapper, реализуемого в Build 029.
|
||||
|
||||
---
|
||||
|
||||
## 7. Schema Validation
|
||||
|
||||
В файле:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/validation/schema.py
|
||||
```
|
||||
|
||||
реализована проверка структуры quote payload.
|
||||
|
||||
Проверяются обязательные поля:
|
||||
|
||||
```text
|
||||
symbol
|
||||
lastPrice
|
||||
bidPrice
|
||||
askPrice
|
||||
closeTime
|
||||
```
|
||||
|
||||
Schema validation отвечает только на вопрос:
|
||||
|
||||
> Имеет ли входящее сообщение необходимую структуру для дальнейшей обработки?
|
||||
|
||||
Она не должна:
|
||||
|
||||
- преобразовывать значения;
|
||||
- вычислять midpoint;
|
||||
- определять freshness;
|
||||
- создавать `Quote`;
|
||||
- обращаться к сети;
|
||||
- обращаться к store;
|
||||
- зависеть от `ExchangeService`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Value Validation
|
||||
|
||||
В файле:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/validation/values.py
|
||||
```
|
||||
|
||||
реализована проверка допустимости значений REST-котировки.
|
||||
|
||||
Контролируются следующие инварианты:
|
||||
|
||||
```text
|
||||
symbol != empty
|
||||
last_price > 0
|
||||
bid_price > 0
|
||||
ask_price > 0
|
||||
close_time >= 0
|
||||
bid_price <= ask_price
|
||||
```
|
||||
|
||||
Проверка:
|
||||
|
||||
```text
|
||||
bid_price <= ask_price
|
||||
```
|
||||
|
||||
является важным базовым инвариантом котировки.
|
||||
|
||||
Payload, в котором:
|
||||
|
||||
```text
|
||||
bid_price > ask_price
|
||||
```
|
||||
|
||||
не должен бесконтрольно попадать в канонический слой платформы.
|
||||
|
||||
---
|
||||
|
||||
## 9. Исключения
|
||||
|
||||
В файле:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/exceptions.py
|
||||
```
|
||||
|
||||
используются специализированные исключения Acquisition layer для ошибок обработки рыночных данных.
|
||||
|
||||
Ошибки quote parsing и validation не должны зависеть от:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/exceptions.py
|
||||
```
|
||||
|
||||
Это необходимо для соблюдения направления зависимостей:
|
||||
|
||||
```text
|
||||
market_data/acquisition
|
||||
X
|
||||
integrations/exchange legacy layer
|
||||
```
|
||||
|
||||
Новая подсистема Acquisition не должна архитектурно зависеть от legacy `ExchangeService`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Архитектурные решения Build 028
|
||||
|
||||
### 10.1. Каноническая модель не зависит от формата Dzengi
|
||||
|
||||
Поля API:
|
||||
|
||||
```text
|
||||
lastPrice
|
||||
bidPrice
|
||||
askPrice
|
||||
closeTime
|
||||
```
|
||||
|
||||
существуют только внутри Dzengi adapter layer.
|
||||
|
||||
Во внутренних слоях платформы используются канонические имена:
|
||||
|
||||
```text
|
||||
last_price
|
||||
bid_price
|
||||
ask_price
|
||||
source_timestamp_ms
|
||||
```
|
||||
|
||||
Преобразование между ними является ответственностью mapper.
|
||||
|
||||
---
|
||||
|
||||
### 10.2. Parser не выполняет mapping
|
||||
|
||||
Разделение сохраняется строго:
|
||||
|
||||
```text
|
||||
parser
|
||||
↓
|
||||
разбирает внешний payload
|
||||
|
||||
validation
|
||||
↓
|
||||
проверяет структуру и значения
|
||||
|
||||
mapper
|
||||
↓
|
||||
преобразует adapter model в canonical model
|
||||
```
|
||||
|
||||
Это предотвращает смешивание:
|
||||
|
||||
- API-specific parsing;
|
||||
- validation;
|
||||
- domain mapping.
|
||||
|
||||
---
|
||||
|
||||
### 10.3. REST quote не зависит от legacy TickerPrice
|
||||
|
||||
Новая цепочка не использует:
|
||||
|
||||
```text
|
||||
src.integrations.exchange.models.TickerPrice
|
||||
```
|
||||
|
||||
`TickerPrice` остаётся временной legacy-моделью и будет удалён только после перевода всех потребителей согласно плану миграции.
|
||||
|
||||
---
|
||||
|
||||
### 10.4. REST quote не зависит от MarketPriceCache
|
||||
|
||||
Build 028 не изменяет:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/market_cache.py
|
||||
```
|
||||
|
||||
и не записывает данные в:
|
||||
|
||||
```text
|
||||
MarketPriceCache
|
||||
```
|
||||
|
||||
Миграция хранения выполняется отдельно:
|
||||
|
||||
```text
|
||||
Build 032 — Канонический Quote Store
|
||||
Build 033 — Перенос MarketPriceCache на Quote Store
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10.5. Runtime-поведение бота не изменено
|
||||
|
||||
На этапе Build 028:
|
||||
|
||||
- новый parser не подключён к production runtime;
|
||||
- `ExchangeService` продолжает работать по прежнему интерфейсу;
|
||||
- `MarketPriceCache` не изменён;
|
||||
- `MarketDataRunner` не изменён;
|
||||
- execution-потребители не изменены;
|
||||
- UI-потребители не изменены.
|
||||
|
||||
Таким образом, Build 028 является безопасным additive-этапом миграции.
|
||||
|
||||
---
|
||||
|
||||
## 11. Что намеренно не реализовано
|
||||
|
||||
В Build 028 не входят:
|
||||
|
||||
```text
|
||||
Dzengi mapper
|
||||
Quotes Handler
|
||||
Quotes Feed
|
||||
регистрация Quotes Feed
|
||||
подключение к Acquisition Service
|
||||
подключение к ExchangeService facade
|
||||
Quote Store
|
||||
перенос MarketPriceCache
|
||||
WebSocket quote parser
|
||||
перевод MarketDataRunner
|
||||
перевод UI-потребителей
|
||||
перевод execution-потребителей
|
||||
удаление TickerPrice
|
||||
удаление market snapshot dict layer
|
||||
удаление MarketPriceCache
|
||||
```
|
||||
|
||||
Эти изменения выполняются только в соответствующих последующих Build.
|
||||
|
||||
---
|
||||
|
||||
## 12. Проверки
|
||||
|
||||
Выполнена синтаксическая проверка:
|
||||
|
||||
```bash
|
||||
python -m py_compile \
|
||||
src/market_data/acquisition/adapters/dzengi/models.py \
|
||||
src/market_data/acquisition/adapters/dzengi/parser.py \
|
||||
src/market_data/acquisition/validation/schema.py \
|
||||
src/market_data/acquisition/validation/values.py \
|
||||
src/market_data/acquisition/exceptions.py \
|
||||
tests/unit/market_data/acquisition/adapters/dzengi/test_quote_parser.py \
|
||||
tests/unit/market_data/acquisition/validation/test_quote_schema.py \
|
||||
tests/unit/market_data/acquisition/validation/test_quote_values.py
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
Успешно.
|
||||
```
|
||||
|
||||
Выполнены специализированные тесты Build 028:
|
||||
|
||||
```bash
|
||||
python -m pytest \
|
||||
tests/unit/market_data/acquisition/adapters/dzengi/test_quote_parser.py \
|
||||
tests/unit/market_data/acquisition/validation/test_quote_schema.py \
|
||||
tests/unit/market_data/acquisition/validation/test_quote_values.py \
|
||||
-q
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
22 passed in 0.02s
|
||||
```
|
||||
|
||||
Выполнена полная регрессия проекта:
|
||||
|
||||
```bash
|
||||
python -m pytest -q
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
445 passed in 0.27s
|
||||
```
|
||||
|
||||
Регрессий не обнаружено.
|
||||
|
||||
---
|
||||
|
||||
## 13. Критерии завершения Build 028
|
||||
|
||||
Build 028 считается завершённым, поскольку выполнены все необходимые условия:
|
||||
|
||||
- [x] получен реальный успешный ответ `/api/v1/ticker/24hr`;
|
||||
- [x] определён минимальный набор полей текущей котировки;
|
||||
- [x] реализована специализированная Dzengi REST quote model;
|
||||
- [x] реализован REST quote parser;
|
||||
- [x] поддержан прямой payload;
|
||||
- [x] поддержан wrapped payload;
|
||||
- [x] реализована schema validation;
|
||||
- [x] реализована value validation;
|
||||
- [x] проверяются положительные цены;
|
||||
- [x] проверяется временная метка;
|
||||
- [x] проверяется инвариант `bid_price <= ask_price`;
|
||||
- [x] новая реализация не зависит от legacy `TickerPrice`;
|
||||
- [x] новая реализация не зависит от `MarketPriceCache`;
|
||||
- [x] production runtime не изменён;
|
||||
- [x] специализированные тесты проходят;
|
||||
- [x] полная регрессия проходит.
|
||||
|
||||
---
|
||||
|
||||
## 14. Итог
|
||||
|
||||
В результате Build 028 создан специализированный входной контур для REST-котировок Dzengi:
|
||||
|
||||
```text
|
||||
Dzengi /api/v1/ticker/24hr
|
||||
↓
|
||||
raw payload
|
||||
↓
|
||||
schema validation
|
||||
↓
|
||||
Dzengi quote parser
|
||||
↓
|
||||
value validation
|
||||
↓
|
||||
Dzengi REST quote model
|
||||
```
|
||||
|
||||
При этом сохранены ключевые архитектурные свойства миграции:
|
||||
|
||||
- новая реализация добавлена параллельно legacy-контуру;
|
||||
- работающий бот не сломан;
|
||||
- публичное поведение `ExchangeService` не изменено;
|
||||
- отсутствует зависимость новой Acquisition subsystem от legacy quote models;
|
||||
- parsing, validation и будущий mapping разделены по ответственности;
|
||||
- сохранена возможность безопасного поэтапного переключения потребителей.
|
||||
|
||||
**Build 028 завершён.**
|
||||
|
||||
Следующий этап:
|
||||
|
||||
```text
|
||||
Build 029 — Dzengi mapper и Quotes Handler
|
||||
```
|
||||
Reference in New Issue
Block a user