build 039: complete Quotes Feed migration foundation
This commit is contained in:
608
docs/migrations/build_037.md
Normal file
608
docs/migrations/build_037.md
Normal file
@@ -0,0 +1,608 @@
|
||||
# Build 037 — Перевод execution-потребителей на canonical Quote
|
||||
|
||||
**Статус:** завершён
|
||||
**Тип изменения:** миграция / архитектурный рефакторинг
|
||||
**Подсистема:** Market Data / Exchange Integration / Trading Execution
|
||||
**Дата завершения:** 13 июля 2026
|
||||
|
||||
---
|
||||
|
||||
## 1. Цель Build
|
||||
|
||||
Цель Build 037 — перевести execution-потребителей с legacy-представлений рыночной котировки на каноническую модель `Quote`, сохранив существующее поведение торговой системы и обратную совместимость переходного периода.
|
||||
|
||||
Build продолжает миграционную последовательность:
|
||||
|
||||
```text
|
||||
Build 031 — Quotes Feed
|
||||
↓
|
||||
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 037 execution-контур получает рыночную котировку через канонический `Quote`, а специализированный execution boundary предоставляет типизированное представление `ExecutionPriceSnapshot`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Архитектурный принцип
|
||||
|
||||
В рамках Build 037 зафиксировано следующее направление зависимости:
|
||||
|
||||
```text
|
||||
Dzengi REST / WebSocket
|
||||
↓
|
||||
Market Data Acquisition
|
||||
↓
|
||||
canonical Quote
|
||||
↓
|
||||
Quote Store
|
||||
↓
|
||||
ExchangeService
|
||||
↓
|
||||
ExecutionPriceSnapshot
|
||||
↓
|
||||
Execution consumers
|
||||
```
|
||||
|
||||
Execution-потребители не должны самостоятельно:
|
||||
|
||||
- разбирать сырой payload биржи;
|
||||
- знать формат Dzengi REST или WebSocket;
|
||||
- обращаться напрямую к `Quote Store`;
|
||||
- зависеть от legacy `MarketPriceSnapshot`;
|
||||
- использовать dict-based market snapshot там, где необходим типизированный execution-контракт;
|
||||
- самостоятельно определять источник котировки.
|
||||
|
||||
Канонический объект рыночной котировки:
|
||||
|
||||
```python
|
||||
Quote
|
||||
```
|
||||
|
||||
Типизированное представление для execution-контура:
|
||||
|
||||
```python
|
||||
ExecutionPriceSnapshot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Область изменений
|
||||
|
||||
В рамках Build 037 изменены следующие production-файлы:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/service.py
|
||||
src/trading/auto/execution_quality.py
|
||||
src/trading/debug/execution.py
|
||||
```
|
||||
|
||||
Добавлены специализированные тесты:
|
||||
|
||||
```text
|
||||
tests/unit/integrations/exchange/test_service_execution_quote.py
|
||||
tests/unit/trading/auto/test_execution_quality.py
|
||||
tests/unit/trading/debug/test_execution.py
|
||||
```
|
||||
|
||||
Файл:
|
||||
|
||||
```text
|
||||
src/trading/execution/pricing.py
|
||||
```
|
||||
|
||||
был проанализирован, но не потребовал production-изменений, поскольку уже использовал типизированный boundary:
|
||||
|
||||
```python
|
||||
ExchangeService().get_execution_snapshot(symbol)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Изменения в ExchangeService
|
||||
|
||||
### 4.1. Execution snapshot теперь строится из canonical Quote
|
||||
|
||||
Метод:
|
||||
|
||||
```python
|
||||
get_execution_snapshot()
|
||||
```
|
||||
|
||||
переведён на получение канонической котировки через:
|
||||
|
||||
```python
|
||||
get_quote()
|
||||
```
|
||||
|
||||
Таким образом, execution boundary больше не зависит от legacy `MarketPriceSnapshot`.
|
||||
|
||||
Целевая цепочка:
|
||||
|
||||
```text
|
||||
Quote Store
|
||||
↓
|
||||
canonical Quote
|
||||
↓
|
||||
ExchangeService.get_quote()
|
||||
↓
|
||||
ExchangeService.get_execution_snapshot()
|
||||
↓
|
||||
ExecutionPriceSnapshot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.2. Сохранён типизированный execution-контракт
|
||||
|
||||
Execution-потребители получают:
|
||||
|
||||
```python
|
||||
ExecutionPriceSnapshot
|
||||
```
|
||||
|
||||
с полями:
|
||||
|
||||
```text
|
||||
symbol
|
||||
last_price
|
||||
bid_price
|
||||
ask_price
|
||||
updated_at
|
||||
source
|
||||
is_fresh
|
||||
age_seconds
|
||||
```
|
||||
|
||||
Это позволяет execution-слою работать с явным типизированным контрактом вместо произвольного словаря.
|
||||
|
||||
---
|
||||
|
||||
### 4.3. Сохранена семантика freshness
|
||||
|
||||
При построении `ExecutionPriceSnapshot` сохраняется информация о возрасте котировки:
|
||||
|
||||
```text
|
||||
age_seconds
|
||||
```
|
||||
|
||||
и состоянии актуальности:
|
||||
|
||||
```text
|
||||
is_fresh
|
||||
```
|
||||
|
||||
Execution-контур продолжает использовать существующие ограничения максимального возраста котировки.
|
||||
|
||||
В частности:
|
||||
|
||||
```python
|
||||
_max_execution_snapshot_age_seconds = 5
|
||||
```
|
||||
|
||||
остаётся execution-policy и не переносится в слой Market Data.
|
||||
|
||||
Это соответствует разделению ответственности:
|
||||
|
||||
```text
|
||||
Market Data
|
||||
предоставляет Quote и объективный возраст данных
|
||||
|
||||
Execution
|
||||
определяет, допустим ли этот возраст для исполнения сделки
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Изменения в execution quality
|
||||
|
||||
Файл:
|
||||
|
||||
```text
|
||||
src/trading/auto/execution_quality.py
|
||||
```
|
||||
|
||||
переведён с legacy dict-based market snapshot на типизированный:
|
||||
|
||||
```python
|
||||
ExecutionPriceSnapshot
|
||||
```
|
||||
|
||||
Ранее execution quality зависел от:
|
||||
|
||||
```python
|
||||
get_market_snapshot()
|
||||
```
|
||||
|
||||
и извлекал значения через:
|
||||
|
||||
```python
|
||||
snapshot.get("bid_price")
|
||||
snapshot.get("ask_price")
|
||||
snapshot.get("last_price")
|
||||
snapshot.get("age_seconds")
|
||||
snapshot.get("is_fresh")
|
||||
```
|
||||
|
||||
После Build 037 используется типизированный execution boundary:
|
||||
|
||||
```python
|
||||
get_execution_snapshot()
|
||||
```
|
||||
|
||||
и атрибуты:
|
||||
|
||||
```python
|
||||
snapshot.bid_price
|
||||
snapshot.ask_price
|
||||
snapshot.last_price
|
||||
snapshot.age_seconds
|
||||
snapshot.is_fresh
|
||||
```
|
||||
|
||||
Это устраняет зависимость execution quality от legacy dict-based представления котировки.
|
||||
|
||||
---
|
||||
|
||||
## 6. Устранение legacy fallback через get_price()
|
||||
|
||||
В execution quality существовал fallback через:
|
||||
|
||||
```python
|
||||
ExchangeService().get_price(...)
|
||||
```
|
||||
|
||||
В рамках Build 037 этот путь устранён из execution-потребителя.
|
||||
|
||||
Fallback теперь также проходит через типизированный execution boundary:
|
||||
|
||||
```python
|
||||
ExchangeService().get_execution_snapshot(...)
|
||||
```
|
||||
|
||||
Таким образом, основной и fallback-пути используют единый контракт данных.
|
||||
|
||||
Целевая схема:
|
||||
|
||||
```text
|
||||
canonical Quote
|
||||
↓
|
||||
ExecutionPriceSnapshot
|
||||
├── основной execution path
|
||||
└── fallback execution path
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Изменения debug execution
|
||||
|
||||
Файл:
|
||||
|
||||
```text
|
||||
src/trading/debug/execution.py
|
||||
```
|
||||
|
||||
переведён с:
|
||||
|
||||
```python
|
||||
get_fresh_market_snapshot()
|
||||
```
|
||||
|
||||
на:
|
||||
|
||||
```python
|
||||
get_execution_snapshot()
|
||||
```
|
||||
|
||||
Debug execution теперь использует тот же типизированный execution boundary, что и production execution.
|
||||
|
||||
Это устраняет архитектурное расхождение:
|
||||
|
||||
```text
|
||||
production execution → ExecutionPriceSnapshot
|
||||
debug execution → legacy dict snapshot
|
||||
```
|
||||
|
||||
и заменяет его единым подходом:
|
||||
|
||||
```text
|
||||
production execution → ExecutionPriceSnapshot
|
||||
debug execution → ExecutionPriceSnapshot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Side-aware execution pricing
|
||||
|
||||
Build 037 сохраняет существующую семантику выбора цены исполнения.
|
||||
|
||||
Для входа в LONG:
|
||||
|
||||
```text
|
||||
ask_price
|
||||
↓ fallback
|
||||
last_price
|
||||
```
|
||||
|
||||
Для входа в SHORT:
|
||||
|
||||
```text
|
||||
bid_price
|
||||
↓ fallback
|
||||
last_price
|
||||
```
|
||||
|
||||
Для выхода из LONG:
|
||||
|
||||
```text
|
||||
bid_price
|
||||
↓ fallback
|
||||
last_price
|
||||
```
|
||||
|
||||
Для выхода из SHORT:
|
||||
|
||||
```text
|
||||
ask_price
|
||||
↓ fallback
|
||||
last_price
|
||||
```
|
||||
|
||||
Для общего получения текущей рыночной цены:
|
||||
|
||||
```text
|
||||
last_price
|
||||
```
|
||||
|
||||
Это поведение не изменялось в рамках Build 037.
|
||||
|
||||
---
|
||||
|
||||
## 9. Что намеренно не изменялось
|
||||
|
||||
Build 037 ограничен execution-потребителями.
|
||||
|
||||
В рамках данного Build намеренно не переводились:
|
||||
|
||||
```text
|
||||
src/trading/auto/signal_runtime.py
|
||||
src/trading/strategies/trend.py
|
||||
src/trading/strategies/scalp.py
|
||||
```
|
||||
|
||||
Эти файлы относятся к следующему этапу:
|
||||
|
||||
```text
|
||||
Build 038 — Перевод strategy и runtime-потребителей на canonical Quote
|
||||
```
|
||||
|
||||
Также не выполнялись:
|
||||
|
||||
- удаление legacy `get_market_snapshot()`;
|
||||
- удаление legacy `get_fresh_market_snapshot()`;
|
||||
- удаление `MarketPriceCache`;
|
||||
- глобальная перестройка `ExchangeService`;
|
||||
- изменение утверждённой структуры каталогов;
|
||||
- изменение торговой стратегии;
|
||||
- изменение execution thresholds;
|
||||
- изменение логики открытия, закрытия или flip позиции.
|
||||
|
||||
---
|
||||
|
||||
## 10. Обратная совместимость
|
||||
|
||||
Build 037 выполнен как безопасный миграционный этап.
|
||||
|
||||
Legacy API не удалялись, поскольку некоторые потребители ещё используют переходные методы.
|
||||
|
||||
Сохраняются:
|
||||
|
||||
```text
|
||||
get_market_snapshot()
|
||||
get_fresh_market_snapshot()
|
||||
get_price()
|
||||
MarketPriceCache
|
||||
```
|
||||
|
||||
Их удаление допустимо только после полного перевода всех production-потребителей и отдельного контрольного `grep`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Тестовое покрытие
|
||||
|
||||
Для Build 037 добавлены специализированные unit-тесты:
|
||||
|
||||
```text
|
||||
tests/unit/integrations/exchange/test_service_execution_quote.py
|
||||
tests/unit/trading/auto/test_execution_quality.py
|
||||
tests/unit/trading/debug/test_execution.py
|
||||
```
|
||||
|
||||
Тестами подтверждено:
|
||||
|
||||
- `get_execution_snapshot()` использует canonical `Quote`;
|
||||
- execution service не зависит от legacy market snapshot;
|
||||
- execution quality использует `ExecutionPriceSnapshot`;
|
||||
- legacy `get_market_snapshot()` не используется новым execution quality path;
|
||||
- debug execution использует `get_execution_snapshot()`;
|
||||
- legacy `get_fresh_market_snapshot()` не используется новым debug execution path;
|
||||
- сохраняется корректная передача `last_price`;
|
||||
- сохраняется корректная передача `bid_price`;
|
||||
- сохраняется корректная передача `ask_price`;
|
||||
- сохраняется информация о возрасте котировки;
|
||||
- сохраняется freshness semantics.
|
||||
|
||||
---
|
||||
|
||||
## 12. Результаты проверки
|
||||
|
||||
Проверка синтаксиса:
|
||||
|
||||
```bash
|
||||
python -m py_compile \
|
||||
src/integrations/exchange/service.py \
|
||||
src/trading/auto/execution_quality.py \
|
||||
src/trading/debug/execution.py \
|
||||
tests/unit/integrations/exchange/test_service_execution_quote.py \
|
||||
tests/unit/trading/auto/test_execution_quality.py \
|
||||
tests/unit/trading/debug/test_execution.py
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
успешно
|
||||
```
|
||||
|
||||
Специализированные тесты:
|
||||
|
||||
```bash
|
||||
python -m pytest \
|
||||
tests/unit/integrations/exchange/test_service_execution_quote.py \
|
||||
tests/unit/trading/auto/test_execution_quality.py \
|
||||
tests/unit/trading/debug/test_execution.py \
|
||||
-q
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
4 passed in 0.08s
|
||||
```
|
||||
|
||||
Полная регрессия:
|
||||
|
||||
```bash
|
||||
python -m pytest -q
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
618 passed in 0.28s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. Изменение количества тестов
|
||||
|
||||
До Build 037:
|
||||
|
||||
```text
|
||||
614 passed
|
||||
```
|
||||
|
||||
После Build 037:
|
||||
|
||||
```text
|
||||
618 passed
|
||||
```
|
||||
|
||||
Добавлено:
|
||||
|
||||
```text
|
||||
4 теста
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Итоговая архитектура после Build 037
|
||||
|
||||
После завершения Build 037 execution-контур выглядит следующим образом:
|
||||
|
||||
```text
|
||||
Dzengi REST / WebSocket
|
||||
↓
|
||||
Market Data Acquisition
|
||||
↓
|
||||
canonical Quote
|
||||
↓
|
||||
Quote Store
|
||||
↓
|
||||
ExchangeService.get_quote()
|
||||
↓
|
||||
ExchangeService.get_execution_snapshot()
|
||||
↓
|
||||
ExecutionPriceSnapshot
|
||||
├── trading/execution/pricing.py
|
||||
├── trading/auto/execution_quality.py
|
||||
└── trading/debug/execution.py
|
||||
```
|
||||
|
||||
Legacy market snapshot больше не является обязательным источником данных для переведённых execution-потребителей.
|
||||
|
||||
---
|
||||
|
||||
## 15. Критерии завершения Build 037
|
||||
|
||||
Build 037 считается завершённым, поскольку выполнены все обязательные условия:
|
||||
|
||||
- [x] `get_execution_snapshot()` строится из canonical `Quote`;
|
||||
- [x] execution quality переведён на `ExecutionPriceSnapshot`;
|
||||
- [x] debug execution переведён на `ExecutionPriceSnapshot`;
|
||||
- [x] legacy `get_price()` устранён из изменённого execution fallback path;
|
||||
- [x] `pricing.py` проверен и уже использует typed execution boundary;
|
||||
- [x] side-aware pricing сохранён;
|
||||
- [x] freshness semantics сохранена;
|
||||
- [x] legacy API не удалены преждевременно;
|
||||
- [x] специализированные тесты проходят;
|
||||
- [x] полная регрессия проходит;
|
||||
- [x] существующая торговая логика не изменена.
|
||||
|
||||
---
|
||||
|
||||
## 16. Следующий этап
|
||||
|
||||
Следующий этап миграции:
|
||||
|
||||
```text
|
||||
Build 038 — Перевод strategy и runtime-потребителей на canonical Quote
|
||||
```
|
||||
|
||||
Основные кандидаты следующего Build:
|
||||
|
||||
```text
|
||||
src/trading/auto/signal_runtime.py
|
||||
src/trading/strategies/trend.py
|
||||
src/trading/strategies/scalp.py
|
||||
```
|
||||
|
||||
Цель Build 038:
|
||||
|
||||
```text
|
||||
убрать зависимость strategy и runtime-потребителей
|
||||
от legacy dict-based market snapshot и перевести их
|
||||
на canonical Quote с сохранением существующей торговой семантики.
|
||||
```
|
||||
|
||||
После Build 038 должен быть выполнен новый контрольный `grep` для определения оставшихся production-зависимостей от:
|
||||
|
||||
```text
|
||||
get_market_snapshot()
|
||||
get_fresh_market_snapshot()
|
||||
get_price()
|
||||
MarketPriceCache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17. Статус
|
||||
|
||||
```text
|
||||
Build 037 — ЗАВЕРШЁН
|
||||
```
|
||||
|
||||
Следующий Build:
|
||||
|
||||
```text
|
||||
Build 038 — Перевод strategy и runtime-потребителей на canonical Quote
|
||||
```
|
||||
Reference in New Issue
Block a user