19 KiB
Build 029 — Dzengi Mapper и Quotes Handler
Статус
Завершён
1. Цель Build 029
Цель Build 029 — реализовать преобразование специализированной модели REST-котировки Dzengi в каноническую модель Quote и создать обработчик полного цикла преобразования сырого REST-документа в проверенную внутреннюю модель котировки.
Build является частью поэтапной миграции подсистемы:
Quotes Feed
в новую архитектуру:
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
Утверждённая последовательность:
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.
После его завершения сформирована цепочка:
raw Dzengi REST document
↓
schema validation
↓
Dzengi quote parser
↓
value validation
↓
DzengiTicker24hrResponse
↓
Dzengi quote mapper
↓
canonical Quote
3. Исходное состояние перед Build 029
До начала Build 029 уже были реализованы:
Build 027
src/market_data/acquisition/models/quote.py
src/market_data/acquisition/protocol.py
Были определены:
- каноническая модель
Quote; - контракт источника сырого quote-документа;
- контракт обработчика quote-документа;
- контракт готового
Quotes Feed.
Build 028
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 изменены только два исходных файла:
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
Другие файлы в рамках фактически применённого Build 029 не изменялись.
5. Dzengi Quote Mapper
В файле:
src/market_data/acquisition/adapters/dzengi/mapper.py
реализовано преобразование:
DzengiTicker24hrResponse
↓
Quote
Mapper является архитектурной границей между:
exchange-specific adapter model
и:
canonical Acquisition model
Его ответственность:
- принять проверенную модель
DzengiTicker24hrResponse; - преобразовать биржевые значения цен в канонический тип;
- преобразовать биржевой timestamp;
- определить источник данных;
- зафиксировать время получения котировки;
- создать канонический
Quote.
Mapper не должен:
- выполнять REST-запрос;
- разбирать сырой JSON payload;
- выполнять schema validation сырого документа;
- управлять store или cache;
- обращаться к
ExchangeService; - содержать UI-логику;
- содержать execution-логику.
6. Преобразование модели Dzengi в канонический Quote
Исходная модель адаптера содержит данные, соответствующие REST-ответу Dzengi:
symbol
lastPrice
bidPrice
askPrice
closeTime
После parsing и validation эти данные представлены специализированной моделью:
DzengiTicker24hrResponse
Mapper преобразует её в:
Quote
с канонической семантикой:
symbol
last_price
bid_price
ask_price
source_timestamp
received_at
source
Таким образом, API-specific имена:
lastPrice
bidPrice
askPrice
closeTime
не выходят за пределы Dzengi adapter layer.
7. Использование Decimal для цен
Цены преобразуются в Decimal.
Целевая семантика:
last_price: Decimal
bid_price: Decimal
ask_price: Decimal
Это решение исключает ненужную потерю точности при преобразовании рыночных цен через бинарный float.
Архитектурная цепочка:
Dzengi string price
↓
Decimal
↓
canonical Quote
Например:
"64159.45"
↓
Decimal("64159.45")
Mapper не должен сначала преобразовывать строку в float, а затем создавать Decimal, поскольку такой путь способен внести артефакты двоичного представления числа.
8. Преобразование биржевого timestamp
Поле Dzengi:
closeTime
содержит Unix timestamp в миллисекундах.
Mapper преобразует его в timezone-aware UTC datetime.
Семантика преобразования:
closeTime milliseconds
↓
UTC datetime
↓
Quote.source_timestamp
Использование timezone-aware значения необходимо для однозначного представления времени рыночного события и последующих операций:
- freshness calculation;
- sequence validation;
- event ordering;
- диагностика задержек;
- сопоставление данных из нескольких источников.
9. Время получения котировки
Помимо биржевого времени события, канонический Quote содержит время фактического получения данных платформой:
received_at
Разделение двух временных характеристик принципиально:
source_timestamp
означает время, указанное источником данных;
received_at
означает время, когда котировка была преобразована во внутреннюю модель платформы.
Это создаёт фундамент для последующего определения:
- возраста котировки;
- сетевой задержки;
- freshness;
- stale data;
- задержки между биржей и локальной системой.
10. Источник котировки
Канонический Quote получает идентификатор источника:
dzengi
Это позволяет внутренней модели не зависеть от конкретного adapter-класса, сохраняя при этом происхождение рыночных данных.
Целевая модель допускает дальнейшую работу с несколькими источниками:
Dzengi
Binance
Coinbase
другие источники
При этом приоритетным источником для торговых решений остаётся биржа исполнения.
11. Quotes Handler
В файле:
src/market_data/acquisition/handlers/quotes_handler.py
реализован специализированный обработчик quote-документа.
Его ответственность — оркестрировать существующие специализированные стадии обработки:
raw document
↓
schema validation
↓
parser
↓
value validation
↓
mapper
↓
Quote
Handler является единой точкой преобразования:
object → Quote
Он не должен самостоятельно дублировать внутреннюю реализацию:
- schema validation;
- parsing;
- value validation;
- mapping.
Вместо этого handler координирует специализированные компоненты.
12. Полная цепочка обработки
После Build 029 полный путь REST-документа выглядит следующим образом:
{
"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 знает о:
DzengiTicker24hrResponse
lastPrice
bidPrice
askPrice
closeTime
После mapping верхние слои работают исключительно с:
Quote
13.2. Handler не зависит от ExchangeService
Новый QuotesHandler не использует:
src.integrations.exchange.service.ExchangeService
Направление зависимостей остаётся правильным:
external Dzengi payload
↓
Acquisition adapter
↓
Acquisition handler
↓
canonical Quote
Обратной зависимости новой подсистемы от legacy integration layer нет.
13.3. Handler не является Feed
QuotesHandler отвечает только за преобразование документа:
object → Quote
Он не отвечает за получение документа от биржи.
Получение данных будет ответственностью:
src/market_data/acquisition/feeds/quotes_feed.py
на следующем этапе миграции.
13.4. Handler не является Store
QuotesHandler не сохраняет котировки.
Хранение будет реализовано отдельно:
Build 032 — Канонический Quote Store
Такое разделение предотвращает смешивание:
acquisition
processing
storage
13.5. Build не изменяет production runtime
В Build 029 не изменены:
src/integrations/exchange/service.py
src/integrations/exchange/market_cache.py
src/integrations/exchange/market_stream.py
src/integrations/exchange/market_data_runner.py
Не переведены:
UI consumers
execution consumers
strategy consumers
diagnostics consumers
Работающий бот продолжает использовать прежний runtime-контур.
14. Что намеренно не реализовано
В Build 029 не входят:
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. Проверки
Выполнена синтаксическая проверка:
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
Результат:
Успешно.
Выполнены специализированные тесты Build 029:
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
Результат:
12 passed in 0.03s
Выполнена полная регрессия проекта:
python -m pytest -q
Результат:
457 passed in 0.26s
Регрессий не обнаружено.
Количество тестов увеличилось:
После Build 028: 445 passed
После Build 029: 457 passed
Добавлено:
12 специализированных тестов
16. Критерии завершения Build 029
Build 029 считается завершённым, поскольку выполнены все необходимые условия:
- реализован специализированный Dzengi quote mapper;
DzengiTicker24hrResponseпреобразуется в каноническийQuote;- API-specific имена не выходят за пределы adapter layer;
- цены преобразуются в
Decimal; - не используется промежуточное преобразование цен через
float; closeTimeпреобразуется в timezone-aware UTCdatetime;- фиксируется
received_at; - сохраняется источник котировки;
- реализован специализированный
QuotesHandler; - handler оркестрирует полный цикл обработки сырого документа;
- handler не дублирует ответственность parser;
- handler не дублирует ответственность validation;
- handler не дублирует ответственность mapper;
- новая реализация не зависит от
ExchangeService; - новая реализация не зависит от
MarketPriceCache; - production runtime не изменён;
- специализированные тесты проходят;
- полная регрессия проходит.
17. Итог
В результате Build 029 завершён слой преобразования REST-котировки Dzengi во внутреннюю каноническую модель платформы:
Dzengi REST payload
↓
schema validation
↓
parser
↓
value validation
↓
DzengiTicker24hrResponse
↓
mapper
↓
canonical Quote
Также создан единый специализированный обработчик:
QuotesHandler
который предоставляет операцию:
raw document → canonical Quote
При этом сохранены ключевые архитектурные свойства миграции:
- новая реализация развивается параллельно legacy-контуру;
- работающий бот не сломан;
ExchangeServiceне изменён;MarketPriceCacheне изменён;- runtime-потребители не изменены;
- Dzengi-specific формат изолирован внутри adapter layer;
- верхние слои получают каноническую модель
Quote; - mapping и orchestration разделены по ответственности;
- сохранена возможность безопасного поэтапного переключения системы.
Build 029 завершён.
Следующий этап:
Build 030 — Quotes Feed и регистрация в Acquisition Service