Stage 07.4.3.5 — Debug commands & test mode
This commit is contained in:
@@ -48,6 +48,9 @@ class Settings:
|
|||||||
db_user: str
|
db_user: str
|
||||||
db_password: str
|
db_password: str
|
||||||
|
|
||||||
|
# Debag helper
|
||||||
|
debug_enabled: bool
|
||||||
|
|
||||||
# helper: demo/live mode
|
# helper: demo/live mode
|
||||||
def is_demo_mode(self) -> bool:
|
def is_demo_mode(self) -> bool:
|
||||||
return "demo" in self.exchange_base_url.lower()
|
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",
|
app_env=os.getenv("APP_ENV", "dev").strip() or "dev",
|
||||||
log_level=os.getenv("LOG_LEVEL", "INFO").strip().upper() or "INFO",
|
log_level=os.getenv("LOG_LEVEL", "INFO").strip().upper() or "INFO",
|
||||||
tz=os.getenv("TZ", "Europe/Minsk").strip() or "Europe/Minsk",
|
tz=os.getenv("TZ", "Europe/Minsk").strip() or "Europe/Minsk",
|
||||||
|
debug_enabled=_parse_bool(os.getenv("DEBUG_ENABLED", "false")),
|
||||||
|
|
||||||
# Exchange
|
# Exchange
|
||||||
exchange_enabled=_parse_bool(os.getenv("EXCHANGE_ENABLED", "false")),
|
exchange_enabled=_parse_bool(os.getenv("EXCHANGE_ENABLED", "false")),
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# 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
|
||||||
|
from src.trading.execution.engine import ExecutionEngine
|
||||||
|
|
||||||
|
|
||||||
|
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}",
|
||||||
|
)
|
||||||
|
|
||||||
|
if state.status == "OFF":
|
||||||
|
state.status = "RUNNING"
|
||||||
|
|
||||||
|
await AutoTradeRunner._handle_important_event(state)
|
||||||
|
|
||||||
|
ExecutionEngine().process(state)
|
||||||
|
|
||||||
|
AutoTradeRunner.start()
|
||||||
|
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
|
||||||
|
if state.status == "OFF":
|
||||||
|
state.status = "RUNNING"
|
||||||
|
|
||||||
|
await AutoTradeRunner._handle_important_event(state)
|
||||||
|
|
||||||
|
ExecutionEngine().process(state)
|
||||||
|
|
||||||
|
AutoTradeRunner.start()
|
||||||
|
|
||||||
|
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}"
|
||||||
|
)
|
||||||
@@ -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.system import router as system_router
|
||||||
from src.telegram.handlers.trade.main import router as trade_main_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.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:
|
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(trade_new_order_router)
|
||||||
dispatcher.include_router(auto_router)
|
dispatcher.include_router(auto_router)
|
||||||
dispatcher.include_router(journal_router)
|
dispatcher.include_router(journal_router)
|
||||||
|
dispatcher.include_router(debug_router)
|
||||||
dispatcher.include_router(system_router)
|
dispatcher.include_router(system_router)
|
||||||
@@ -147,6 +147,11 @@ class AutoTradeRunner:
|
|||||||
|
|
||||||
await asyncio.sleep(cls._analysis_interval_seconds)
|
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
|
@classmethod
|
||||||
async def _handle_important_event(cls, state) -> None:
|
async def _handle_important_event(cls, state) -> None:
|
||||||
event_type, payload = EventBus.last_event()
|
event_type, payload = EventBus.last_event()
|
||||||
@@ -178,7 +183,8 @@ class AutoTradeRunner:
|
|||||||
|
|
||||||
alert_key = (
|
alert_key = (
|
||||||
f"{symbol}:{strategy}:{signal}:"
|
f"{symbol}:{strategy}:{signal}:"
|
||||||
f"{repeat_count}:{confidence:.2f}:{state.decision_status}"
|
f"{repeat_count}:{confidence:.2f}:"
|
||||||
|
f"{state.decision_status}:{reason}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if alert_key == cls._last_strong_alert_key:
|
if alert_key == cls._last_strong_alert_key:
|
||||||
|
|||||||
@@ -32,6 +32,59 @@ class AutoTradeService:
|
|||||||
_last_signal_payload: dict | None = None
|
_last_signal_payload: dict | None = None
|
||||||
_same_signal_count = 0
|
_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:
|
def get_state(self) -> AutoTradeState:
|
||||||
if not self._state.symbol:
|
if not self._state.symbol:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class ExecutionEngine:
|
|||||||
|
|
||||||
# получить текущую paper-позицию
|
# получить текущую paper-позицию
|
||||||
def get_position(self) -> PositionState:
|
def get_position(self) -> PositionState:
|
||||||
return self._position
|
return type(self)._position
|
||||||
|
|
||||||
# обработать состояние автоторговли и принять paper-execution решение
|
# обработать состояние автоторговли и принять paper-execution решение
|
||||||
def process(self, state: AutoTradeState) -> ExecutionDecision:
|
def process(self, state: AutoTradeState) -> ExecutionDecision:
|
||||||
@@ -30,6 +30,8 @@ class ExecutionEngine:
|
|||||||
reason="Execution доступен только в режиме RUNNING.",
|
reason="Execution доступен только в режиме RUNNING.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._update_unrealized_pnl(state)
|
||||||
|
|
||||||
if state.decision_status != "READY" or not state.is_signal_ready:
|
if state.decision_status != "READY" or not state.is_signal_ready:
|
||||||
return ExecutionDecision(
|
return ExecutionDecision(
|
||||||
action="NONE",
|
action="NONE",
|
||||||
@@ -37,6 +39,9 @@ class ExecutionEngine:
|
|||||||
reason="Сигнал ещё не готов к execution.",
|
reason="Сигнал ещё не готов к execution.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self._should_close_position(state):
|
||||||
|
return self._close_position(state)
|
||||||
|
|
||||||
if state.last_signal == "BUY":
|
if state.last_signal == "BUY":
|
||||||
return self._open_position_if_empty(
|
return self._open_position_if_empty(
|
||||||
state=state,
|
state=state,
|
||||||
@@ -65,7 +70,9 @@ class ExecutionEngine:
|
|||||||
side: str,
|
side: str,
|
||||||
action: str,
|
action: str,
|
||||||
) -> ExecutionDecision:
|
) -> ExecutionDecision:
|
||||||
if self._position.side != "NONE":
|
position = type(self)._position
|
||||||
|
|
||||||
|
if position.side != "NONE":
|
||||||
self._sync_state_from_position(state)
|
self._sync_state_from_position(state)
|
||||||
return ExecutionDecision(
|
return ExecutionDecision(
|
||||||
action="NONE",
|
action="NONE",
|
||||||
@@ -83,16 +90,19 @@ class ExecutionEngine:
|
|||||||
reason=f"Не удалось получить цену для paper execution: {exc}",
|
reason=f"Не удалось получить цену для paper execution: {exc}",
|
||||||
)
|
)
|
||||||
|
|
||||||
now = datetime.now().strftime("%H:%M:%S")
|
now = self._now_time()
|
||||||
|
size = self._calculate_position_size(state)
|
||||||
|
|
||||||
self._position.side = side
|
type(self)._position = PositionState(
|
||||||
self._position.symbol = state.symbol
|
side=side,
|
||||||
self._position.entry_price = entry_price
|
symbol=state.symbol,
|
||||||
self._position.size = self._calculate_position_size(state)
|
entry_price=entry_price,
|
||||||
self._position.leverage = state.leverage
|
size=size,
|
||||||
self._position.unrealized_pnl_usd = 0.0
|
leverage=state.leverage,
|
||||||
self._position.opened_at = now
|
unrealized_pnl_usd=0.0,
|
||||||
self._position.updated_at = now
|
opened_at=now,
|
||||||
|
updated_at=now,
|
||||||
|
)
|
||||||
|
|
||||||
self._sync_state_from_position(state)
|
self._sync_state_from_position(state)
|
||||||
|
|
||||||
@@ -105,7 +115,7 @@ class ExecutionEngine:
|
|||||||
"symbol": state.symbol,
|
"symbol": state.symbol,
|
||||||
"side": side,
|
"side": side,
|
||||||
"entry_price": entry_price,
|
"entry_price": entry_price,
|
||||||
"size": self._position.size,
|
"size": size,
|
||||||
"leverage": state.leverage,
|
"leverage": state.leverage,
|
||||||
"signal": state.last_signal,
|
"signal": state.last_signal,
|
||||||
"confidence": state.last_signal_confidence,
|
"confidence": state.last_signal_confidence,
|
||||||
@@ -118,7 +128,7 @@ class ExecutionEngine:
|
|||||||
"symbol": state.symbol,
|
"symbol": state.symbol,
|
||||||
"side": side,
|
"side": side,
|
||||||
"entry_price": entry_price,
|
"entry_price": entry_price,
|
||||||
"size": self._position.size,
|
"size": size,
|
||||||
"leverage": state.leverage,
|
"leverage": state.leverage,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -129,6 +139,103 @@ class ExecutionEngine:
|
|||||||
reason=f"Paper-позиция {side} открыта.",
|
reason=f"Paper-позиция {side} открыта.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# закрыть текущую paper-позицию
|
||||||
|
def _close_position(self, state: AutoTradeState) -> ExecutionDecision:
|
||||||
|
position = type(self)._position
|
||||||
|
|
||||||
|
if position.side == "NONE":
|
||||||
|
self._sync_state_from_position(state)
|
||||||
|
return ExecutionDecision(
|
||||||
|
action="NONE",
|
||||||
|
can_execute=False,
|
||||||
|
reason="Нет открытой позиции для закрытия.",
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
ticker = ExchangeService().get_price(state.symbol)
|
||||||
|
exit_price = ticker.price
|
||||||
|
except Exception as exc:
|
||||||
|
return ExecutionDecision(
|
||||||
|
action="NONE",
|
||||||
|
can_execute=False,
|
||||||
|
reason=f"Ошибка получения цены для закрытия: {exc}",
|
||||||
|
)
|
||||||
|
|
||||||
|
pnl = self._calculate_pnl(exit_price)
|
||||||
|
|
||||||
|
JournalService().log_ui_info(
|
||||||
|
event_type="paper_position_closed",
|
||||||
|
message=f"Позиция закрыта: {position.side} {state.symbol}",
|
||||||
|
screen="auto",
|
||||||
|
action="paper_execution",
|
||||||
|
payload={
|
||||||
|
"symbol": state.symbol,
|
||||||
|
"side": position.side,
|
||||||
|
"entry_price": position.entry_price,
|
||||||
|
"exit_price": exit_price,
|
||||||
|
"size": position.size,
|
||||||
|
"leverage": position.leverage,
|
||||||
|
"pnl": pnl,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
EventBus.emit(
|
||||||
|
"paper_position_closed",
|
||||||
|
{
|
||||||
|
"symbol": state.symbol,
|
||||||
|
"side": position.side,
|
||||||
|
"entry_price": position.entry_price,
|
||||||
|
"exit_price": exit_price,
|
||||||
|
"size": position.size,
|
||||||
|
"leverage": position.leverage,
|
||||||
|
"pnl": pnl,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
type(self)._position = PositionState()
|
||||||
|
self._sync_state_from_position(state)
|
||||||
|
|
||||||
|
return ExecutionDecision(
|
||||||
|
action="CLOSE",
|
||||||
|
can_execute=True,
|
||||||
|
reason="Позиция закрыта.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# проверить, нужно ли закрывать позицию по противоположному сигналу
|
||||||
|
def _should_close_position(self, state: AutoTradeState) -> bool:
|
||||||
|
position = type(self)._position
|
||||||
|
|
||||||
|
if position.side == "NONE":
|
||||||
|
return False
|
||||||
|
|
||||||
|
if position.side == "LONG" and state.last_signal == "SELL":
|
||||||
|
return True
|
||||||
|
|
||||||
|
if position.side == "SHORT" and state.last_signal == "BUY":
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
# обновить unrealized PnL по текущей цене
|
||||||
|
def _update_unrealized_pnl(self, state: AutoTradeState) -> None:
|
||||||
|
position = type(self)._position
|
||||||
|
|
||||||
|
if position.side == "NONE":
|
||||||
|
self._sync_state_from_position(state)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
ticker = ExchangeService().get_price(position.symbol or state.symbol)
|
||||||
|
current_price = ticker.price
|
||||||
|
except Exception:
|
||||||
|
self._sync_state_from_position(state)
|
||||||
|
return
|
||||||
|
|
||||||
|
position.unrealized_pnl_usd = self._calculate_pnl(current_price)
|
||||||
|
position.updated_at = self._now_time()
|
||||||
|
|
||||||
|
self._sync_state_from_position(state)
|
||||||
|
|
||||||
# временный расчёт размера позиции для paper mode
|
# временный расчёт размера позиции для paper mode
|
||||||
def _calculate_position_size(self, state: AutoTradeState) -> float:
|
def _calculate_position_size(self, state: AutoTradeState) -> float:
|
||||||
risk_percent = state.risk_percent or 0.0
|
risk_percent = state.risk_percent or 0.0
|
||||||
@@ -136,9 +243,30 @@ class ExecutionEngine:
|
|||||||
|
|
||||||
return round((risk_percent * leverage) / 100, 8)
|
return round((risk_percent * leverage) / 100, 8)
|
||||||
|
|
||||||
|
# расчёт PnL для paper-позиции
|
||||||
|
def _calculate_pnl(self, current_price: float) -> float:
|
||||||
|
position = type(self)._position
|
||||||
|
|
||||||
|
entry = position.entry_price or 0.0
|
||||||
|
size = position.size or 0.0
|
||||||
|
|
||||||
|
if position.side == "LONG":
|
||||||
|
return round((current_price - entry) * size, 4)
|
||||||
|
|
||||||
|
if position.side == "SHORT":
|
||||||
|
return round((entry - current_price) * size, 4)
|
||||||
|
|
||||||
|
return 0.0
|
||||||
|
|
||||||
# синхронизировать AutoTradeState с текущей paper-позицией
|
# синхронизировать AutoTradeState с текущей paper-позицией
|
||||||
def _sync_state_from_position(self, state: AutoTradeState) -> None:
|
def _sync_state_from_position(self, state: AutoTradeState) -> None:
|
||||||
state.position_side = self._position.side
|
position = type(self)._position
|
||||||
state.entry_price = self._position.entry_price
|
|
||||||
state.position_size = self._position.size
|
state.position_side = position.side
|
||||||
state.unrealized_pnl_usd = self._position.unrealized_pnl_usd
|
state.entry_price = position.entry_price
|
||||||
|
state.position_size = position.size
|
||||||
|
state.unrealized_pnl_usd = position.unrealized_pnl_usd
|
||||||
|
|
||||||
|
# текущее время для paper execution
|
||||||
|
def _now_time(self) -> str:
|
||||||
|
return datetime.now().strftime("%H:%M:%S")
|
||||||
@@ -129,19 +129,19 @@
|
|||||||
✔ confidence scoring
|
✔ confidence scoring
|
||||||
✔ UI integration
|
✔ UI integration
|
||||||
|
|
||||||
### 07.4.3.1 — UI Optimization
|
#### 07.4.3.1 — UI Optimization ✅
|
||||||
✔ compact auto screen
|
✔ compact auto screen
|
||||||
✔ state-based rendering (OFF / RUNNING / OBSERVING)
|
✔ state-based rendering (OFF / RUNNING / OBSERVING)
|
||||||
✔ minimal trading layout
|
✔ minimal trading layout
|
||||||
✔ duplicate info removal
|
✔ duplicate info removal
|
||||||
|
|
||||||
### 07.4.3.2 — Engine Decoupling (NEXT)
|
#### 07.4.3.2 — Engine Decoupling (NEXT) ✅
|
||||||
✔ split analysis / UI refresh
|
✔ split analysis / UI refresh
|
||||||
✔ fast price polling (1s)
|
✔ fast price polling (1s)
|
||||||
✔ slow UI updates (event-driven / 60s)
|
✔ slow UI updates (event-driven / 60s)
|
||||||
✔ anti-flood protection
|
✔ anti-flood protection
|
||||||
|
|
||||||
### 07.4.3.3 — Paper Position & Execution Engine
|
#### 07.4.3.3 — Paper Position & Execution Engine ✅
|
||||||
- добавлен ExecutionEngine
|
- добавлен ExecutionEngine
|
||||||
- реализованы paper-позиции (LONG / SHORT)
|
- реализованы paper-позиции (LONG / SHORT)
|
||||||
- интеграция с AutoTradeService
|
- интеграция с AutoTradeService
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
- логирование paper execution
|
- логирование paper execution
|
||||||
- EventBus события (paper_position_opened)
|
- EventBus события (paper_position_opened)
|
||||||
|
|
||||||
### Stage 07.4.3.4 — Telegram Strong Signal Alerts
|
#### Stage 07.4.3.4 — Telegram Strong Signal Alerts ✅
|
||||||
- EventBus-driven уведомления
|
- EventBus-driven уведомления
|
||||||
- Фильтрация READY сигналов
|
- Фильтрация READY сигналов
|
||||||
- Поддержка BUY / SELL
|
- Поддержка BUY / SELL
|
||||||
@@ -157,6 +157,15 @@
|
|||||||
- Интеграция с Journal
|
- Интеграция с Journal
|
||||||
- Runner полностью управляет Telegram-уведомлениями
|
- Runner полностью управляет Telegram-уведомлениями
|
||||||
|
|
||||||
|
#### Stage 07.4.3.5 — Debug Commands & Test Mode ✅
|
||||||
|
- DEBUG_ENABLED env flag
|
||||||
|
- debug_force_signal API
|
||||||
|
- instant EventBus processing
|
||||||
|
- Telegram debug commands
|
||||||
|
- state inspection (/debug_state)
|
||||||
|
- journal logging for debug actions
|
||||||
|
- full pipeline testing without market dependency
|
||||||
|
|
||||||
### 07.4.4
|
### 07.4.4
|
||||||
⏳ Grid Strategy
|
⏳ Grid Strategy
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 07.4.3.1 — UI Optimization
|
#### 07.4.3.1 — UI Optimization
|
||||||
✔ компактный экран автоторговли
|
✔ компактный экран автоторговли
|
||||||
✔ разделение OFF / ACTIVE / OBSERVING
|
✔ разделение OFF / ACTIVE / OBSERVING
|
||||||
✔ убраны дубли (WAITING / HOLD и т.д.)
|
✔ убраны дубли (WAITING / HOLD и т.д.)
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 07.4.3.2 — Engine Decoupling (NEXT)
|
#### 07.4.3.2 — Engine Decoupling (NEXT)
|
||||||
⏳ разделение:
|
⏳ разделение:
|
||||||
- analysis loop (частый)
|
- analysis loop (частый)
|
||||||
- UI loop (редкий)
|
- UI loop (редкий)
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Stage 07.4.3.3 — Paper Position & Execution Engine
|
#### Stage 07.4.3.3 — Paper Position & Execution Engine
|
||||||
- добавлен ExecutionEngine
|
- добавлен ExecutionEngine
|
||||||
- реализованы paper-позиции (LONG / SHORT)
|
- реализованы paper-позиции (LONG / SHORT)
|
||||||
- интеграция с AutoTradeService
|
- интеграция с AutoTradeService
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Stage 07.4.3.4 — Telegram Strong Signal Alerts
|
#### Stage 07.4.3.4 — Telegram Strong Signal Alerts
|
||||||
- EventBus-driven уведомления
|
- EventBus-driven уведомления
|
||||||
- Фильтрация READY сигналов
|
- Фильтрация READY сигналов
|
||||||
- Поддержка BUY / SELL
|
- Поддержка BUY / SELL
|
||||||
@@ -144,6 +144,18 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### Stage 07.4.3.5 — Debug Commands & Test Mode ✅
|
||||||
|
|
||||||
|
- DEBUG_ENABLED env flag
|
||||||
|
- debug_force_signal API
|
||||||
|
- instant EventBus processing
|
||||||
|
- Telegram debug commands
|
||||||
|
- state inspection (/debug_state)
|
||||||
|
- journal logging for debug actions
|
||||||
|
- full pipeline testing without market dependency
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### 07.4.4
|
### 07.4.4
|
||||||
⏳ Grid strategy
|
⏳ Grid strategy
|
||||||
|
|
||||||
|
|||||||
281
docs/stages/stage-07_4_3_4-telegram_alerts-final.md
Normal file
281
docs/stages/stage-07_4_3_4-telegram_alerts-final.md
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
# Stage 07.4.3.4 — Telegram Strong Signal Alerts via EventBus
|
||||||
|
|
||||||
|
## Цель этапа
|
||||||
|
|
||||||
|
Добавить отдельные Telegram-уведомления для сильных торговых сигналов автоторговли.
|
||||||
|
|
||||||
|
Уведомление должно отправляться только тогда, когда сигнал действительно готов к действию:
|
||||||
|
|
||||||
|
- сигнал `BUY` или `SELL`;
|
||||||
|
- `decision_status = READY`;
|
||||||
|
- `is_signal_ready = True`;
|
||||||
|
- событие пришло через `EventBus`;
|
||||||
|
- уведомление не дублируется на каждом цикле анализа.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Что реализовано
|
||||||
|
|
||||||
|
### 1. EventBus-событие для READY-сигналов
|
||||||
|
|
||||||
|
В `AutoTradeService` событие `auto_decision_changed` теперь передаёт расширенный payload:
|
||||||
|
|
||||||
|
```python
|
||||||
|
EventBus.emit(
|
||||||
|
"auto_decision_changed",
|
||||||
|
{
|
||||||
|
"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,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Это позволяет runner-слою принимать решение об отправке уведомления без прямой связи со стратегией.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. AutoTradeRunner обрабатывает важные события
|
||||||
|
|
||||||
|
В `AutoTradeRunner` добавлена обработка последнего события из `EventBus`.
|
||||||
|
|
||||||
|
Runner проверяет:
|
||||||
|
|
||||||
|
```text
|
||||||
|
event_type == auto_decision_changed
|
||||||
|
decision_status == READY
|
||||||
|
signal in BUY / SELL
|
||||||
|
```
|
||||||
|
|
||||||
|
Если условия выполнены — отправляется отдельное Telegram-сообщение.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Отдельное Telegram-уведомление
|
||||||
|
|
||||||
|
Формат уведомления:
|
||||||
|
|
||||||
|
```text
|
||||||
|
🚨 Сильный сигнал 🟢 BUY
|
||||||
|
|
||||||
|
BTC/USD_LEVERAGE · TREND · x2
|
||||||
|
Confidence: 0.90
|
||||||
|
Repeats: 2
|
||||||
|
|
||||||
|
Причина: DEBUG FORCE BUY
|
||||||
|
```
|
||||||
|
|
||||||
|
Для `SELL` используется красный индикатор.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Защита от дублей
|
||||||
|
|
||||||
|
Добавлен ключ последнего уведомления:
|
||||||
|
|
||||||
|
```python
|
||||||
|
_last_strong_alert_key
|
||||||
|
```
|
||||||
|
|
||||||
|
Ключ строится из:
|
||||||
|
|
||||||
|
```text
|
||||||
|
symbol + strategy + signal + repeat_count + confidence + decision_status
|
||||||
|
```
|
||||||
|
|
||||||
|
Если такой же alert уже был отправлен — повторная отправка блокируется.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Логирование уведомлений
|
||||||
|
|
||||||
|
При успешной отправке уведомления пишется событие журнала:
|
||||||
|
|
||||||
|
```text
|
||||||
|
auto_strong_signal_alert_sent
|
||||||
|
```
|
||||||
|
|
||||||
|
Payload содержит:
|
||||||
|
|
||||||
|
- symbol;
|
||||||
|
- strategy;
|
||||||
|
- signal;
|
||||||
|
- repeat_count;
|
||||||
|
- confidence;
|
||||||
|
- leverage;
|
||||||
|
- reason.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Debug-команды для проверки
|
||||||
|
|
||||||
|
Для проверки сильных сигналов без ожидания рынка добавлены debug-команды.
|
||||||
|
|
||||||
|
Добавлен флаг:
|
||||||
|
|
||||||
|
```env
|
||||||
|
DEBUG_ENABLED=true
|
||||||
|
```
|
||||||
|
|
||||||
|
Команды:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/debug_signal BUY
|
||||||
|
/debug_signal SELL
|
||||||
|
/debug_ready
|
||||||
|
/debug_state
|
||||||
|
```
|
||||||
|
|
||||||
|
При выключенном debug mode команды не выполняются:
|
||||||
|
|
||||||
|
```env
|
||||||
|
DEBUG_ENABLED=false
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Мгновенная обработка debug-события
|
||||||
|
|
||||||
|
Добавлен публичный метод runner-а:
|
||||||
|
|
||||||
|
```python
|
||||||
|
AutoTradeRunner.process_last_event_now()
|
||||||
|
```
|
||||||
|
|
||||||
|
Он нужен для debug-сценариев, чтобы событие `READY BUY / SELL` было обработано сразу, а не ожидало следующего цикла автоторговли.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Изменённые файлы
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/trading/auto/service.py
|
||||||
|
app/src/trading/auto/runner.py
|
||||||
|
app/src/core/config.py
|
||||||
|
app/src/telegram/handlers/debug.py
|
||||||
|
app/src/telegram/routers.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
### 1. Включить debug mode
|
||||||
|
|
||||||
|
```env
|
||||||
|
DEBUG_ENABLED=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Запустить приложение
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m src.main
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Открыть экран автоторговли
|
||||||
|
|
||||||
|
Нужно, чтобы `AutoTradeRunner` зарегистрировал `bot` и `chat_id`.
|
||||||
|
|
||||||
|
### 4. Выполнить команду
|
||||||
|
|
||||||
|
```text
|
||||||
|
/debug_signal BUY
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидаемый результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
🚨 Сильный сигнал 🟢 BUY
|
||||||
|
|
||||||
|
BTC/USD_LEVERAGE · TREND · x2
|
||||||
|
Confidence: 0.90
|
||||||
|
Repeats: 2
|
||||||
|
|
||||||
|
Причина: DEBUG FORCE BUY
|
||||||
|
```
|
||||||
|
|
||||||
|
Также приходит подтверждение debug-команды:
|
||||||
|
|
||||||
|
```text
|
||||||
|
✅ Debug signal forced
|
||||||
|
Signal: BUY
|
||||||
|
Decision: READY
|
||||||
|
Confidence: 0.90
|
||||||
|
Repeats: 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Проверить журнал
|
||||||
|
|
||||||
|
В журнале должно появиться событие:
|
||||||
|
|
||||||
|
```text
|
||||||
|
auto_strong_signal_alert_sent
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Важное замечание по безопасности
|
||||||
|
|
||||||
|
Debug-команды можно оставлять в коде, но в боевом режиме они должны быть выключены:
|
||||||
|
|
||||||
|
```env
|
||||||
|
DEBUG_ENABLED=false
|
||||||
|
```
|
||||||
|
|
||||||
|
Это важно, потому что debug-команды могут вручную выставлять `READY BUY / SELL`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ограничения текущего этапа
|
||||||
|
|
||||||
|
Пока не реализовано:
|
||||||
|
|
||||||
|
- cooldown между alert-ами;
|
||||||
|
- настройка включения/выключения alert-ов из UI;
|
||||||
|
- разные уровни сигналов;
|
||||||
|
- подписки на отдельные типы уведомлений;
|
||||||
|
- уведомления по закрытию позиции.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
Этап добавил полноценный механизм Telegram-уведомлений для сильных сигналов через событийную архитектуру.
|
||||||
|
|
||||||
|
Теперь цепочка работает так:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Strategy
|
||||||
|
↓
|
||||||
|
AutoTradeService
|
||||||
|
↓
|
||||||
|
EventBus
|
||||||
|
↓
|
||||||
|
AutoTradeRunner
|
||||||
|
↓
|
||||||
|
Telegram alert
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Следующий этап
|
||||||
|
|
||||||
|
Рекомендуемый следующий этап:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Stage 07.4.3.5 — Position lifecycle and PnL
|
||||||
|
```
|
||||||
|
|
||||||
|
Цели следующего этапа:
|
||||||
|
|
||||||
|
- закрытие paper-позиции;
|
||||||
|
- пересчёт PnL;
|
||||||
|
- логирование закрытия;
|
||||||
|
- обновление UI позиции;
|
||||||
|
- подготовка к Risk Engine.
|
||||||
272
docs/stages/stage-07_4_3_5-debug-mode.md
Normal file
272
docs/stages/stage-07_4_3_5-debug-mode.md
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
# Stage 07.4.3.5 — Debug Commands & Test Mode
|
||||||
|
|
||||||
|
## Цель этапа
|
||||||
|
|
||||||
|
Добавить безопасный механизм тестирования автоторговли без зависимости от рыночных условий.
|
||||||
|
|
||||||
|
Этап позволяет:
|
||||||
|
|
||||||
|
- принудительно генерировать сигналы BUY / SELL / READY;
|
||||||
|
- тестировать Telegram-уведомления;
|
||||||
|
- тестировать Execution Engine;
|
||||||
|
- проверять состояние автоторговли в реальном времени.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Что реализовано
|
||||||
|
|
||||||
|
### 1. Debug Mode через ENV
|
||||||
|
|
||||||
|
Добавлен флаг:
|
||||||
|
|
||||||
|
```env
|
||||||
|
DEBUG_ENABLED=true
|
||||||
|
```
|
||||||
|
|
||||||
|
При значении `false`:
|
||||||
|
|
||||||
|
- debug-команды полностью отключены;
|
||||||
|
- любые попытки их вызвать возвращают сообщение "Debug mode выключен".
|
||||||
|
|
||||||
|
Это обеспечивает безопасность для production.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Debug API в AutoTradeService
|
||||||
|
|
||||||
|
Добавлен метод:
|
||||||
|
|
||||||
|
```python
|
||||||
|
debug_force_signal(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Позволяет вручную задать:
|
||||||
|
|
||||||
|
- signal (BUY / SELL / HOLD)
|
||||||
|
- confidence
|
||||||
|
- repeat_count
|
||||||
|
- reason
|
||||||
|
|
||||||
|
Метод:
|
||||||
|
|
||||||
|
- обновляет `AutoTradeState`;
|
||||||
|
- выставляет `decision_status = READY` (для BUY/SELL);
|
||||||
|
- эмитит событие `auto_decision_changed`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Интеграция с EventBus
|
||||||
|
|
||||||
|
После debug-сигнала:
|
||||||
|
|
||||||
|
```text
|
||||||
|
AutoTradeService
|
||||||
|
↓
|
||||||
|
EventBus.emit(...)
|
||||||
|
↓
|
||||||
|
AutoTradeRunner
|
||||||
|
```
|
||||||
|
|
||||||
|
Это позволяет тестировать всю цепочку без изменения стратегий.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Мгновенная обработка событий
|
||||||
|
|
||||||
|
Добавлен метод:
|
||||||
|
|
||||||
|
```python
|
||||||
|
AutoTradeRunner.process_last_event_now()
|
||||||
|
```
|
||||||
|
|
||||||
|
Он позволяет:
|
||||||
|
|
||||||
|
- обработать событие сразу;
|
||||||
|
- не ждать следующий цикл (`run_cycle`);
|
||||||
|
- использовать debug-команды в реальном времени.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Telegram Debug Commands
|
||||||
|
|
||||||
|
Добавлен handler:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/telegram/handlers/debug.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Доступные команды:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/debug_signal BUY
|
||||||
|
/debug_signal SELL
|
||||||
|
/debug_ready
|
||||||
|
/debug_state
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Поведение команд
|
||||||
|
|
||||||
|
#### `/debug_signal BUY`
|
||||||
|
|
||||||
|
- выставляет сигнал BUY;
|
||||||
|
- делает его READY;
|
||||||
|
- запускает обработку EventBus;
|
||||||
|
- отправляет alert (если включён этап 07.4.3.4).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `/debug_ready`
|
||||||
|
|
||||||
|
- shortcut для READY BUY;
|
||||||
|
- максимальная уверенность;
|
||||||
|
- используется для быстрого теста execution и alert.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `/debug_state`
|
||||||
|
|
||||||
|
Показывает текущее состояние:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Status
|
||||||
|
Symbol
|
||||||
|
Strategy
|
||||||
|
Risk
|
||||||
|
Leverage
|
||||||
|
|
||||||
|
Signal
|
||||||
|
Repeats
|
||||||
|
Confidence
|
||||||
|
Decision
|
||||||
|
|
||||||
|
Position
|
||||||
|
Entry
|
||||||
|
PnL
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Логирование
|
||||||
|
|
||||||
|
Добавлено событие:
|
||||||
|
|
||||||
|
```text
|
||||||
|
debug_signal_forced
|
||||||
|
```
|
||||||
|
|
||||||
|
Позволяет:
|
||||||
|
|
||||||
|
- отслеживать тестовые действия;
|
||||||
|
- отделять debug от реальных сигналов.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Изменённые файлы
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/core/config.py
|
||||||
|
app/src/trading/auto/service.py
|
||||||
|
app/src/trading/auto/runner.py
|
||||||
|
app/src/telegram/handlers/debug.py
|
||||||
|
app/src/telegram/routers.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
### 1. Включить debug
|
||||||
|
|
||||||
|
```env
|
||||||
|
DEBUG_ENABLED=true
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Запустить приложение
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m src.main
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Открыть автоторговлю
|
||||||
|
|
||||||
|
Важно: чтобы runner получил bot/chat_id.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Выполнить команды
|
||||||
|
|
||||||
|
```text
|
||||||
|
/debug_signal BUY
|
||||||
|
/debug_signal SELL
|
||||||
|
/debug_ready
|
||||||
|
/debug_state
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Ожидаемый результат
|
||||||
|
|
||||||
|
- приходит debug confirmation;
|
||||||
|
- приходит Telegram alert (если READY);
|
||||||
|
- обновляется UI;
|
||||||
|
- пишется журнал.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Важное замечание
|
||||||
|
|
||||||
|
Debug-режим нельзя оставлять включённым в production:
|
||||||
|
|
||||||
|
```env
|
||||||
|
DEBUG_ENABLED=false
|
||||||
|
```
|
||||||
|
|
||||||
|
Иначе:
|
||||||
|
|
||||||
|
- можно форсировать сделки;
|
||||||
|
- можно обойти стратегию;
|
||||||
|
- нарушается безопасность.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
Пока не реализовано:
|
||||||
|
|
||||||
|
- авторизация debug-команд (по user_id);
|
||||||
|
- rate limit debug-команд;
|
||||||
|
- отдельный debug-чат;
|
||||||
|
- UI toggle debug mode.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
Добавлен полноценный test harness для автоторговли.
|
||||||
|
|
||||||
|
Теперь можно тестировать:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Strategy → Signal → Decision → EventBus → Runner → Telegram → Execution
|
||||||
|
```
|
||||||
|
|
||||||
|
без ожидания рынка.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Следующий этап
|
||||||
|
|
||||||
|
```text
|
||||||
|
Stage 07.4.3.6 — Position lifecycle & PnL
|
||||||
|
```
|
||||||
|
|
||||||
|
- закрытие позиции;
|
||||||
|
- расчёт прибыли;
|
||||||
|
- обновление UI;
|
||||||
|
- подготовка к Risk Engine.
|
||||||
Reference in New Issue
Block a user