feat: add market data architecture and complete migration through build 039

This commit is contained in:
2026-07-14 09:58:16 +03:00
parent 26deb861bc
commit a996f2f797
443 changed files with 80452 additions and 1335 deletions

View File

@@ -0,0 +1,567 @@
# Build 038 — Перевод strategy и runtime-потребителей на canonical Quote
**Dzentra Market Data Migration**
---
## Контроль документа
| Свойство | Значение |
|---|---|
| Документ | Build 038 — Перевод strategy и runtime-потребителей на canonical Quote |
| Тип документа | Build Record |
| Версия | 1.0 |
| Статус | **Completed** |
| Подсистема | Market Data Acquisition |
| Проект | Dzentra |
| Язык | Русский |
| Предыдущий этап | Build 037 — Перевод execution-потребителей |
| Результат полной регрессии | **626 passed** |
---
## 1. Назначение Build 038
Build 038 завершает перевод выбранных strategy- и runtime-потребителей с legacy snapshot API на каноническую модель рыночной котировки:
```text
Quote
```
Цель этапа — исключить использование словарных market snapshot в следующих потребителях:
```text
src/trading/auto/signal_runtime.py
src/trading/strategies/trend.py
src/trading/strategies/scalp.py
```
и перевести их на единый типизированный источник текущей рыночной котировки:
```text
ExchangeService.get_quote()
Quote
```
---
## 2. Контекст предыдущих Build
Build 038 является продолжением последовательной миграции Market Data:
```text
Build 031
Canonical Quote model
Build 032
Quote acquisition pipeline
Build 033
Quote Store
Build 034
Dzengi WebSocket quote parsing и adapter
Build 035
Market runtime переведён на Quotes Feed
Build 036
Read-only и UI-потребители переведены на canonical Quote
Build 037
Execution-потребители переведены на typed execution snapshot
Build 038
Strategy и runtime-потребители переведены на canonical Quote
```
В результате Build 038 каноническая модель `Quote` становится непосредственным источником текущей рыночной котировки для выбранных runtime- и strategy-компонентов.
---
## 3. Каноническая модель Quote
Используется модель:
```python
@dataclass(frozen=True, slots=True)
class Quote:
symbol: str
last_price: Decimal
bid_price: Decimal
ask_price: Decimal
exchange_timestamp: datetime | None
received_at: datetime
source: str
```
Модель расположена в:
```text
src/market_data/acquisition/models/quote.py
```
Основные свойства модели:
- независимость от конкретного поставщика рыночных данных;
- типизированные цены через `Decimal`;
- отсутствие словарного доступа к ценовым полям;
- наличие времени биржи;
- наличие времени получения данных;
- явное указание источника.
---
## 4. Изменённые runtime-потребители
### 4.1. Auto Signal Runtime
Файл:
```text
src/trading/auto/signal_runtime.py
```
Legacy-путь:
```text
ExchangeService.get_market_snapshot()
dict[str, object]
snapshot.get("bid_price")
snapshot.get("ask_price")
snapshot.get("last_price")
```
Новый путь:
```text
ExchangeService.get_quote()
Quote
quote.bid_price
quote.ask_price
quote.last_price
```
В результате runtime больше не зависит от словарного представления текущей рыночной котировки.
---
## 5. Изменённые strategy-потребители
### 5.1. Trend Strategy
Файл:
```text
src/trading/strategies/trend.py
```
Стратегия переведена с legacy market snapshot на canonical `Quote`.
Новый источник:
```text
ExchangeService.get_quote(
symbol,
runtime_key="auto",
)
```
Ценовые данные теперь читаются непосредственно из типизированной модели:
```text
quote.last_price
quote.bid_price
quote.ask_price
```
Сохранена существующая логика выбора цены анализа:
```text
1. midpoint между bid и ask;
2. last_price;
3. 0.0 при отсутствии пригодной цены.
```
Midpoint остаётся предпочтительной ценой анализа:
```text
(bid + ask) / 2
```
Это позволяет уменьшить зависимость анализа от случайного последнего исполнения сделки.
---
### 5.2. Scalp Strategy
Файл:
```text
src/trading/strategies/scalp.py
```
Стратегия также переведена на:
```text
ExchangeService.get_quote(
symbol,
runtime_key="auto",
)
```
Ценовые поля теперь получаются через:
```text
quote.last_price
quote.bid_price
quote.ask_price
```
Сохранены:
- существующая логика определения analysis price;
- midpoint между bid и ask;
- fallback на `last_price`;
- safe fallback;
- существующие strategy payload;
- существующие торговые пороги;
- существующая логика принятия решений.
---
## 6. Обработка Decimal
Canonical `Quote` использует:
```text
Decimal
```
для полей:
```text
last_price
bid_price
ask_price
```
Существующие strategy helper-функции `_safe_float()` ранее принимали:
```text
NumericLike | None
```
где `Decimal` не входил в контракт `NumericLike`.
Это приводило к ошибкам статической типизации Pylance:
```text
Аргумент типа "Decimal" нельзя присвоить параметру "value"
типа "NumericLike | None"
```
В рамках Build 038 контракт локальных strategy helper-функций расширен до:
```python
value: NumericLike | Decimal | None
```
Глобальный тип:
```text
NumericLike
```
не изменялся.
Это сохраняет локальность изменения и не расширяет общий типовой контракт проекта без необходимости.
Архитектурная граница имеет вид:
```text
canonical Quote
Decimal
strategy helper
float
существующие strategy calculations и payload
```
---
## 7. Что намеренно не изменялось
Build 038 не изменяет:
- структуру canonical `Quote`;
- `QuoteStore`;
- acquisition pipeline;
- WebSocket adapter;
- REST adapter;
- market runtime producer;
- execution pricing semantics;
- торговые пороги;
- логику открытия позиции;
- логику закрытия позиции;
- flip-логику;
- risk management;
- journal payload contracts;
- strategy payload keys;
- Telegram UI;
- структуру каталогов проекта.
Build 038 является локальным этапом миграции потребителей, а не изменением торговой логики.
---
## 8. Контроль legacy-зависимостей
После выполнения Build 038 выполнен контрольный поиск:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
-E "get_market_snapshot\(|get_fresh_market_snapshot\(|get_execution_snapshot\(|get_price\(|snapshot\.get\(|quote\.get\(|runtime_key" \
src/trading/auto/signal_runtime.py \
src/trading/strategies/trend.py \
src/trading/strategies/scalp.py
```
Результат:
```text
src/trading/auto/signal_runtime.py:881: runtime_key="auto",
src/trading/strategies/trend.py:244: runtime_key="auto",
src/trading/strategies/scalp.py:64: runtime_key="auto",
```
Legacy-вызовы не обнаружены.
В проверенных файлах отсутствуют рабочие зависимости от:
```text
get_market_snapshot()
get_fresh_market_snapshot()
get_execution_snapshot()
get_price()
snapshot.get(...)
quote.get(...)
```
Оставшиеся:
```text
runtime_key="auto"
```
являются ожидаемой частью вызова `get_quote()` и не представляют legacy-зависимость.
---
## 9. Проверка синтаксиса
Выполнена проверка:
```bash
python -m py_compile \
src/trading/auto/signal_runtime.py \
src/trading/strategies/trend.py \
src/trading/strategies/scalp.py
```
Результат:
```text
успешно
```
Ошибки синтаксиса отсутствуют.
---
## 10. Целевые тесты
После исправления типизации `Decimal` выполнены целевые тесты:
```bash
python -m pytest \
tests/unit/trading/strategies/test_trend_quote.py \
tests/unit/trading/strategies/test_scalp_quote.py \
-q
```
Результат:
```text
...... [100%]
6 passed in 0.09s
```
---
## 11. Полная регрессия
Выполнена полная проверка проекта:
```bash
python -m pytest -q
```
Результат:
```text
626 passed in 0.31s
```
Регрессий не обнаружено.
Для сравнения:
```text
После Build 037: 618 passed
После Build 038: 626 passed
```
Количество тестов увеличено на:
```text
8
```
---
## 12. Итоговая архитектура после Build 038
После завершения Build 038 путь текущей рыночной котировки для мигрированных strategy- и runtime-потребителей выглядит следующим образом:
```text
Dzengi WebSocket
DzengiWebSocketQuoteAdapter
canonical Quote
Quote Store
ExchangeService.get_quote()
├── Auto Signal Runtime
├── Trend Strategy
└── Scalp Strategy
```
Для execution-контура сохраняется специализированная типизированная граница, введённая в Build 037:
```text
canonical Quote
ExchangeService
ExecutionPriceSnapshot
Execution consumers
```
Таким образом, после Build 038 разделены два типа потребления:
```text
Market / Strategy / Runtime
canonical Quote
Execution
ExecutionPriceSnapshot
```
---
## 13. Архитектурный результат
Build 038 устраняет ещё один слой legacy market snapshot API из рабочего торгового контура.
До Build 038:
```text
Market Data
legacy dict snapshot
snapshot.get(...)
runtime / strategies
```
После Build 038:
```text
Market Data
canonical Quote
typed attributes
runtime / strategies
```
Это обеспечивает:
- единый канонический контракт текущей котировки;
- статическую типизацию;
- отказ от строковых ключей для доступа к ценам;
- явную работу с `Decimal`;
- уменьшение зависимости trading layer от legacy exchange representations;
- подготовку к дальнейшему удалению legacy snapshot API.
---
## 14. Критерии завершения
Build 038 считается завершённым, поскольку выполнены все критерии:
- [x] `signal_runtime.py` переведён на canonical `Quote`;
- [x] `trend.py` переведён на canonical `Quote`;
- [x] `scalp.py` переведён на canonical `Quote`;
- [x] legacy `get_market_snapshot()` удалён из мигрированных путей;
- [x] словарный доступ `snapshot.get(...)` к текущей котировке удалён;
- [x] типизация `Decimal` обработана явно;
- [x] глобальный `NumericLike` не изменён;
- [x] существующая торговая логика сохранена;
- [x] strategy payload contracts сохранены;
- [x] `py_compile` проходит успешно;
- [x] целевые тесты проходят успешно;
- [x] полная регрессия проходит успешно;
- [x] итоговый результат — **626 passed**.
---
## 15. Статус
```text
Build 038: COMPLETED
```
Build 038 завершён и зафиксирован.
Система готова к следующему этапу миграции.