Stage 07.4.3.5 — Debug commands & test mode

This commit is contained in:
2026-05-03 11:13:19 +03:00
parent af2d27761f
commit e4e78eb4ca
9 changed files with 757 additions and 8 deletions

View File

@@ -48,6 +48,9 @@ class Settings:
db_user: str
db_password: str
# Debag helper
debug_enabled: bool
# helper: demo/live mode
def is_demo_mode(self) -> bool:
return "demo" in self.exchange_base_url.lower()
@@ -87,6 +90,7 @@ def load_settings() -> Settings:
app_env=os.getenv("APP_ENV", "dev").strip() or "dev",
log_level=os.getenv("LOG_LEVEL", "INFO").strip().upper() or "INFO",
tz=os.getenv("TZ", "Europe/Minsk").strip() or "Europe/Minsk",
debug_enabled=_parse_bool(os.getenv("DEBUG_ENABLED", "false")),
# Exchange
exchange_enabled=_parse_bool(os.getenv("EXCHANGE_ENABLED", "false")),

View File

@@ -0,0 +1,111 @@
# app/src/telegram/handlers/debug.py
from __future__ import annotations
from aiogram import F, Router
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
router = Router(name="debug")
def _debug_enabled() -> bool:
return load_settings().debug_enabled
@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"
service = AutoTradeService()
state = service.debug_force_signal(
signal=signal,
confidence=0.9,
repeat_count=2,
reason=f"DEBUG FORCE {signal}",
)
AutoTradeRunner.start()
await AutoTradeRunner.process_last_event_now()
JournalService().log_ui_info(
event_type="debug_signal_forced",
message=f"Debug-сигнал принудительно установлен: {signal}.",
screen="debug",
action="debug_signal",
user_id=message.from_user.id if message.from_user else None,
chat_id=message.chat.id,
payload={
"signal": state.last_signal,
"decision_status": state.decision_status,
"confidence": state.last_signal_confidence,
"repeat_count": state.last_signal_repeat_count,
},
)
await message.answer(
"✅ Debug signal forced\n\n"
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}"
)
@router.message(F.text == "/debug_ready")
async def debug_ready(message: Message) -> None:
if not _debug_enabled():
await message.answer("Debug mode выключен.")
return
service = AutoTradeService()
state = service.debug_force_signal(
signal="BUY",
confidence=0.95,
repeat_count=2,
reason="DEBUG READY BUY",
)
AutoTradeRunner.start()
await AutoTradeRunner.process_last_event_now()
await message.answer(
"✅ Debug READY создан\n\n"
f"Signal: {state.last_signal}\n"
f"Decision: {state.decision_status}"
)
@router.message(F.text == "/debug_state")
async def debug_state(message: Message) -> None:
if not _debug_enabled():
await message.answer("Debug mode выключен.")
return
state = AutoTradeService().get_state()
await message.answer(
"<b>Debug Auto State</b>\n\n"
f"Status: {state.status}\n"
f"Symbol: {state.symbol}\n"
f"Strategy: {state.strategy}\n"
f"Risk: {state.risk_percent}\n"
f"Leverage: {state.leverage}\n\n"
f"Signal: {state.last_signal}\n"
f"Repeats: {state.last_signal_repeat_count}\n"
f"Confidence: {state.last_signal_confidence:.2f}\n"
f"Decision: {state.decision_status}\n\n"
f"Position: {state.position_side}\n"
f"Entry: {state.entry_price}\n"
f"PnL: {state.unrealized_pnl_usd}"
)

View File

@@ -12,6 +12,7 @@ from src.telegram.handlers.start import router as start_router
from src.telegram.handlers.system import router as system_router
from src.telegram.handlers.trade.main import router as trade_main_router
from src.telegram.handlers.trade.new_order import router as trade_new_order_router
from src.telegram.handlers.debug import router as debug_router
def setup_routers(dispatcher: Dispatcher) -> None:
@@ -24,4 +25,5 @@ def setup_routers(dispatcher: Dispatcher) -> None:
dispatcher.include_router(trade_new_order_router)
dispatcher.include_router(auto_router)
dispatcher.include_router(journal_router)
dispatcher.include_router(debug_router)
dispatcher.include_router(system_router)

View File

@@ -147,6 +147,11 @@ class AutoTradeRunner:
await asyncio.sleep(cls._analysis_interval_seconds)
@classmethod
async def process_last_event_now(cls) -> None:
state = AutoTradeService().get_state()
await cls._handle_important_event(state)
@classmethod
async def _handle_important_event(cls, state) -> None:
event_type, payload = EventBus.last_event()

View File

@@ -32,6 +32,59 @@ class AutoTradeService:
_last_signal_payload: dict | None = None
_same_signal_count = 0
# debug: принудительно выставить сигнал и decision
def debug_force_signal(
self,
*,
signal: str,
confidence: float = 0.9,
repeat_count: int = 2,
reason: str = "DEBUG SIGNAL",
) -> AutoTradeState:
state = self.get_state()
normalized_signal = signal.strip().upper()
if normalized_signal not in {"BUY", "SELL", "HOLD"}:
normalized_signal = "HOLD"
previous_signal = state.last_signal
previous_decision_status = state.decision_status
state.last_signal = normalized_signal
state.last_signal_repeat_count = repeat_count
state.last_signal_confidence = confidence
state.last_signal_reason = reason
if normalized_signal == "HOLD":
state.decision_status = "WAITING"
state.decision_reason = "Debug HOLD."
state.is_signal_confirmed = False
state.is_signal_ready = False
else:
state.decision_status = "READY"
state.decision_reason = "Debug READY signal."
state.is_signal_confirmed = True
state.is_signal_ready = True
EventBus.emit(
"auto_decision_changed",
{
"previous_signal": previous_signal,
"previous_decision_status": previous_decision_status,
"decision_status": state.decision_status,
"signal": state.last_signal,
"repeat_count": state.last_signal_repeat_count,
"confidence": state.last_signal_confidence,
"symbol": state.symbol,
"strategy": state.strategy,
"leverage": state.leverage,
"reason": state.last_signal_reason,
"debug": True,
},
)
return state
# получить текущее состояние автоторговли
def get_state(self) -> AutoTradeState:
if not self._state.symbol: