diff --git a/app/src/telegram/handlers/debug.py b/app/src/telegram/handlers/debug.py new file mode 100644 index 0000000..e69de29 diff --git a/app/src/trading/auto/runner.py b/app/src/trading/auto/runner.py index 7cb15ff..ff01243 100644 --- a/app/src/trading/auto/runner.py +++ b/app/src/trading/auto/runner.py @@ -12,6 +12,7 @@ from aiogram.exceptions import TelegramBadRequest, TelegramRetryAfter from src.core.event_bus import EventBus from src.integrations.exchange.market_data_runner import MarketDataRunner from src.trading.auto.service import AutoTradeService +from src.trading.journal.service import JournalService class AutoTradeRunner: @@ -33,6 +34,7 @@ class AutoTradeRunner: _last_ui_refresh_at: float = 0.0 _last_event_version: int = 0 _retry_after_until: float = 0.0 + _last_strong_alert_key: str | None = None @classmethod def register_screen( @@ -139,11 +141,94 @@ class AutoTradeRunner: if has_important_event: cls._last_event_version = current_event_version + await cls._handle_important_event(state) await cls._refresh_screen(force=has_important_event) await asyncio.sleep(cls._analysis_interval_seconds) + @classmethod + async def _handle_important_event(cls, state) -> None: + event_type, payload = EventBus.last_event() + + if event_type != "auto_decision_changed": + return + + if payload.get("decision_status") != "READY": + return + + signal = str(payload.get("signal", "")).upper() + if signal not in {"BUY", "SELL"}: + return + + await cls._send_strong_signal_alert(state=state, payload=payload) + + @classmethod + async def _send_strong_signal_alert(cls, *, state, payload: dict) -> None: + if cls._bot is None or cls._chat_id is None: + return + + signal = str(payload.get("signal", "")).upper() + symbol = str(payload.get("symbol") or state.symbol or "—") + strategy = str(payload.get("strategy") or state.strategy or "—") + repeat_count = int(payload.get("repeat_count") or state.last_signal_repeat_count or 0) + confidence = float(payload.get("confidence") or state.last_signal_confidence or 0.0) + 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 "—") + + alert_key = ( + f"{symbol}:{strategy}:{signal}:" + f"{repeat_count}:{confidence:.2f}:{state.decision_status}" + ) + + if alert_key == cls._last_strong_alert_key: + return + + cls._last_strong_alert_key = alert_key + + signal_icon = { + "BUY": "🟢", + "SELL": "🔴", + }.get(signal, "🚨") + + leverage_text = f"x{leverage:g}" if isinstance(leverage, (int, float)) else "—" + + text = ( + f"🚨 Сильный сигнал {signal_icon} {signal}\n\n" + f"{symbol} · {strategy} · {leverage_text}\n" + f"Confidence: {confidence:.2f}\n" + f"Repeats: {repeat_count}\n\n" + f"Причина: {reason}" + ) + + try: + await cls._bot.send_message( + chat_id=cls._chat_id, + text=text, + ) + + JournalService().log_ui_info( + event_type="auto_strong_signal_alert_sent", + message=f"Отправлено уведомление о сильном сигнале {signal}.", + screen="auto", + action="strong_signal_alert", + payload={ + "symbol": symbol, + "strategy": strategy, + "signal": signal, + "repeat_count": repeat_count, + "confidence": confidence, + "leverage": leverage, + "reason": reason, + }, + ) + + except TelegramRetryAfter as exc: + cls._retry_after_until = time.monotonic() + exc.retry_after + 5 + + except Exception: + pass + @classmethod async def _refresh_screen(cls, *, force: bool = False) -> None: now = time.monotonic() diff --git a/app/src/trading/auto/service.py b/app/src/trading/auto/service.py index eae59e7..924daa3 100644 --- a/app/src/trading/auto/service.py +++ b/app/src/trading/auto/service.py @@ -349,6 +349,10 @@ class AutoTradeService: "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, }, ) diff --git a/docs/roadmap/master-roadmap.md b/docs/roadmap/master-roadmap.md index 2398a42..84088f5 100644 --- a/docs/roadmap/master-roadmap.md +++ b/docs/roadmap/master-roadmap.md @@ -149,6 +149,14 @@ - логирование paper execution - EventBus события (paper_position_opened) +### Stage 07.4.3.4 — Telegram Strong Signal Alerts +- EventBus-driven уведомления +- Фильтрация READY сигналов +- Поддержка BUY / SELL +- Анти-спам (deduplication) +- Интеграция с Journal +- Runner полностью управляет Telegram-уведомлениями + ### 07.4.4 ⏳ Grid Strategy diff --git a/docs/roadmap/stage-07-auto-trading-roadmap.md b/docs/roadmap/stage-07-auto-trading-roadmap.md index 8bda882..47f4b3e 100644 --- a/docs/roadmap/stage-07-auto-trading-roadmap.md +++ b/docs/roadmap/stage-07-auto-trading-roadmap.md @@ -119,7 +119,7 @@ --- -### Stage 07.4.3.3 — Paper Position & Execution Engine ✅ +### Stage 07.4.3.3 — Paper Position & Execution Engine - добавлен ExecutionEngine - реализованы paper-позиции (LONG / SHORT) - интеграция с AutoTradeService @@ -129,6 +129,21 @@ --- +## Stage 07.4.3.4 — Telegram Strong Signal Alerts +- EventBus-driven уведомления +- Фильтрация READY сигналов +- Поддержка BUY / SELL +- Анти-спам (deduplication) +- Интеграция с Journal +- Runner полностью управляет Telegram-уведомлениями + +➡️ Подготовка к: +- Debug tools +- Execution lifecycle +- Real trading notifications + +--- + ### 07.4.4 ⏳ Grid strategy diff --git a/docs/stages/stage-07_4_3_4-telegram_alerts.md b/docs/stages/stage-07_4_3_4-telegram_alerts.md new file mode 100644 index 0000000..d67c543 --- /dev/null +++ b/docs/stages/stage-07_4_3_4-telegram_alerts.md @@ -0,0 +1,167 @@ +# Stage 07.4.3.4 — Telegram Strong Signal Alerts + +## 📌 Цель этапа + +Добавить систему уведомлений в Telegram при появлении **сильного торгового сигнала**: + +- Только при `BUY / SELL` +- Только при `decision_status = READY` +- Без спама (дедупликация) +- Через EventBus (без прямой связи UI ↔ стратегия) + +--- + +## 🧠 Архитектурная идея + +Strategy → AutoTradeService → EventBus → AutoTradeRunner → Telegram + +- Strategy генерирует сигнал +- Service обновляет decision state +- EventBus фиксирует изменение +- Runner реагирует и отправляет сообщение + +--- + +## ⚙️ Реализация + +### 1. EventBus расширен + +Добавлены дополнительные поля в событие: + +```python +EventBus.emit("auto_decision_changed", { + "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, +}) +``` +### 2. AutoTradeRunner — обработка событий + +Добавлены методы: +_handle_important_event() +_send_strong_signal_alert() + +Логика: + +* слушаем EventBus.version() +* проверяем last_event() +* фильтруем только READY + BUY/SELL +* отправляем уведомление + +### 3. Анти-спам защита + +Добавлено поле: +_last_strong_alert_key + +Ключ: +symbol + strategy + signal + repeat + confidence + decision_status + +👉 Если ключ совпадает — сообщение не отправляется + +### 4. Telegram уведомление + +Формат сообщения: + +🚨 Сильный сигнал 🟢 BUY + +BTC/USD · TREND · x2 +Confidence: 0.82 +Repeats: 3 + +Причина: Пробой уровня сопротивления + +### 5. Логирование + +Добавлено событие: +auto_strong_signal_alert_sent + +С payload: + +* symbol +* strategy +* signal +* confidence +* repeat_count +* leverage +* reason + +## ✅ Критерии готовности + +✔ Уведомление приходит только при: + +* BUY или SELL +* decision_status = READY + +✔ Не приходит при: + +* HOLD +* CONFIRMING +* BLOCKED + +✔ Не спамит при каждом цикле + +✔ Логируется в Journal + +✔ Не ломает UI + +## 🧪 Тестирование + +Рекомендуемый способ + +Форсирование события: + +``` +EventBus.emit("auto_decision_changed", { + "decision_status": "READY", + "signal": "BUY", + "repeat_count": 2, + "confidence": 0.9, + "symbol": state.symbol, + "strategy": state.strategy, + "leverage": state.leverage, + "reason": "TEST SIGNAL", +}) +``` + +## ⚠️ Ограничения + +* Нет cooldown между сигналами +* Нет разных уровней сигналов (weak/strong) +* Нет user-level подписок +* Нет отключения уведомлений + +## 🚀 Следующий этап + +Stage 07.4.3.5 — Debug Commands & Test Mode + +Добавим: + +* /debug signal BUY +* /debug ready +* test_mode=True +* override стратегии + +## 📦 Итог + +Этап завершает переход к event-driven архитектуре автоторговли: + +* UI отвязан от логики +* Runner управляет Telegram +* Service управляет сигналами +* EventBus связывает всё + +Это фундамент для: + +* Execution Engine +* Risk Engine +* Real trading + +## 💾 Коммит + +git add . +git commit -m "Stage 07.4.3.4 — Telegram strong signal alerts via EventBus" \ No newline at end of file