07.4.4.1.8 Execution Freshness and Market Quality Layer
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user