# 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 завершён.