feat: add market data architecture and complete migration through build 039
This commit is contained in:
567
docs/migrations/build_038.md
Normal file
567
docs/migrations/build_038.md
Normal 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 завершён и зафиксирован.
|
||||
|
||||
Система готова к следующему этапу миграции.
|
||||
Reference in New Issue
Block a user