# 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 ```