feat: add market data architecture and complete migration through build 039
This commit is contained in:
717
docs/migrations/build_029.md
Normal file
717
docs/migrations/build_029.md
Normal file
@@ -0,0 +1,717 @@
|
||||
# Build 029 — Dzengi Mapper и Quotes Handler
|
||||
|
||||
## Статус
|
||||
|
||||
**Завершён**
|
||||
|
||||
---
|
||||
|
||||
## 1. Цель Build 029
|
||||
|
||||
Цель Build 029 — реализовать преобразование специализированной модели REST-котировки Dzengi в каноническую модель `Quote` и создать обработчик полного цикла преобразования сырого REST-документа в проверенную внутреннюю модель котировки.
|
||||
|
||||
Build является частью поэтапной миграции подсистемы:
|
||||
|
||||
**Quotes Feed**
|
||||
|
||||
в новую архитектуру:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/
|
||||
```
|
||||
|
||||
На данном этапе реализованы:
|
||||
|
||||
- специализированный Dzengi quote mapper;
|
||||
- преобразование `DzengiTicker24hrResponse` в канонический `Quote`;
|
||||
- преобразование цен в `Decimal`;
|
||||
- преобразование биржевого timestamp в timezone-aware UTC `datetime`;
|
||||
- фиксация времени получения котировки;
|
||||
- специализированный `QuotesHandler`;
|
||||
- полная handler-цепочка обработки сырого REST-документа.
|
||||
|
||||
Подключение `Quotes Feed`, registry, `Acquisition Service`, `Quote Store`, `ExchangeService` facade и runtime-потребителей в данный Build не входит.
|
||||
|
||||
---
|
||||
|
||||
## 2. Место Build 029 в плане миграции 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 029 продолжает фундамент, созданный в Builds 027–028.
|
||||
|
||||
После его завершения сформирована цепочка:
|
||||
|
||||
```text
|
||||
raw Dzengi REST document
|
||||
↓
|
||||
schema validation
|
||||
↓
|
||||
Dzengi quote parser
|
||||
↓
|
||||
value validation
|
||||
↓
|
||||
DzengiTicker24hrResponse
|
||||
↓
|
||||
Dzengi quote mapper
|
||||
↓
|
||||
canonical Quote
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Исходное состояние перед Build 029
|
||||
|
||||
До начала Build 029 уже были реализованы:
|
||||
|
||||
### Build 027
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/models/quote.py
|
||||
src/market_data/acquisition/protocol.py
|
||||
```
|
||||
|
||||
Были определены:
|
||||
|
||||
- каноническая модель `Quote`;
|
||||
- контракт источника сырого quote-документа;
|
||||
- контракт обработчика quote-документа;
|
||||
- контракт готового `Quotes Feed`.
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
Были реализованы:
|
||||
|
||||
- модель REST-ответа `/api/v1/ticker/24hr`;
|
||||
- parser quote payload;
|
||||
- schema validation;
|
||||
- value validation;
|
||||
- специализированные ошибки Acquisition layer.
|
||||
|
||||
Отсутствовал слой, преобразующий проверенную Dzengi-specific модель в канонический `Quote`, а также единая точка оркестрации всей цепочки обработки сырого документа.
|
||||
|
||||
---
|
||||
|
||||
## 4. Изменённые и добавленные файлы
|
||||
|
||||
В рамках Build 029 изменены только два исходных файла:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/adapters/dzengi/mapper.py
|
||||
src/market_data/acquisition/handlers/quotes_handler.py
|
||||
```
|
||||
|
||||
Добавлены два специализированных файла тестов:
|
||||
|
||||
```text
|
||||
tests/unit/market_data/acquisition/adapters/dzengi/test_quote_mapper.py
|
||||
tests/unit/market_data/acquisition/handlers/test_quotes_handler.py
|
||||
```
|
||||
|
||||
Другие файлы в рамках фактически применённого Build 029 не изменялись.
|
||||
|
||||
---
|
||||
|
||||
## 5. Dzengi Quote Mapper
|
||||
|
||||
В файле:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/adapters/dzengi/mapper.py
|
||||
```
|
||||
|
||||
реализовано преобразование:
|
||||
|
||||
```text
|
||||
DzengiTicker24hrResponse
|
||||
↓
|
||||
Quote
|
||||
```
|
||||
|
||||
Mapper является архитектурной границей между:
|
||||
|
||||
```text
|
||||
exchange-specific adapter model
|
||||
```
|
||||
|
||||
и:
|
||||
|
||||
```text
|
||||
canonical Acquisition model
|
||||
```
|
||||
|
||||
Его ответственность:
|
||||
|
||||
- принять проверенную модель `DzengiTicker24hrResponse`;
|
||||
- преобразовать биржевые значения цен в канонический тип;
|
||||
- преобразовать биржевой timestamp;
|
||||
- определить источник данных;
|
||||
- зафиксировать время получения котировки;
|
||||
- создать канонический `Quote`.
|
||||
|
||||
Mapper не должен:
|
||||
|
||||
- выполнять REST-запрос;
|
||||
- разбирать сырой JSON payload;
|
||||
- выполнять schema validation сырого документа;
|
||||
- управлять store или cache;
|
||||
- обращаться к `ExchangeService`;
|
||||
- содержать UI-логику;
|
||||
- содержать execution-логику.
|
||||
|
||||
---
|
||||
|
||||
## 6. Преобразование модели Dzengi в канонический Quote
|
||||
|
||||
Исходная модель адаптера содержит данные, соответствующие REST-ответу Dzengi:
|
||||
|
||||
```text
|
||||
symbol
|
||||
lastPrice
|
||||
bidPrice
|
||||
askPrice
|
||||
closeTime
|
||||
```
|
||||
|
||||
После parsing и validation эти данные представлены специализированной моделью:
|
||||
|
||||
```text
|
||||
DzengiTicker24hrResponse
|
||||
```
|
||||
|
||||
Mapper преобразует её в:
|
||||
|
||||
```text
|
||||
Quote
|
||||
```
|
||||
|
||||
с канонической семантикой:
|
||||
|
||||
```text
|
||||
symbol
|
||||
last_price
|
||||
bid_price
|
||||
ask_price
|
||||
source_timestamp
|
||||
received_at
|
||||
source
|
||||
```
|
||||
|
||||
Таким образом, API-specific имена:
|
||||
|
||||
```text
|
||||
lastPrice
|
||||
bidPrice
|
||||
askPrice
|
||||
closeTime
|
||||
```
|
||||
|
||||
не выходят за пределы Dzengi adapter layer.
|
||||
|
||||
---
|
||||
|
||||
## 7. Использование Decimal для цен
|
||||
|
||||
Цены преобразуются в `Decimal`.
|
||||
|
||||
Целевая семантика:
|
||||
|
||||
```text
|
||||
last_price: Decimal
|
||||
bid_price: Decimal
|
||||
ask_price: Decimal
|
||||
```
|
||||
|
||||
Это решение исключает ненужную потерю точности при преобразовании рыночных цен через бинарный `float`.
|
||||
|
||||
Архитектурная цепочка:
|
||||
|
||||
```text
|
||||
Dzengi string price
|
||||
↓
|
||||
Decimal
|
||||
↓
|
||||
canonical Quote
|
||||
```
|
||||
|
||||
Например:
|
||||
|
||||
```text
|
||||
"64159.45"
|
||||
↓
|
||||
Decimal("64159.45")
|
||||
```
|
||||
|
||||
Mapper не должен сначала преобразовывать строку в `float`, а затем создавать `Decimal`, поскольку такой путь способен внести артефакты двоичного представления числа.
|
||||
|
||||
---
|
||||
|
||||
## 8. Преобразование биржевого timestamp
|
||||
|
||||
Поле Dzengi:
|
||||
|
||||
```text
|
||||
closeTime
|
||||
```
|
||||
|
||||
содержит Unix timestamp в миллисекундах.
|
||||
|
||||
Mapper преобразует его в timezone-aware UTC `datetime`.
|
||||
|
||||
Семантика преобразования:
|
||||
|
||||
```text
|
||||
closeTime milliseconds
|
||||
↓
|
||||
UTC datetime
|
||||
↓
|
||||
Quote.source_timestamp
|
||||
```
|
||||
|
||||
Использование timezone-aware значения необходимо для однозначного представления времени рыночного события и последующих операций:
|
||||
|
||||
- freshness calculation;
|
||||
- sequence validation;
|
||||
- event ordering;
|
||||
- диагностика задержек;
|
||||
- сопоставление данных из нескольких источников.
|
||||
|
||||
---
|
||||
|
||||
## 9. Время получения котировки
|
||||
|
||||
Помимо биржевого времени события, канонический `Quote` содержит время фактического получения данных платформой:
|
||||
|
||||
```text
|
||||
received_at
|
||||
```
|
||||
|
||||
Разделение двух временных характеристик принципиально:
|
||||
|
||||
```text
|
||||
source_timestamp
|
||||
```
|
||||
|
||||
означает время, указанное источником данных;
|
||||
|
||||
```text
|
||||
received_at
|
||||
```
|
||||
|
||||
означает время, когда котировка была преобразована во внутреннюю модель платформы.
|
||||
|
||||
Это создаёт фундамент для последующего определения:
|
||||
|
||||
- возраста котировки;
|
||||
- сетевой задержки;
|
||||
- freshness;
|
||||
- stale data;
|
||||
- задержки между биржей и локальной системой.
|
||||
|
||||
---
|
||||
|
||||
## 10. Источник котировки
|
||||
|
||||
Канонический `Quote` получает идентификатор источника:
|
||||
|
||||
```text
|
||||
dzengi
|
||||
```
|
||||
|
||||
Это позволяет внутренней модели не зависеть от конкретного adapter-класса, сохраняя при этом происхождение рыночных данных.
|
||||
|
||||
Целевая модель допускает дальнейшую работу с несколькими источниками:
|
||||
|
||||
```text
|
||||
Dzengi
|
||||
Binance
|
||||
Coinbase
|
||||
другие источники
|
||||
```
|
||||
|
||||
При этом приоритетным источником для торговых решений остаётся биржа исполнения.
|
||||
|
||||
---
|
||||
|
||||
## 11. Quotes Handler
|
||||
|
||||
В файле:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/handlers/quotes_handler.py
|
||||
```
|
||||
|
||||
реализован специализированный обработчик quote-документа.
|
||||
|
||||
Его ответственность — оркестрировать существующие специализированные стадии обработки:
|
||||
|
||||
```text
|
||||
raw document
|
||||
↓
|
||||
schema validation
|
||||
↓
|
||||
parser
|
||||
↓
|
||||
value validation
|
||||
↓
|
||||
mapper
|
||||
↓
|
||||
Quote
|
||||
```
|
||||
|
||||
Handler является единой точкой преобразования:
|
||||
|
||||
```text
|
||||
object → Quote
|
||||
```
|
||||
|
||||
Он не должен самостоятельно дублировать внутреннюю реализацию:
|
||||
|
||||
- schema validation;
|
||||
- parsing;
|
||||
- value validation;
|
||||
- mapping.
|
||||
|
||||
Вместо этого handler координирует специализированные компоненты.
|
||||
|
||||
---
|
||||
|
||||
## 12. Полная цепочка обработки
|
||||
|
||||
После Build 029 полный путь REST-документа выглядит следующим образом:
|
||||
|
||||
```text
|
||||
{
|
||||
"askPrice": "64159.55",
|
||||
"bidPrice": "64159.45",
|
||||
"closeTime": 1783887270312,
|
||||
"lastPrice": "64159.45",
|
||||
"symbol": "BTC/USD_LEVERAGE"
|
||||
}
|
||||
↓
|
||||
schema validation
|
||||
↓
|
||||
Dzengi REST quote parser
|
||||
↓
|
||||
DzengiTicker24hrResponse
|
||||
↓
|
||||
value validation
|
||||
↓
|
||||
Dzengi quote mapper
|
||||
↓
|
||||
Quote(
|
||||
symbol=...,
|
||||
last_price=...,
|
||||
bid_price=...,
|
||||
ask_price=...,
|
||||
source_timestamp=...,
|
||||
received_at=...,
|
||||
source=...
|
||||
)
|
||||
```
|
||||
|
||||
Таким образом, верхние слои платформы больше не обязаны знать формат ответа Dzengi.
|
||||
|
||||
---
|
||||
|
||||
## 13. Архитектурные решения Build 029
|
||||
|
||||
### 13.1. Mapper изолирует специфику Dzengi
|
||||
|
||||
Только adapter layer знает о:
|
||||
|
||||
```text
|
||||
DzengiTicker24hrResponse
|
||||
lastPrice
|
||||
bidPrice
|
||||
askPrice
|
||||
closeTime
|
||||
```
|
||||
|
||||
После mapping верхние слои работают исключительно с:
|
||||
|
||||
```text
|
||||
Quote
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 13.2. Handler не зависит от ExchangeService
|
||||
|
||||
Новый `QuotesHandler` не использует:
|
||||
|
||||
```text
|
||||
src.integrations.exchange.service.ExchangeService
|
||||
```
|
||||
|
||||
Направление зависимостей остаётся правильным:
|
||||
|
||||
```text
|
||||
external Dzengi payload
|
||||
↓
|
||||
Acquisition adapter
|
||||
↓
|
||||
Acquisition handler
|
||||
↓
|
||||
canonical Quote
|
||||
```
|
||||
|
||||
Обратной зависимости новой подсистемы от legacy integration layer нет.
|
||||
|
||||
---
|
||||
|
||||
### 13.3. Handler не является Feed
|
||||
|
||||
`QuotesHandler` отвечает только за преобразование документа:
|
||||
|
||||
```text
|
||||
object → Quote
|
||||
```
|
||||
|
||||
Он не отвечает за получение документа от биржи.
|
||||
|
||||
Получение данных будет ответственностью:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/feeds/quotes_feed.py
|
||||
```
|
||||
|
||||
на следующем этапе миграции.
|
||||
|
||||
---
|
||||
|
||||
### 13.4. Handler не является Store
|
||||
|
||||
`QuotesHandler` не сохраняет котировки.
|
||||
|
||||
Хранение будет реализовано отдельно:
|
||||
|
||||
```text
|
||||
Build 032 — Канонический Quote Store
|
||||
```
|
||||
|
||||
Такое разделение предотвращает смешивание:
|
||||
|
||||
```text
|
||||
acquisition
|
||||
processing
|
||||
storage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 13.5. Build не изменяет production runtime
|
||||
|
||||
В Build 029 не изменены:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/service.py
|
||||
src/integrations/exchange/market_cache.py
|
||||
src/integrations/exchange/market_stream.py
|
||||
src/integrations/exchange/market_data_runner.py
|
||||
```
|
||||
|
||||
Не переведены:
|
||||
|
||||
```text
|
||||
UI consumers
|
||||
execution consumers
|
||||
strategy consumers
|
||||
diagnostics consumers
|
||||
```
|
||||
|
||||
Работающий бот продолжает использовать прежний runtime-контур.
|
||||
|
||||
---
|
||||
|
||||
## 14. Что намеренно не реализовано
|
||||
|
||||
В Build 029 не входят:
|
||||
|
||||
```text
|
||||
Quotes Feed
|
||||
регистрация Quotes Feed
|
||||
подключение к Acquisition Service
|
||||
подключение нового REST Quotes Feed к ExchangeService facade
|
||||
Quote Store
|
||||
перенос MarketPriceCache на Quote Store
|
||||
WebSocket quote parsing
|
||||
WebSocket quote adapter
|
||||
перевод market runtime
|
||||
перевод read-only потребителей
|
||||
перевод UI-потребителей
|
||||
перевод execution-потребителей
|
||||
удаление TickerPrice
|
||||
удаление market snapshot dict layer
|
||||
удаление legacy quote parsing
|
||||
удаление MarketPriceCache
|
||||
```
|
||||
|
||||
Каждая из этих задач выполняется только в соответствующем последующем Build.
|
||||
|
||||
---
|
||||
|
||||
## 15. Проверки
|
||||
|
||||
Выполнена синтаксическая проверка:
|
||||
|
||||
```bash
|
||||
python -m py_compile \
|
||||
src/market_data/acquisition/adapters/dzengi/mapper.py \
|
||||
src/market_data/acquisition/handlers/quotes_handler.py \
|
||||
tests/unit/market_data/acquisition/adapters/dzengi/test_quote_mapper.py \
|
||||
tests/unit/market_data/acquisition/handlers/test_quotes_handler.py
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
Успешно.
|
||||
```
|
||||
|
||||
Выполнены специализированные тесты Build 029:
|
||||
|
||||
```bash
|
||||
python -m pytest \
|
||||
tests/unit/market_data/acquisition/adapters/dzengi/test_quote_mapper.py \
|
||||
tests/unit/market_data/acquisition/handlers/test_quotes_handler.py \
|
||||
-q
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
12 passed in 0.03s
|
||||
```
|
||||
|
||||
Выполнена полная регрессия проекта:
|
||||
|
||||
```bash
|
||||
python -m pytest -q
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
457 passed in 0.26s
|
||||
```
|
||||
|
||||
Регрессий не обнаружено.
|
||||
|
||||
Количество тестов увеличилось:
|
||||
|
||||
```text
|
||||
После Build 028: 445 passed
|
||||
После Build 029: 457 passed
|
||||
```
|
||||
|
||||
Добавлено:
|
||||
|
||||
```text
|
||||
12 специализированных тестов
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. Критерии завершения Build 029
|
||||
|
||||
Build 029 считается завершённым, поскольку выполнены все необходимые условия:
|
||||
|
||||
- [x] реализован специализированный Dzengi quote mapper;
|
||||
- [x] `DzengiTicker24hrResponse` преобразуется в канонический `Quote`;
|
||||
- [x] API-specific имена не выходят за пределы adapter layer;
|
||||
- [x] цены преобразуются в `Decimal`;
|
||||
- [x] не используется промежуточное преобразование цен через `float`;
|
||||
- [x] `closeTime` преобразуется в timezone-aware UTC `datetime`;
|
||||
- [x] фиксируется `received_at`;
|
||||
- [x] сохраняется источник котировки;
|
||||
- [x] реализован специализированный `QuotesHandler`;
|
||||
- [x] handler оркестрирует полный цикл обработки сырого документа;
|
||||
- [x] handler не дублирует ответственность parser;
|
||||
- [x] handler не дублирует ответственность validation;
|
||||
- [x] handler не дублирует ответственность mapper;
|
||||
- [x] новая реализация не зависит от `ExchangeService`;
|
||||
- [x] новая реализация не зависит от `MarketPriceCache`;
|
||||
- [x] production runtime не изменён;
|
||||
- [x] специализированные тесты проходят;
|
||||
- [x] полная регрессия проходит.
|
||||
|
||||
---
|
||||
|
||||
## 17. Итог
|
||||
|
||||
В результате Build 029 завершён слой преобразования REST-котировки Dzengi во внутреннюю каноническую модель платформы:
|
||||
|
||||
```text
|
||||
Dzengi REST payload
|
||||
↓
|
||||
schema validation
|
||||
↓
|
||||
parser
|
||||
↓
|
||||
value validation
|
||||
↓
|
||||
DzengiTicker24hrResponse
|
||||
↓
|
||||
mapper
|
||||
↓
|
||||
canonical Quote
|
||||
```
|
||||
|
||||
Также создан единый специализированный обработчик:
|
||||
|
||||
```text
|
||||
QuotesHandler
|
||||
```
|
||||
|
||||
который предоставляет операцию:
|
||||
|
||||
```text
|
||||
raw document → canonical Quote
|
||||
```
|
||||
|
||||
При этом сохранены ключевые архитектурные свойства миграции:
|
||||
|
||||
- новая реализация развивается параллельно legacy-контуру;
|
||||
- работающий бот не сломан;
|
||||
- `ExchangeService` не изменён;
|
||||
- `MarketPriceCache` не изменён;
|
||||
- runtime-потребители не изменены;
|
||||
- Dzengi-specific формат изолирован внутри adapter layer;
|
||||
- верхние слои получают каноническую модель `Quote`;
|
||||
- mapping и orchestration разделены по ответственности;
|
||||
- сохранена возможность безопасного поэтапного переключения системы.
|
||||
|
||||
**Build 029 завершён.**
|
||||
|
||||
Следующий этап:
|
||||
|
||||
```text
|
||||
Build 030 — Quotes Feed и регистрация в Acquisition Service
|
||||
```
|
||||
Reference in New Issue
Block a user