07.4.4.1.8 Execution Freshness and Market Quality Layer

This commit is contained in:
2026-05-11 20:08:29 +03:00
parent ec9904f91d
commit eb40ecc4dd
7 changed files with 1021 additions and 7 deletions

View File

@@ -159,6 +159,7 @@ def _build_waiting_text(state) -> str:
_signal_line(state), _signal_line(state),
_market_state_line(state), _market_state_line(state),
_entry_block_line(state), _entry_block_line(state),
_execution_quality_line(state),
*_signal_confidence_lines(state), *_signal_confidence_lines(state),
*_execution_block_lines(state), *_execution_block_lines(state),
] ]
@@ -173,8 +174,7 @@ def _build_waiting_text(state) -> str:
"", "",
*signal_lines, *signal_lines,
"", "",
"🧾 <b>Подготовка ордера</b>", "🧾 Подготовка ордера",
"",
_order_header_line(state), _order_header_line(state),
f"<b>{_price_label_for_signal(state)}</b> · {_format_usd_or_dash(price)}", f"<b>{_price_label_for_signal(state)}</b> · {_format_usd_or_dash(price)}",
_estimated_size_text(state, price), _estimated_size_text(state, price),
@@ -217,6 +217,7 @@ def _build_active_position_text(state) -> str:
f"<b>Зарезервировано</b> · $ {_format_money_compact(reserved)}", f"<b>Зарезервировано</b> · $ {_format_money_compact(reserved)}",
f"<b>P&L</b> {_format_signed_usd_with_direction(pnl)}", f"<b>P&L</b> {_format_signed_usd_with_direction(pnl)}",
_market_state_line(state), _market_state_line(state),
_execution_quality_line(state),
*_execution_block_lines(state), *_execution_block_lines(state),
"", "",
( (
@@ -286,7 +287,7 @@ def _entry_block_line(state) -> str:
signal = (state.last_signal or "HOLD").upper() signal = (state.last_signal or "HOLD").upper()
if signal == "HOLD": if signal == "HOLD":
return f"Ожидание · {compact_message}" return f"Условие · {compact_message}"
if signal in {"BUY", "SELL"}: if signal in {"BUY", "SELL"}:
return f"Вход · {compact_message}" return f"Вход · {compact_message}"
@@ -294,12 +295,55 @@ def _entry_block_line(state) -> str:
return "" return ""
def _execution_quality_line(state) -> str:
quality = getattr(state, "execution_quality", None)
reason = getattr(state, "execution_quality_reason", None)
spread_percent = getattr(state, "spread_percent", None)
age_seconds = getattr(state, "snapshot_age_seconds", None)
if not quality:
return ""
if quality == "GOOD":
return ""
if reason == "WIDE_SPREAD" and spread_percent is not None:
return f"⚠️ Рынок · spread {_format_percent(spread_percent)}"
if reason == "AGING_SNAPSHOT" and age_seconds is not None:
return f"⚠️ Рынок · данные стареют ({age_seconds:.1f}с)"
if reason == "STALE_SNAPSHOT":
return "⛔ Вход · рынок неактуален"
if reason == "HIGH_SPREAD" and spread_percent is not None:
return f"⛔ Вход · высокий spread {_format_percent(spread_percent)}"
if reason == "SNAPSHOT_UNAVAILABLE":
return "⚠️ Рынок · нет depth snapshot"
if reason == "SNAPSHOT_ERROR":
return "⛔ Вход · нет данных рынка"
message = getattr(state, "execution_quality_message", None)
if not message:
return ""
return f"⚠️ Рынок · {message}"
def _execution_block_lines(state) -> list[str]: def _execution_block_lines(state) -> list[str]:
lines: list[str] = [] lines: list[str] = []
reason = getattr(state, "execution_block_reason", None) reason = getattr(state, "execution_block_reason", None)
if reason: if reason and reason not in {
lines.append(f"Исполнение · {reason}") "высокий spread",
"spread повышен",
"snapshot устарел",
"рынок неактуален",
}:
lines.append(f"Вход · {reason}")
adjustment = getattr(state, "execution_size_adjustment_reason", None) adjustment = getattr(state, "execution_size_adjustment_reason", None)

View File

@@ -13,6 +13,7 @@ from src.trading.execution.engine import ExecutionEngine
from src.trading.journal.service import JournalService from src.trading.journal.service import JournalService
from src.trading.strategies.base import BaseStrategy, StrategyContext from src.trading.strategies.base import BaseStrategy, StrategyContext
from src.trading.strategies.registry import StrategyRegistry from src.trading.strategies.registry import StrategyRegistry
from src.integrations.exchange.service import ExchangeService
class AutoTradeService: class AutoTradeService:
@@ -42,6 +43,12 @@ class AutoTradeService:
_last_logged_entry_block_reason: str | None = None _last_logged_entry_block_reason: str | None = None
_same_signal_count = 0 _same_signal_count = 0
_max_snapshot_age_seconds = 5.0
_warning_snapshot_age_seconds = 2.0
_max_spread_percent = 0.15
_warning_spread_percent = 0.08
_last_logged_execution_quality_key: str | None = None
# debug: принудительно выставить сигнал и decision # debug: принудительно выставить сигнал и decision
def debug_force_signal( def debug_force_signal(
self, self,
@@ -325,6 +332,12 @@ class AutoTradeService:
state.entry_block_message = None state.entry_block_message = None
state.runtime_expired_reason = None state.runtime_expired_reason = None
state.runtime_expired_message = None state.runtime_expired_message = None
state.snapshot_age_seconds = None
state.spread_percent = None
state.execution_quality = None
state.execution_quality_reason = None
state.execution_quality_message = None
state.market_runtime_degraded = False
# собрать контекст для стратегии # собрать контекст для стратегии
def _build_strategy_context(self) -> StrategyContext: def _build_strategy_context(self) -> StrategyContext:
@@ -952,6 +965,203 @@ class AutoTradeService:
except Exception: except Exception:
pass pass
def _sync_execution_quality_state(self, state: AutoTradeState) -> None:
try:
snapshot = ExchangeService().get_market_snapshot(
state.symbol,
runtime_key="auto",
)
except Exception as exc:
fallback_price = None
try:
fallback_price = float(
ExchangeService().get_price(
state.symbol,
runtime_key="auto",
).price
)
except Exception:
pass
state.snapshot_age_seconds = None
state.spread_percent = None
if fallback_price is not None and fallback_price > 0:
state.execution_quality = "WARNING"
state.execution_quality_reason = "SNAPSHOT_UNAVAILABLE"
state.execution_quality_message = "нет depth snapshot"
state.market_runtime_degraded = True
else:
state.execution_quality = "BLOCKED"
state.execution_quality_reason = "SNAPSHOT_ERROR"
state.execution_quality_message = "нет market data"
state.market_runtime_degraded = True
self._log_execution_quality_if_changed(
state=state,
payload={
"error": str(exc),
"error_type": type(exc).__name__,
"fallback_price_available": fallback_price is not None,
},
)
return
bid_price = self._safe_float(snapshot.get("bid_price"))
ask_price = self._safe_float(snapshot.get("ask_price"))
last_price = self._safe_float(snapshot.get("last_price"))
age_seconds = self._safe_float(snapshot.get("age_seconds"))
is_fresh = bool(snapshot.get("is_fresh", False))
source = str(snapshot.get("source") or "")
state.snapshot_age_seconds = age_seconds
state.spread_percent = self._spread_percent(
bid_price=bid_price,
ask_price=ask_price,
)
if age_seconds is not None and age_seconds > self._max_snapshot_age_seconds:
state.execution_quality = "BLOCKED"
state.execution_quality_reason = "STALE_SNAPSHOT"
state.execution_quality_message = "snapshot устарел"
state.market_runtime_degraded = True
elif state.spread_percent is not None and state.spread_percent > self._max_spread_percent:
state.execution_quality = "BLOCKED"
state.execution_quality_reason = "HIGH_SPREAD"
state.execution_quality_message = "высокий spread"
state.market_runtime_degraded = False
elif age_seconds is not None and age_seconds > self._warning_snapshot_age_seconds:
state.execution_quality = "WARNING"
state.execution_quality_reason = "AGING_SNAPSHOT"
state.execution_quality_message = "snapshot стареет"
state.market_runtime_degraded = not is_fresh
elif state.spread_percent is not None and state.spread_percent > self._warning_spread_percent:
state.execution_quality = "WARNING"
state.execution_quality_reason = "WIDE_SPREAD"
state.execution_quality_message = "spread повышен"
state.market_runtime_degraded = False
else:
state.execution_quality = "GOOD"
state.execution_quality_reason = "MARKET_OK"
state.execution_quality_message = "рынок готов"
state.market_runtime_degraded = False
if state.execution_quality == "BLOCKED":
state.execution_block_reason = state.execution_quality_message
elif state.execution_block_reason == state.execution_quality_message:
state.execution_block_reason = None
self._log_execution_quality_if_changed(
state=state,
payload={
"symbol": state.symbol,
"strategy": state.strategy,
"bid_price": bid_price,
"ask_price": ask_price,
"last_price": last_price,
"snapshot_age_seconds": age_seconds,
"spread_percent": state.spread_percent,
"is_fresh": is_fresh,
"source": source,
"execution_quality": state.execution_quality,
"execution_quality_reason": state.execution_quality_reason,
"execution_quality_message": state.execution_quality_message,
"market_runtime_degraded": state.market_runtime_degraded,
"max_snapshot_age_seconds": self._max_snapshot_age_seconds,
"warning_snapshot_age_seconds": self._warning_snapshot_age_seconds,
"max_spread_percent": self._max_spread_percent,
"warning_spread_percent": self._warning_spread_percent,
},
)
def _spread_percent(
self,
*,
bid_price: float | None,
ask_price: float | None,
) -> float | None:
if bid_price is None or ask_price is None:
return None
if bid_price <= 0 or ask_price <= 0:
return None
mid_price = (bid_price + ask_price) / 2
if mid_price <= 0:
return None
spread = ask_price - bid_price
if spread < 0:
return None
return round((spread / mid_price) * 100, 5)
def _safe_float(self, value: object) -> float | None:
if value is None:
return None
try:
return float(value)
except (TypeError, ValueError):
return None
def _log_execution_quality_if_changed(
self,
*,
state: AutoTradeState,
payload: dict,
) -> None:
quality = state.execution_quality
reason = state.execution_quality_reason
message = state.execution_quality_message
if not quality or not reason or not message:
return
key = f"{state.status}:{state.symbol}:{state.strategy}:{quality}:{reason}:{message}"
if key == type(self)._last_logged_execution_quality_key:
return
type(self)._last_logged_execution_quality_key = key
if quality == "GOOD":
return
try:
log_payload = {
**payload,
"status": state.status,
"symbol": state.symbol,
"strategy": state.strategy,
}
if quality == "BLOCKED":
JournalService().log_ui_warning(
event_type="execution_quality_changed",
message=f"Качество исполнения: {message}.",
screen="auto",
action="execution_quality",
payload=log_payload,
)
return
JournalService().log_ui_info(
event_type="execution_quality_changed",
message=f"Качество исполнения: {message}.",
screen="auto",
action="execution_quality",
payload=log_payload,
)
except Exception:
pass
def run_cycle(self) -> AutoTradeState: def run_cycle(self) -> AutoTradeState:
state = self.get_state() state = self.get_state()
@@ -969,6 +1179,8 @@ class AutoTradeService:
payload=result.payload, payload=result.payload,
) )
self._sync_execution_quality_state(state)
state.last_check_at = datetime.now().strftime("%H:%M:%S") state.last_check_at = datetime.now().strftime("%H:%M:%S")
self._log_signal_if_changed( self._log_signal_if_changed(
@@ -980,6 +1192,7 @@ class AutoTradeService:
payload=result.payload, payload=result.payload,
) )
ExecutionEngine().process(state) if state.execution_quality != "BLOCKED":
ExecutionEngine().process(state)
return state return state

View File

@@ -137,4 +137,22 @@ class AutoTradeState:
runtime_expired_reason: str | None = None runtime_expired_reason: str | None = None
# человекочитаемое сообщение runtime expiration # человекочитаемое сообщение runtime expiration
runtime_expired_message: str | None = None runtime_expired_message: str | None = None
# возраст последнего market snapshot в секундах
snapshot_age_seconds: float | None = None
# spread между bid/ask в %
spread_percent: float | None = None
# качество рынка для исполнения: GOOD / WARNING / BLOCKED / UNKNOWN
execution_quality: str | None = None
# код причины качества исполнения
execution_quality_reason: str | None = None
# человекочитаемое объяснение качества исполнения
execution_quality_message: str | None = None
# признак деградации runtime market data
market_runtime_degraded: bool = False

View File

@@ -575,6 +575,44 @@
- подготовлена база для advanced market diagnostics - подготовлена база для advanced market diagnostics
- подготовлена база для multi-timeframe analysis - подготовлена база для multi-timeframe analysis
#### 07.4.4.1.8 ✅ Execution Freshness & Market Quality Layer
- добавлен слой execution freshness diagnostics
- добавлен слой market quality diagnostics
- AutoTradeState расширен execution quality полями
- добавлены execution_quality и execution_quality_reason
- добавлены execution_quality_message, spread_percent и snapshot_age_seconds
- AutoTradeService начал синхронизировать execution quality в runtime state
- добавлена проверка наличия market snapshot
- добавлена диагностика SNAPSHOT_ERROR
- добавлена диагностика SNAPSHOT_UNAVAILABLE
- добавлена диагностика AGING_SNAPSHOT
- добавлена диагностика STALE_SNAPSHOT
- stale snapshot теперь блокирует вход
- aging snapshot теперь отображается как warning
- нормальный snapshot age больше не засоряет Telegram UI
- age отображается только для AGING_SNAPSHOT / STALE_SNAPSHOT сценариев
- добавлен расчёт bid/ask spread
- добавлен spread warning layer
- добавлен spread block layer
- высокий spread теперь блокирует вход
- повышенный spread теперь отображается как market warning
- execution diagnostics отделены от strategy entry diagnostics
- UI разделяет Условие, Рынок и Вход
- термин Исполнение заменён на более короткий Вход
- `нет market data` заменено на `нет данных рынка`
- Telegram UI получил строку market quality warning
- Telegram UI получил строку execution block по качеству рынка
- wide spread отображается компактно как `Рынок · spread ...`
- high spread отображается как `Вход · высокий spread ...`
- устранено дублирование age при нормальном snapshot
- добавлена база для spread-aware execution
- добавлена база для stale snapshot protection
- добавлена база для slippage protection
- добавлена база для execution quality analytics
- добавлена база для instrument quality scoring
- выявлена необходимость Spread Hysteresis Layer
- подготовлен следующий этап 07.4.4.1.8.1 Spread Hysteresis Layer
--- ---
### 07.4.5 ### 07.4.5

View File

@@ -551,6 +551,44 @@
- подготовлена база для advanced market diagnostics - подготовлена база для advanced market diagnostics
- подготовлена база для multi-timeframe analysis - подготовлена база для multi-timeframe analysis
#### 07.4.4.1.8 ✅ Execution Freshness & Market Quality Layer
- добавлен слой execution freshness diagnostics
- добавлен слой market quality diagnostics
- AutoTradeState расширен execution quality полями
- добавлены execution_quality и execution_quality_reason
- добавлены execution_quality_message, spread_percent и snapshot_age_seconds
- AutoTradeService начал синхронизировать execution quality в runtime state
- добавлена проверка наличия market snapshot
- добавлена диагностика SNAPSHOT_ERROR
- добавлена диагностика SNAPSHOT_UNAVAILABLE
- добавлена диагностика AGING_SNAPSHOT
- добавлена диагностика STALE_SNAPSHOT
- stale snapshot теперь блокирует вход
- aging snapshot теперь отображается как warning
- нормальный snapshot age больше не засоряет Telegram UI
- age отображается только для AGING_SNAPSHOT / STALE_SNAPSHOT сценариев
- добавлен расчёт bid/ask spread
- добавлен spread warning layer
- добавлен spread block layer
- высокий spread теперь блокирует вход
- повышенный spread теперь отображается как market warning
- execution diagnostics отделены от strategy entry diagnostics
- UI разделяет Условие, Рынок и Вход
- термин Исполнение заменён на более короткий Вход
- `нет market data` заменено на `нет данных рынка`
- Telegram UI получил строку market quality warning
- Telegram UI получил строку execution block по качеству рынка
- wide spread отображается компактно как `Рынок · spread ...`
- high spread отображается как `Вход · высокий spread ...`
- устранено дублирование age при нормальном snapshot
- добавлена база для spread-aware execution
- добавлена база для stale snapshot protection
- добавлена база для slippage protection
- добавлена база для execution quality analytics
- добавлена база для instrument quality scoring
- выявлена необходимость Spread Hysteresis Layer
- подготовлен следующий этап 07.4.4.1.8.1 Spread Hysteresis Layer
--- ---
### 07.4.5 ### 07.4.5

View File

@@ -0,0 +1,663 @@
# 07.4.4.1.8 — Execution Freshness & Market Quality Layer
## Статус
Этап завершён.
## Цель этапа
Добавить отдельный слой проверки качества рынка перед входом в позицию.
Этап 07.4.4.1.7 уже перевёл автоторговлю на live market runtime, websocket snapshots, REST fallback и bid/ask-aware анализ. Но после этого оставалась важная проблема: стратегия могла видеть корректный тренд и live-импульс, но сама возможность безопасного исполнения сделки ещё не оценивалась отдельно.
Этап 07.4.4.1.8 добавляет execution freshness и market quality diagnostics:
- проверку наличия market snapshot;
- проверку свежести snapshot;
- проверку возраста bid/ask данных;
- расчёт spread;
- разделение warning и blocking состояний;
- отображение качества рынка в Telegram UI;
- блокировку входа при плохих execution-условиях;
- human-readable диагностику причин, почему вход сейчас небезопасен.
---
# Что было до этапа
До внедрения этого слоя система уже умела:
- получать live bid/ask через MarketDataRunner;
- хранить snapshot в MarketPriceCache;
- использовать market snapshot в TrendStrategy;
- показывать причину HOLD;
- защищать runtime loop от падения.
Но execution layer ещё не отвечал на вопросы:
```text
А можно ли прямо сейчас безопасно открыть позицию?
```
Например, могли возникать ситуации:
```text
Сигнал готов
Рынок трендовый
Импульс подтверждён
```
но при этом:
- bid/ask snapshot устарел;
- spread стал слишком широким;
- websocket lagging;
- REST fallback дал данные, но они уже начали стареть;
- market data временно недоступны.
До этого такие ситуации были видны только косвенно или не были видны в UI совсем.
---
# Что внедрено
## 1. Execution quality layer
В runtime автоторговли добавлен отдельный слой оценки качества исполнения.
Теперь система анализирует не только торговый сигнал, но и качество рынка для входа:
```text
Strategy signal
Market analysis
Live impulse diagnostics
Execution freshness & market quality
Execution decision
```
Это отделяет две разные сущности:
- стратегия может быть права по направлению;
- рынок может быть плохим для исполнения.
---
## 2. Snapshot availability check
Добавлена проверка наличия market snapshot.
Если snapshot отсутствует, система не должна считать вход безопасным.
Диагностика:
```text
⛔ Вход · нет данных рынка
```
Это означает, что в текущий момент нет достаточных данных bid/ask для безопасной оценки цены входа.
---
## 3. Snapshot age tracking
Система начала учитывать возраст market snapshot.
`age` — это возраст последнего market snapshot в секундах.
Пример:
```text
age 0.6с
```
означает, что последние bid/ask данные были получены 0.6 секунды назад.
Это важно, потому что при автоторговле нельзя входить по данным, которые выглядят валидными, но фактически уже устарели.
---
## 4. AGING_SNAPSHOT
Добавлено промежуточное состояние:
```text
AGING_SNAPSHOT
```
Оно означает:
```text
snapshot ещё можно использовать, но он начинает стареть
```
Это warning-состояние, а не обязательно блокировка.
UI может показать:
```text
⚠️ Рынок · данные стареют (2.8с)
```
Смысл:
- market runtime жив;
- данные ещё есть;
- но задержка уже повышена;
- вход становится менее надёжным.
---
## 5. STALE_SNAPSHOT
Добавлено критическое состояние:
```text
STALE_SNAPSHOT
```
Оно означает:
```text
snapshot уже слишком старый для безопасного входа
```
В этом случае execution должен быть заблокирован.
UI:
```text
⛔ Вход · рынок неактуален
```
Смысл:
- последние bid/ask данные могли устареть;
- текущая цена могла измениться;
- риск slippage повышен;
- вход по такой цене небезопасен.
---
## 6. Spread calculation
Добавлен расчёт spread между bid и ask.
Формула:
```text
spread_percent = ((ask - bid) / mid_price) * 100
mid_price = (ask + bid) / 2
```
Теперь система понимает, насколько дорого входить в рынок прямо сейчас.
---
## 7. Spread warning layer
Добавлено warning-состояние для повышенного spread.
UI:
```text
⚠️ Рынок · spread 0.1%
```
Это означает:
- вход ещё не обязательно запрещён;
- но рынок стал менее качественным;
- bid/ask разъехались;
- скрытая стоимость входа выросла.
Для торговли это значит:
- хуже цена входа;
- выше вероятность мгновенного минуса после открытия;
- выше риск slippage;
- слабая ликвидность по текущему инструменту.
---
## 8. Spread block layer
Добавлено blocking-состояние для слишком высокого spread.
UI:
```text
⛔ Вход · высокий spread 0.17%
```
Это означает:
```text
сигнал может быть валидным, но вход сейчас запрещён из-за плохих рыночных условий
```
Такой слой особенно важен для менее ликвидных инструментов, например LTC, где spread может быстро расширяться.
---
## 9. Разделение WARNING и BLOCKED
Слой качества рынка разделён на два уровня:
```text
GOOD — рынок нормальный
WARNING — рынок ухудшился, но вход ещё может быть допустим
BLOCKED — вход запрещён
```
Это важно, потому что не каждый широкий spread должен немедленно блокировать систему.
Пример:
```text
⚠️ Рынок · spread 0.1%
```
это warning.
А:
```text
⛔ Вход · высокий spread 0.17%
```
это уже block.
---
# Что изменилось в AutoTradeState
В состояние автоторговли добавлены поля execution quality diagnostics.
Они нужны, чтобы UI, execution engine и journal могли видеть одну и ту же runtime-оценку рынка.
Логически добавлены поля такого уровня:
```python
execution_quality
execution_quality_reason
execution_quality_message
spread_percent
snapshot_age_seconds
```
Их назначение:
- `execution_quality` — общий статус качества рынка;
- `execution_quality_reason` — машинный код причины;
- `execution_quality_message` — короткое human-readable сообщение;
- `spread_percent` — текущий spread в процентах;
- `snapshot_age_seconds` — возраст market snapshot.
---
# Что изменилось в AutoTradeService
## 1. Добавлена синхронизация execution quality
В AutoTradeService добавлен слой синхронизации execution quality state.
Он получает market snapshot, рассчитывает качество рынка и сохраняет результат в AutoTradeState.
Теперь state содержит не только:
```text
last_signal
market_state
entry_block_reason
```
но и:
```text
execution_quality
execution_quality_reason
spread_percent
snapshot_age_seconds
```
---
## 2. Execution quality теперь обновляется в runtime cycle
В `run_cycle()` execution quality проверяется каждый цикл автоторговли.
Это значит, что Telegram UI может показывать актуальное состояние рынка даже когда стратегия остаётся в HOLD.
---
## 3. Execution block reason получил market-quality причины
Если рынок плохой для входа, state получает execution block reason.
Например:
```text
высокий spread
нет данных рынка
рынок неактуален
```
Это не strategy diagnostics, а именно execution diagnostics.
---
# Что изменилось в Telegram UI
## 1. Добавлена строка качества рынка
В UI появилась отдельная строка:
```text
⚠️ Рынок · spread 0.1%
```
Она показывает warning по качеству рынка.
---
## 2. Добавлена строка блокировки входа
Если execution quality блокирует вход, UI показывает:
```text
⛔ Вход · высокий spread 0.17%
```
Это означает, что проблема не в стратегии, а в условиях исполнения.
---
## 3. Убрана лишняя детализация age при нормальном snapshot
Ранее строка могла выглядеть слишком длинно:
```text
⚠️ Рынок · spread повышен (spread 0.12%, age 0.8с)
```
После правки нормальный age не показывается.
Теперь:
```text
⚠️ Рынок · spread 0.12%
```
Age показывается только когда он важен:
```text
⚠️ Рынок · данные стареют (2.8с)
⛔ Вход · рынок неактуален
```
---
## 4. Терминология UI приведена к единому стилю
Используются короткие роли строк:
```text
Сигнал — сырой сигнал стратегии
Тренд — состояние рынка
Условие — почему стратегия пока не дала вход
Рынок — качество рыночных данных / spread warning
Вход — блокировка входа в позицию
```
Это сделало UI компактнее и понятнее.
---
# Улучшения аналитики
## 1. Strategy diagnostics отделены от execution diagnostics
Теперь система различает:
```text
Условие · слабый импульс
```
и:
```text
⛔ Вход · высокий spread
```
Первое означает:
```text
стратегия пока не видит достаточного импульса
```
Второе означает:
```text
даже если сигнал появится, вход сейчас небезопасен
```
Это критично для будущей аналитики, потому что причины отсутствия сделки теперь можно классифицировать отдельно.
---
## 2. Добавлена аналитика spread как качества рынка
Spread теперь рассматривается не как техническая деталь стакана, а как полноценный market quality metric.
Это позволяет в будущем строить аналитику:
- какие активы чаще имеют высокий spread;
- в какие часы execution quality ухудшается;
- какие сигналы были заблокированы из-за плохой ликвидности;
- где стратегия была права, но рынок был плохой для входа.
---
## 3. Добавлена аналитика freshness
Возраст snapshot теперь является отдельной метрикой.
Это позволяет отличать:
```text
рынок плохой из-за spread
```
от:
```text
рынок плохой из-за старых данных
```
Для live autotrading это разные причины риска.
---
## 4. Подготовка к slippage protection
Spread diagnostics создаёт основу для будущей защиты от slippage.
Если spread растёт, то реальная цена входа может заметно отличаться от ожидаемой.
Теперь система может заранее остановить вход до того, как execution откроет позицию по плохой цене.
---
## 5. Подготовка к instrument quality scoring
На примере LTC стало видно, что разные активы имеют разную норму spread.
Этап подготовил основу для future scoring:
```text
BTC — stricter spread thresholds
LTC — wider normal spread range
XRP — отдельная ликвидность
ETH — отдельный профиль
```
Это будет важно для adaptive thresholds и per-symbol execution profiles.
---
## 6. Подготовка к hysteresis
В ходе проверки стало видно, что spread может колебаться около порога.
Из-за этого UI может переключаться между:
```text
⚠️ Рынок · spread 0.1%
```
и:
```text
⛔ Вход · высокий spread 0.17%
```
Это не ошибка, а следствие отсутствия hysteresis.
Следующий этап:
```text
07.4.4.1.8.1 — Spread Hysteresis Layer
```
должен добавить зоны стабилизации, чтобы UI и execution state не мигали на границе порогов.
---
# Что было проверено
Проверено на live UI:
- market quality line отображается;
- spread warning отображается компактно;
- высокий spread блокирует вход;
- age не показывается при нормальном snapshot;
- snapshot age остаётся доступен для AGING / STALE сценариев;
- терминология `Исполнение` заменена на более короткое `Вход`;
- `нет market data` заменено на `нет данных рынка`;
- strategy diagnostics и execution diagnostics отображаются разными строками;
- HOLD timer продолжает работать как runtime heartbeat;
- live screen обновляется после правок runner lifecycle;
- LTC корректно показывает spread diagnostics.
---
# Примеры UI после этапа
## Spread warning
```text
Сигнал 🟡 HOLD · 8м 13с
📈 Тренд · Вверх
Условие · слабый импульс
⚠️ Рынок · spread 0.1%
```
## Spread block
```text
Сигнал 🟡 HOLD · 54с
📈 Тренд · Вверх
Условие · слабый импульс
⛔ Вход · высокий spread 0.17%
```
## Нет данных рынка
```text
⛔ Вход · нет данных рынка
```
## Устаревший рынок
```text
⛔ Вход · рынок неактуален
```
---
# Что подготовлено для следующих этапов
Этап подготовил базу для:
```text
Spread Hysteresis Layer
per-symbol spread thresholds
adaptive execution quality
slippage protection
liquidity quality scoring
instrument quality profiles
market quality analytics
execution block analytics
freshness-aware execution engine
spread-aware signal confidence
```
---
# Основные изменённые файлы
- `app/src/trading/auto/service.py`
- `app/src/trading/auto/state.py`
- `app/src/trading/market_analysis/service.py`
- `app/src/trading/market_analysis/models.py`
- `app/src/integrations/exchange/models.py`
- `app/src/telegram/handlers/auto/ui.py`
- `app/src/integrations/exchange/market_data_runner.py`
- `app/src/integrations/exchange/ws_client.py`
---
# Проверка перед commit
Рекомендуемая проверка:
```bash
python -m compileall src
python -m src.main
```
После запуска проверить:
1. Экран автоторговли обновляется автоматически.
2. HOLD timer продолжает тикать.
3. Market state отображается корректно.
4. При нормальном snapshot age не отображается.
5. При warning spread показывается строка `⚠️ Рынок · spread ...`.
6. При block spread показывается строка `⛔ Вход · высокий spread ...`.
7. Сообщение `нет market data` больше не используется.
8. Вместо него отображается `⛔ Вход · нет данных рынка`.
9. Strategy diagnostics отображаются через `Условие`.
10. Execution block diagnostics отображаются через `Вход`.
---
# Итог этапа
Этап 07.4.4.1.8 добавил важный слой между стратегией и исполнением.
Теперь автоторговля оценивает не только направление рынка и силу сигнала, но и качество текущего рынка для входа.
Система стала безопаснее:
- не входит без актуальных данных;
- видит возраст snapshot;
- оценивает spread;
- предупреждает о плохом рынке;
- блокирует вход при плохом execution quality;
- показывает пользователю понятную причину.
Это подготовило автоторговлю к следующему уровню: hysteresis, adaptive execution thresholds и per-symbol market quality profiles.