# 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 завершён и зафиксирован.

Система готова к следующему этапу миграции.