567 lines
13 KiB
Markdown
567 lines
13 KiB
Markdown
# 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 завершён и зафиксирован.
|
||
|
||
Система готова к следующему этапу миграции. |