717 lines
19 KiB
Markdown
717 lines
19 KiB
Markdown
# 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
|
||
``` |