Stage 07.4.3.7 — Alert Priority & UX Improvements
This commit is contained in:
@@ -8,8 +8,8 @@ from aiogram.types import Message
|
||||
from src.core.config import load_settings
|
||||
from src.trading.auto.runner import AutoTradeRunner
|
||||
from src.trading.auto.service import AutoTradeService
|
||||
from src.trading.journal.service import JournalService
|
||||
from src.trading.execution.engine import ExecutionEngine
|
||||
from src.trading.journal.service import JournalService
|
||||
|
||||
|
||||
router = Router(name="debug")
|
||||
@@ -19,21 +19,81 @@ def _debug_enabled() -> bool:
|
||||
return load_settings().debug_enabled
|
||||
|
||||
|
||||
def _parse_debug_signal_args(raw_text: str | None) -> tuple[str, float, int, str | None]:
|
||||
parts = (raw_text or "").split()
|
||||
|
||||
signal = parts[1].upper() if len(parts) > 1 else "BUY"
|
||||
if signal not in {"BUY", "SELL", "HOLD"}:
|
||||
return "BUY", 0.9, 2, "SIGNAL должен быть BUY, SELL или HOLD."
|
||||
|
||||
try:
|
||||
confidence = float(parts[2]) if len(parts) > 2 else 0.9
|
||||
except ValueError:
|
||||
return "BUY", 0.9, 2, "CONFIDENCE должен быть числом от 0.00 до 1.00."
|
||||
|
||||
if confidence < 0 or confidence > 1:
|
||||
return "BUY", 0.9, 2, "CONFIDENCE должен быть от 0.00 до 1.00."
|
||||
|
||||
try:
|
||||
repeat_count = int(parts[3]) if len(parts) > 3 else 2
|
||||
except ValueError:
|
||||
return "BUY", 0.9, 2, "REPEATS должен быть целым числом."
|
||||
|
||||
if repeat_count < 1:
|
||||
return "BUY", 0.9, 2, "REPEATS должен быть больше или равен 1."
|
||||
|
||||
return signal, confidence, repeat_count, None
|
||||
|
||||
|
||||
def _debug_help_text() -> str:
|
||||
return (
|
||||
"<b>🧪 Debug commands</b>\n\n"
|
||||
"<b>Основная команда:</b>\n"
|
||||
"/debug_signal BUY 0.95 3\n"
|
||||
"/debug_signal SELL 0.70 2\n"
|
||||
"/debug_signal HOLD 0.00 1\n\n"
|
||||
"<b>Быстрые команды:</b>\n"
|
||||
"/debug_signal — BUY 0.90 2\n"
|
||||
"/debug_ready — READY BUY\n"
|
||||
"/debug_state — текущее состояние\n"
|
||||
"/debug_help — список команд\n\n"
|
||||
"<b>Priority тест:</b>\n"
|
||||
"HIGH: confidence >= 0.80 и repeats >= 3\n"
|
||||
"MEDIUM: confidence >= 0.60 или repeats >= 2\n"
|
||||
"LOW: всё остальное"
|
||||
)
|
||||
|
||||
|
||||
@router.message(F.text == "/debug_help")
|
||||
async def debug_help(message: Message) -> None:
|
||||
if not _debug_enabled():
|
||||
await message.answer("Debug mode выключен.")
|
||||
return
|
||||
|
||||
await message.answer(_debug_help_text())
|
||||
|
||||
|
||||
@router.message(F.text.startswith("/debug_signal"))
|
||||
async def debug_signal(message: Message) -> None:
|
||||
if not _debug_enabled():
|
||||
await message.answer("Debug mode выключен.")
|
||||
return
|
||||
|
||||
parts = (message.text or "").split()
|
||||
signal = parts[1].upper() if len(parts) > 1 else "BUY"
|
||||
signal, confidence, repeat_count, error = _parse_debug_signal_args(message.text)
|
||||
|
||||
if error is not None:
|
||||
await message.answer(
|
||||
f"⛔️ {error}\n\n"
|
||||
f"{_debug_help_text()}"
|
||||
)
|
||||
return
|
||||
|
||||
service = AutoTradeService()
|
||||
state = service.debug_force_signal(
|
||||
signal=signal,
|
||||
confidence=0.9,
|
||||
repeat_count=2,
|
||||
reason=f"DEBUG FORCE {signal}",
|
||||
confidence=confidence,
|
||||
repeat_count=repeat_count,
|
||||
reason=f"DEBUG FORCE {signal} {confidence:.2f} ×{repeat_count}",
|
||||
)
|
||||
|
||||
if state.status == "OFF":
|
||||
@@ -41,7 +101,7 @@ async def debug_signal(message: Message) -> None:
|
||||
|
||||
await AutoTradeRunner._handle_important_event(state)
|
||||
|
||||
ExecutionEngine().process(state)
|
||||
execution_result = ExecutionEngine().process(state)
|
||||
|
||||
AutoTradeRunner.start()
|
||||
|
||||
@@ -57,6 +117,9 @@ async def debug_signal(message: Message) -> None:
|
||||
"decision_status": state.decision_status,
|
||||
"confidence": state.last_signal_confidence,
|
||||
"repeat_count": state.last_signal_repeat_count,
|
||||
"execution_action": execution_result.action,
|
||||
"execution_can_execute": execution_result.can_execute,
|
||||
"execution_reason": execution_result.reason,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -65,7 +128,10 @@ async def debug_signal(message: Message) -> None:
|
||||
f"Signal: {state.last_signal}\n"
|
||||
f"Decision: {state.decision_status}\n"
|
||||
f"Confidence: {state.last_signal_confidence:.2f}\n"
|
||||
f"Repeats: {state.last_signal_repeat_count}"
|
||||
f"Repeats: {state.last_signal_repeat_count}\n\n"
|
||||
f"Execution: {execution_result.action}\n"
|
||||
f"Can execute: {execution_result.can_execute}\n"
|
||||
f"Reason: {execution_result.reason}"
|
||||
)
|
||||
|
||||
|
||||
@@ -79,8 +145,8 @@ async def debug_ready(message: Message) -> None:
|
||||
state = service.debug_force_signal(
|
||||
signal="BUY",
|
||||
confidence=0.95,
|
||||
repeat_count=2,
|
||||
reason="DEBUG READY BUY",
|
||||
repeat_count=3,
|
||||
reason="DEBUG READY BUY 0.95 ×3",
|
||||
)
|
||||
|
||||
if state.status == "OFF":
|
||||
@@ -88,14 +154,16 @@ async def debug_ready(message: Message) -> None:
|
||||
|
||||
await AutoTradeRunner._handle_important_event(state)
|
||||
|
||||
ExecutionEngine().process(state)
|
||||
execution_result = ExecutionEngine().process(state)
|
||||
|
||||
AutoTradeRunner.start()
|
||||
|
||||
await message.answer(
|
||||
"✅ Debug READY создан\n\n"
|
||||
f"Signal: {state.last_signal}\n"
|
||||
f"Decision: {state.decision_status}"
|
||||
f"Decision: {state.decision_status}\n"
|
||||
f"Execution: {execution_result.action}\n"
|
||||
f"Can execute: {execution_result.can_execute}"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -185,6 +185,11 @@ class AutoTradeRunner:
|
||||
leverage = payload.get("leverage") if payload.get("leverage") is not None else state.leverage
|
||||
reason = str(payload.get("reason") or state.last_signal_reason or "—")
|
||||
|
||||
priority = cls._alert_priority(
|
||||
confidence=confidence,
|
||||
repeat_count=repeat_count,
|
||||
)
|
||||
|
||||
alert_key = (
|
||||
f"{symbol}:{strategy}:{signal}:"
|
||||
f"{repeat_count}:{confidence:.2f}:"
|
||||
@@ -213,19 +218,15 @@ class AutoTradeRunner:
|
||||
cls._last_strong_alert_key = alert_key
|
||||
cls._last_strong_alert_at_by_key[alert_key] = now
|
||||
|
||||
signal_icon = {
|
||||
"BUY": "🟢",
|
||||
"SELL": "🔴",
|
||||
}.get(signal, "🚨")
|
||||
|
||||
leverage_text = f"x{leverage:g}" if isinstance(leverage, (int, float)) else "—"
|
||||
|
||||
text = (
|
||||
f"<b>🚨 Сильный сигнал {signal_icon} {signal}</b>\n\n"
|
||||
f"{symbol} · {strategy} · {leverage_text}\n"
|
||||
f"Confidence: {confidence:.2f}\n"
|
||||
f"Repeats: {repeat_count}\n\n"
|
||||
f"Причина: {reason}"
|
||||
text = cls._build_strong_signal_alert_text(
|
||||
signal=signal,
|
||||
symbol=symbol,
|
||||
strategy=strategy,
|
||||
repeat_count=repeat_count,
|
||||
confidence=confidence,
|
||||
leverage=leverage,
|
||||
reason=reason,
|
||||
priority=priority,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -247,6 +248,7 @@ class AutoTradeRunner:
|
||||
"confidence": confidence,
|
||||
"leverage": leverage,
|
||||
"reason": reason,
|
||||
"priority": priority,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -289,6 +291,85 @@ class AutoTradeRunner:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _alert_priority(
|
||||
cls,
|
||||
*,
|
||||
confidence: float,
|
||||
repeat_count: int,
|
||||
) -> str:
|
||||
if confidence >= 0.8 and repeat_count >= 3:
|
||||
return "HIGH"
|
||||
|
||||
if confidence >= 0.6 or repeat_count >= 2:
|
||||
return "MEDIUM"
|
||||
|
||||
return "LOW"
|
||||
|
||||
@classmethod
|
||||
def _priority_label(cls, priority: str) -> str:
|
||||
mapping = {
|
||||
"HIGH": "🚨 HIGH",
|
||||
"MEDIUM": "⚡ MEDIUM",
|
||||
"LOW": "ℹ️ LOW",
|
||||
}
|
||||
return mapping.get(priority, priority)
|
||||
|
||||
@classmethod
|
||||
def _format_alert_symbol(cls, symbol: str) -> str:
|
||||
if not symbol or symbol == "—":
|
||||
return "—"
|
||||
|
||||
base_symbol = symbol.split("_", 1)[0]
|
||||
parts = base_symbol.split("/", 1)
|
||||
|
||||
if len(parts) == 2:
|
||||
return f"{parts[0]} / {parts[1]}"
|
||||
|
||||
return base_symbol
|
||||
|
||||
@classmethod
|
||||
def _format_alert_leverage(cls, leverage: object) -> str:
|
||||
if isinstance(leverage, (int, float)):
|
||||
return f"x{leverage:g}"
|
||||
|
||||
return "—"
|
||||
|
||||
@classmethod
|
||||
def _signal_icon(cls, signal: str) -> str:
|
||||
mapping = {
|
||||
"BUY": "🟢",
|
||||
"SELL": "🔴",
|
||||
}
|
||||
return mapping.get(signal, "⚪")
|
||||
|
||||
@classmethod
|
||||
def _build_strong_signal_alert_text(
|
||||
cls,
|
||||
*,
|
||||
signal: str,
|
||||
symbol: str,
|
||||
strategy: str,
|
||||
repeat_count: int,
|
||||
confidence: float,
|
||||
leverage: object,
|
||||
reason: str,
|
||||
priority: str,
|
||||
) -> str:
|
||||
icon = cls._signal_icon(signal)
|
||||
symbol_text = cls._format_alert_symbol(symbol)
|
||||
leverage_text = cls._format_alert_leverage(leverage)
|
||||
priority_text = cls._priority_label(priority)
|
||||
|
||||
return (
|
||||
f"<b>{priority_text} · {icon} {signal}</b>\n\n"
|
||||
f"{symbol_text} · {strategy} · {leverage_text}\n\n"
|
||||
f"🧠 Confidence: {confidence:.2f}\n"
|
||||
f"🔁 Repeats: {repeat_count}\n\n"
|
||||
f"💡 Причина:\n"
|
||||
f"{reason}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def _refresh_screen(cls, *, force: bool = False) -> None:
|
||||
now = time.monotonic()
|
||||
|
||||
Reference in New Issue
Block a user