07.4.4.1.4 Entry Decision Diagnostics Layer

This commit is contained in:
2026-05-11 11:22:10 +03:00
parent b5d931bbb7
commit 363719cc8e
8 changed files with 456 additions and 7 deletions

View File

@@ -31,6 +31,9 @@ EVENT_TITLES = {
"market_stream_disconnected": "Рынок",
"market_symbol_changed": "Рынок",
# Мониторинг позиций
"entry_blocked": "Вход в позицию",
# Журнал
"journal_exported": "Журнал",
"journal_export_error": "Журнал",

View File

@@ -155,16 +155,23 @@ def _build_waiting_text(state) -> str:
entry_price_override=price,
)
signal_lines = [
_signal_line(state),
_market_state_line(state),
_entry_block_line(state),
*_signal_confidence_lines(state),
*_execution_block_lines(state),
]
signal_lines = [line for line in signal_lines if line]
parts = [
f"🤖 Автоторговля {_status_text(state.status)}",
_account_mode_line(),
"",
f"<b>Доступно</b> · $ {_format_money_compact(available)}",
"",
_signal_line(state),
_market_state_line(state),
*_signal_confidence_lines(state),
*_execution_block_lines(state),
*signal_lines,
"",
"🧾 <b>Подготовка ордера</b>",
"",
@@ -253,16 +260,28 @@ def _market_state_line(state) -> str:
return labels.get(market_state, "⏳ Рынок · Идёт анализ")
def _entry_block_line(state) -> str:
message = getattr(state, "entry_block_message", None)
if not message:
return ""
return f"Вход в позицию · {message}"
def _execution_block_lines(state) -> list[str]:
lines: list[str] = []
reason = getattr(state, "execution_block_reason", None)
if reason:
lines.append(f"Blocked · {reason}")
lines.append(f"Исполнение · {reason}")
adjustment = getattr(state, "execution_size_adjustment_reason", None)
if adjustment == "MARGIN_LIMIT":
lines.append("Size adjusted by Max Reserved")
lines.append(
"Позиция ограничена настройкой Max Reserved."
)
return lines

View File

@@ -35,6 +35,7 @@ class AutoTradeService:
_last_logged_market_state: str | None = None
_last_logged_market_trend: str | None = None
_last_logged_market_volatility: str | None = None
_last_logged_entry_block_reason: str | None = None
_same_signal_count = 0
# debug: принудительно выставить сигнал и decision
@@ -663,6 +664,8 @@ class AutoTradeService:
state.market_volatility = payload.get("market_volatility")
state.market_analysis_interval = payload.get("market_analysis_interval")
state.market_analysis_reason = payload.get("market_analysis_reason")
state.entry_block_reason = payload.get("entry_block_reason")
state.entry_block_message = payload.get("entry_block_message")
self._log_market_state_if_changed(
state=state,
@@ -672,6 +675,48 @@ class AutoTradeService:
previous_market_volatility=previous_market_volatility,
)
self._log_entry_block_if_changed(
state=state,
payload=payload,
)
def _log_entry_block_if_changed(
self,
*,
state: AutoTradeState,
payload: dict,
) -> None:
reason = state.entry_block_reason
message = state.entry_block_message
if not reason or not message:
return
key = f"{state.status}:{state.symbol}:{state.strategy}:{reason}:{message}"
if key == type(self)._last_logged_entry_block_reason:
return
type(self)._last_logged_entry_block_reason = key
try:
JournalService().log_ui_info(
event_type="entry_blocked",
message=f"Вход в позицию не выполнен: {message}.",
screen="auto",
action="entry_diagnostics",
payload={
**payload,
"entry_block_reason": reason,
"entry_block_message": message,
"symbol": state.symbol,
"strategy": state.strategy,
"status": state.status,
},
)
except Exception:
pass
def _log_market_state_if_changed(
self,
*,

View File

@@ -119,4 +119,10 @@ class AutoTradeState:
market_analysis_interval: str | None = None
# объяснение последнего анализа рынка
market_analysis_reason: str | None = None
market_analysis_reason: str | None = None
# код причины, почему вход в позицию сейчас не выполнен
entry_block_reason: str | None = None
# человекочитаемое объяснение причины не входа
entry_block_message: str | None = None

View File

@@ -92,6 +92,8 @@ class TrendStrategy:
payload={
**base_payload,
"market_filter_blocked": True,
"entry_block_reason": "MARKET_FILTER_BLOCKED",
"entry_block_message": "рынок сейчас не подходит для входа",
},
)