build 039: complete Quotes Feed migration foundation

This commit is contained in:
2026-07-14 09:58:16 +03:00
parent 26deb861bc
commit 7b62873832
443 changed files with 80452 additions and 1335 deletions

View 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 027028.
После его завершения сформирована цепочка:
```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
```