514 lines
15 KiB
Markdown
514 lines
15 KiB
Markdown
# Build 041 — Аудит и удаление остатков legacy quote parsing
|
||
|
||
**Статус:** завершён
|
||
**Подсистема:** Market Data Acquisition / Quotes Feed
|
||
**Проект:** Dzentra
|
||
**Тип изменения:** архитектурная миграция без изменения рабочего поведения
|
||
**Основное требование:** не изменять логику работы текущих файлов и наблюдаемое поведение бота
|
||
|
||
---
|
||
|
||
## 1. Цель Build
|
||
|
||
Цель Build 041 — выполнить аудит оставшихся production-зависимостей от legacy-разбора котировок и удалить подтверждённо неиспользуемую дублирующую логику WebSocket quote parsing из legacy integration layer.
|
||
|
||
Build продолжает миграцию Quotes Feed после:
|
||
|
||
- Build 039 — удаления legacy `TickerPrice` и market snapshot dict layer;
|
||
- Build 040 — восстановления и фиксации REST fallback через канонический Quotes acquisition pipeline.
|
||
|
||
Основной архитектурный принцип Build 041:
|
||
|
||
~~~text
|
||
Raw Dzengi WebSocket payload
|
||
↓
|
||
DzengiWebSocketQuoteAdapter
|
||
↓
|
||
schema validation
|
||
↓
|
||
parser
|
||
↓
|
||
value validation
|
||
↓
|
||
mapper
|
||
↓
|
||
canonical Quote
|
||
↓
|
||
runtime consumers
|
||
~~~
|
||
|
||
Production-код вне `market_data/acquisition` не должен самостоятельно интерпретировать структуру WebSocket payload для получения `bid_price` и `ask_price`.
|
||
|
||
---
|
||
|
||
## 2. Основное ограничение
|
||
|
||
Главное требование Build 041:
|
||
|
||
> **Не менять логику работы текущих файлов.**
|
||
|
||
В рамках Build запрещалось:
|
||
|
||
- изменять торговую логику;
|
||
- изменять алгоритмы принятия решений;
|
||
- изменять execution pricing semantics;
|
||
- изменять формат канонической модели `Quote`;
|
||
- изменять REST Quotes Feed;
|
||
- изменять WebSocket transport;
|
||
- изменять алгоритм переподключения WebSocket;
|
||
- изменять runtime lifecycle;
|
||
- изменять журналирование;
|
||
- изменять UI;
|
||
- изменять обработку торговых сигналов;
|
||
- изменять формат runtime-состояния;
|
||
- удалять код без доказанного отсутствия production-зависимостей.
|
||
|
||
Допускалось только удаление legacy quote parsing, если одновременно подтверждены следующие условия:
|
||
|
||
1. production-код уже использует канонический `DzengiWebSocketQuoteAdapter`;
|
||
2. удаляемая функция больше не вызывается;
|
||
3. соответствующая логика уже реализована в `market_data/acquisition`;
|
||
4. полный набор тестов подтверждает отсутствие регрессий.
|
||
|
||
---
|
||
|
||
## 3. Предварительный аудит
|
||
|
||
Перед изменениями был выполнен аудит всех известных признаков legacy quote parsing.
|
||
|
||
Проверялись:
|
||
|
||
- прямое чтение `lastPrice`;
|
||
- прямое чтение `bidPrice`;
|
||
- прямое чтение `askPrice`;
|
||
- прямое чтение `closeTime`;
|
||
- прямое чтение `eventTime`;
|
||
- прямое чтение `bids`;
|
||
- прямое чтение `asks`;
|
||
- функции извлечения best bid / best ask;
|
||
- старые market snapshot helpers;
|
||
- старые ticker price helpers;
|
||
- прямое создание `Quote`;
|
||
- REST endpoint `/api/v1/ticker/24hr`;
|
||
- все production-потребители `DzengiWebSocketQuoteAdapter`;
|
||
- все production-потребители `QuotesFeed`;
|
||
- все production-потребители `QuoteAcquisitionService`.
|
||
|
||
Аудит показал, что корректный разбор REST-котировок уже сосредоточен в:
|
||
|
||
~~~text
|
||
src/market_data/acquisition/
|
||
├── adapters/dzengi/models.py
|
||
├── adapters/dzengi/parser.py
|
||
├── adapters/dzengi/mapper.py
|
||
├── adapters/dzengi/rest.py
|
||
├── adapters/dzengi/websocket.py
|
||
├── validation/schema.py
|
||
├── validation/values.py
|
||
├── handlers/quotes_handler.py
|
||
├── feeds/quotes_feed.py
|
||
└── service.py
|
||
~~~
|
||
|
||
При этом в legacy integration layer оставались отдельные helpers, самостоятельно интерпретировавшие структуру WebSocket payload.
|
||
|
||
---
|
||
|
||
## 4. Обнаруженные legacy helpers
|
||
|
||
Аудит выявил следующие legacy-функции.
|
||
|
||
### `src/integrations/exchange/market_stream.py`
|
||
|
||
~~~python
|
||
def _extract_depth_prices(
|
||
event: JsonDict,
|
||
) -> tuple[float | None, float | None]:
|
||
...
|
||
~~~
|
||
|
||
~~~python
|
||
def _extract_first_price(
|
||
value: object,
|
||
) -> float | None:
|
||
...
|
||
~~~
|
||
|
||
Эти функции самостоятельно разбирали:
|
||
|
||
~~~text
|
||
bids
|
||
asks
|
||
bidPrice
|
||
askPrice
|
||
~~~
|
||
|
||
и тем самым дублировали ответственность канонического Dzengi WebSocket quote adapter.
|
||
|
||
### `src/integrations/exchange/market_data_runner.py`
|
||
|
||
~~~python
|
||
def _extract_best_price(
|
||
...
|
||
) -> float | None:
|
||
...
|
||
~~~
|
||
|
||
Функция также содержала legacy-логику извлечения цены непосредственно из сырой структуры WebSocket payload.
|
||
|
||
---
|
||
|
||
## 5. Подтверждение канонического пути
|
||
|
||
До удаления legacy helpers было подтверждено, что рабочий runtime уже использует:
|
||
|
||
~~~python
|
||
DzengiWebSocketQuoteAdapter
|
||
~~~
|
||
|
||
Канонический путь обработки WebSocket-котировки:
|
||
|
||
~~~text
|
||
ExchangeWebSocketClient
|
||
↓
|
||
raw WebSocket message
|
||
↓
|
||
DzengiWebSocketQuoteAdapter.map_message()
|
||
↓
|
||
validate_dzengi_websocket_quote_schema()
|
||
↓
|
||
parse_dzengi_websocket_quote()
|
||
↓
|
||
validate_dzengi_websocket_quote_values()
|
||
↓
|
||
map_dzengi_websocket_quote_to_quote()
|
||
↓
|
||
Quote
|
||
~~~
|
||
|
||
Таким образом, production runtime получает уже каноническую модель:
|
||
|
||
~~~python
|
||
Quote
|
||
~~~
|
||
|
||
и не должен повторно разбирать сырой Dzengi payload.
|
||
|
||
---
|
||
|
||
## 6. Выполненные изменения
|
||
|
||
В Build 041 удалена подтверждённо избыточная legacy quote parsing logic.
|
||
|
||
Изменения затронули:
|
||
|
||
~~~text
|
||
app/src/integrations/exchange/market_stream.py
|
||
app/src/integrations/exchange/market_data_runner.py
|
||
app/tests/unit/integrations/exchange/test_market_stream.py
|
||
app/tests/unit/integrations/exchange/test_market_data_runner.py
|
||
~~~
|
||
|
||
Удалены legacy helpers:
|
||
|
||
~~~text
|
||
_extract_depth_prices
|
||
_extract_first_price
|
||
_extract_best_price
|
||
_extract_market_event
|
||
~~~
|
||
|
||
Также удалены остаточные прямые обращения вида:
|
||
|
||
~~~text
|
||
first.get("bidPrice")
|
||
first.get("askPrice")
|
||
~~~
|
||
|
||
в проверяемом legacy integration-контуре.
|
||
|
||
---
|
||
|
||
## 7. Что не изменялось
|
||
|
||
Build 041 не изменял:
|
||
|
||
### REST Quotes Feed
|
||
|
||
Не изменялись:
|
||
|
||
~~~text
|
||
DzengiQuoteDocumentSource
|
||
DzengiQuoteDocumentHandler
|
||
QuotesFeed
|
||
QuoteAcquisitionService
|
||
~~~
|
||
|
||
Не изменялся endpoint:
|
||
|
||
~~~text
|
||
/api/v1/ticker/24hr
|
||
~~~
|
||
|
||
Не изменялся REST fallback, восстановленный и зафиксированный в Build 040.
|
||
|
||
### Каноническая модель Quote
|
||
|
||
Не изменялись:
|
||
|
||
~~~text
|
||
symbol
|
||
last_price
|
||
bid_price
|
||
ask_price
|
||
source
|
||
exchange_timestamp
|
||
received_at
|
||
~~~
|
||
|
||
### WebSocket acquisition pipeline
|
||
|
||
Не изменялись:
|
||
|
||
~~~text
|
||
DzengiWebSocketQuoteAdapter
|
||
validate_dzengi_websocket_quote_schema
|
||
parse_dzengi_websocket_quote
|
||
validate_dzengi_websocket_quote_values
|
||
map_dzengi_websocket_quote_to_quote
|
||
~~~
|
||
|
||
### Runtime semantics
|
||
|
||
Не изменялись:
|
||
|
||
- выбор торгового инструмента;
|
||
- запуск и остановка market runtime;
|
||
- reconnect logic;
|
||
- обработка WebSocket-соединения;
|
||
- вычисление spread;
|
||
- формирование runtime-состояния;
|
||
- execution pricing;
|
||
- стратегия;
|
||
- автоторговля;
|
||
- Telegram UI;
|
||
- журнал.
|
||
|
||
---
|
||
|
||
## 8. Архитектурный результат
|
||
|
||
До Build 041 часть legacy integration layer всё ещё содержала собственную интерпретацию WebSocket payload:
|
||
|
||
~~~text
|
||
Raw WebSocket payload
|
||
├──→ legacy parser in market_stream
|
||
├──→ legacy parser in market_data_runner
|
||
└──→ canonical DzengiWebSocketQuoteAdapter
|
||
~~~
|
||
|
||
После Build 041:
|
||
|
||
~~~text
|
||
Raw WebSocket payload
|
||
↓
|
||
DzengiWebSocketQuoteAdapter
|
||
↓
|
||
Quote
|
||
↓
|
||
market_stream / market_data_runner
|
||
~~~
|
||
|
||
Таким образом:
|
||
|
||
- разбор Dzengi WebSocket payload имеет единственный канонический путь;
|
||
- transport-specific parsing находится в `market_data/acquisition`;
|
||
- integration layer работает с канонической моделью `Quote`;
|
||
- дублирующая legacy parsing logic удалена;
|
||
- runtime-поведение сохранено.
|
||
|
||
---
|
||
|
||
## 9. Проверки
|
||
|
||
После изменений выполнены целевые тесты integration layer:
|
||
|
||
~~~bash
|
||
python -m pytest -q \
|
||
tests/unit/integrations/exchange/test_market_data_runner.py \
|
||
tests/unit/integrations/exchange/test_market_stream.py
|
||
~~~
|
||
|
||
Результат:
|
||
|
||
~~~text
|
||
15 passed in 0.10s
|
||
~~~
|
||
|
||
Выполнены целевые тесты канонического WebSocket quote acquisition pipeline:
|
||
|
||
~~~bash
|
||
python -m pytest -q \
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_parser.py \
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_mapper.py \
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_adapter.py \
|
||
tests/unit/market_data/acquisition/validation/test_websocket_quote_schema.py \
|
||
tests/unit/market_data/acquisition/validation/test_websocket_quote_values.py
|
||
~~~
|
||
|
||
Результат:
|
||
|
||
~~~text
|
||
24 passed in 0.02s
|
||
~~~
|
||
|
||
Выполнен полный набор тестов проекта:
|
||
|
||
~~~bash
|
||
python -m pytest -q
|
||
~~~
|
||
|
||
Результат:
|
||
|
||
~~~text
|
||
614 passed in 1.77s
|
||
~~~
|
||
|
||
---
|
||
|
||
## 10. Финальный grep-контроль
|
||
|
||
После удаления legacy quote parsing выполнена проверка:
|
||
|
||
~~~bash
|
||
grep -RIn \
|
||
--exclude-dir="__pycache__" \
|
||
--exclude="*.pyc" \
|
||
-E "_extract_depth_prices|_extract_first_price|_extract_best_price|_extract_market_event|first\.get\(\"bidPrice\"\)|first\.get\(\"askPrice\"\)" \
|
||
src tests
|
||
~~~
|
||
|
||
Результат:
|
||
|
||
~~~text
|
||
совпадений нет
|
||
~~~
|
||
|
||
Это подтверждает отсутствие проверяемых legacy helpers и прямого parsing `bidPrice` / `askPrice` через старый integration-код.
|
||
|
||
---
|
||
|
||
## 11. Сохранённые допустимые transport-specific поля
|
||
|
||
После Build 041 в проекте по-прежнему существуют обращения к:
|
||
|
||
~~~text
|
||
lastPrice
|
||
bidPrice
|
||
askPrice
|
||
closeTime
|
||
eventTime
|
||
bids
|
||
asks
|
||
~~~
|
||
|
||
Их наличие само по себе не является legacy-зависимостью.
|
||
|
||
Они допустимы внутри канонического Dzengi acquisition adapter:
|
||
|
||
~~~text
|
||
market_data/acquisition/adapters/dzengi/
|
||
market_data/acquisition/validation/
|
||
~~~
|
||
|
||
поскольку именно этот слой отвечает за:
|
||
|
||
- знание transport-specific формата Dzengi;
|
||
- structural validation;
|
||
- parsing;
|
||
- value validation;
|
||
- mapping в каноническую модель `Quote`.
|
||
|
||
Также такие поля допустимы в unit-тестах соответствующего transport adapter.
|
||
|
||
---
|
||
|
||
## 12. Критерии завершения Build
|
||
|
||
Build 041 считается завершённым, поскольку выполнены все критерии:
|
||
|
||
- [x] проведён аудит legacy quote parsing;
|
||
- [x] подтверждён единственный канонический WebSocket parsing pipeline;
|
||
- [x] удалён `_extract_depth_prices`;
|
||
- [x] удалён `_extract_first_price`;
|
||
- [x] удалён `_extract_best_price`;
|
||
- [x] удалён `_extract_market_event`;
|
||
- [x] удалены проверяемые прямые обращения к `bidPrice` и `askPrice` в legacy integration layer;
|
||
- [x] REST Quotes Feed не изменён;
|
||
- [x] REST fallback Build 040 сохранён;
|
||
- [x] каноническая модель `Quote` не изменена;
|
||
- [x] торговая логика не изменена;
|
||
- [x] runtime semantics не изменены;
|
||
- [x] целевые integration-тесты проходят;
|
||
- [x] целевые acquisition-тесты проходят;
|
||
- [x] полный набор из 614 тестов проходит;
|
||
- [x] финальный grep не обнаруживает удалённые legacy helpers.
|
||
|
||
---
|
||
|
||
## 13. Commit
|
||
|
||
Build зафиксирован коммитом:
|
||
|
||
~~~text
|
||
24a4da9 build 041: remove legacy websocket quote parsing
|
||
~~~
|
||
|
||
Предыдущий Build:
|
||
|
||
~~~text
|
||
e68ed7f build 040: restore quote REST fallback after build 039
|
||
~~~
|
||
|
||
---
|
||
|
||
## 14. Итог
|
||
|
||
Build 041 завершает удаление подтверждённых остатков legacy WebSocket quote parsing из integration layer.
|
||
|
||
Итоговая архитектура:
|
||
|
||
~~~text
|
||
Dzengi REST ticker/24hr
|
||
↓
|
||
DzengiQuoteDocumentSource
|
||
↓
|
||
DzengiQuoteDocumentHandler
|
||
↓
|
||
QuotesFeed
|
||
↓
|
||
QuoteAcquisitionService
|
||
↓
|
||
Quote
|
||
|
||
Dzengi WebSocket message
|
||
↓
|
||
DzengiWebSocketQuoteAdapter
|
||
↓
|
||
Quote
|
||
|
||
Quote
|
||
↓
|
||
runtime / execution / strategy / UI consumers
|
||
~~~
|
||
|
||
После Build 041:
|
||
|
||
- REST и WebSocket transport-specific parsing сосредоточен в `market_data/acquisition`;
|
||
- runtime получает каноническую модель `Quote`;
|
||
- legacy WebSocket parsing helpers удалены;
|
||
- дублирующая интерпретация raw payload устранена;
|
||
- рабочая логика текущих файлов сохранена;
|
||
- полный test suite подтверждает отсутствие обнаруженных регрессий.
|
||
|
||
Build 041 завершён. |