diff --git a/app/src/trading/auto/runner.py b/app/src/trading/auto/runner.py index 0b91dc4..6b559eb 100644 --- a/app/src/trading/auto/runner.py +++ b/app/src/trading/auto/runner.py @@ -36,6 +36,10 @@ class AutoTradeRunner: _retry_after_until: float = 0.0 _last_strong_alert_key: str | None = None + # защита от частых одинаковых alert-ов + _strong_alert_cooldown_seconds = 120 + _last_strong_alert_at_by_key: dict[str, float] = {} + @classmethod def register_screen( cls, @@ -187,10 +191,27 @@ class AutoTradeRunner: f"{state.decision_status}:{reason}" ) - if alert_key == cls._last_strong_alert_key: - return + now = time.monotonic() + last_alert_at = cls._last_strong_alert_at_by_key.get(alert_key) + + if last_alert_at is not None: + elapsed = now - last_alert_at + + if elapsed < cls._strong_alert_cooldown_seconds: + cls._log_suppressed_strong_alert( + signal=signal, + symbol=symbol, + strategy=strategy, + repeat_count=repeat_count, + confidence=confidence, + leverage=leverage, + reason=reason, + cooldown_left=round(cls._strong_alert_cooldown_seconds - elapsed, 2), + ) + return cls._last_strong_alert_key = alert_key + cls._last_strong_alert_at_by_key[alert_key] = now signal_icon = { "BUY": "🟢", @@ -235,6 +256,39 @@ class AutoTradeRunner: except Exception: pass + @classmethod + def _log_suppressed_strong_alert( + cls, + *, + signal: str, + symbol: str, + strategy: str, + repeat_count: int, + confidence: float, + leverage: object, + reason: str, + cooldown_left: float, + ) -> None: + try: + JournalService().log_ui_info( + event_type="auto_strong_signal_alert_suppressed", + 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, + "cooldown_left": cooldown_left, + }, + ) + except Exception: + pass + @classmethod async def _refresh_screen(cls, *, force: bool = False) -> None: now = time.monotonic() diff --git a/docs/roadmap/master-roadmap.md b/docs/roadmap/master-roadmap.md index b07b232..0d68381 100644 --- a/docs/roadmap/master-roadmap.md +++ b/docs/roadmap/master-roadmap.md @@ -166,6 +166,12 @@ - journal logging for debug actions - full pipeline testing without market dependency +#### Stage 07.4.3.6 - Smart Alert Throttling ✅ +- cooldown для Telegram сигналов +- suppression повторных BUY/SELL +- journal logging suppressed событий +- не влияет на execution pipeline + ### 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 662411f..e06ca16 100644 --- a/docs/roadmap/stage-07-auto-trading-roadmap.md +++ b/docs/roadmap/stage-07-auto-trading-roadmap.md @@ -154,6 +154,13 @@ - journal logging for debug actions - full pipeline testing without market dependency +#### Stage 07.4.3.6 - Smart Alert Throttling ✅ + +- cooldown для Telegram сигналов +- suppression повторных BUY/SELL +- journal logging suppressed событий +- не влияет на execution pipeline + --- ### 07.4.4 diff --git a/docs/stages/stage-07_4_3_6-smart-alert-throttling.md b/docs/stages/stage-07_4_3_6-smart-alert-throttling.md new file mode 100644 index 0000000..e1b721d --- /dev/null +++ b/docs/stages/stage-07_4_3_6-smart-alert-throttling.md @@ -0,0 +1,158 @@ +# Stage 07.4.3.6 — Smart Alert Throttling + +## 🎯 Цель + +Добавить интеллектуальное ограничение отправки Telegram-уведомлений о сильных сигналах (BUY / SELL), чтобы: + +* исключить спам при повторяющихся сигналах +* сохранить информативность уведомлений +* не влиять на execution (открытие/закрытие позиций) + +--- + +## ⚙️ Основные изменения + +### 1. Cooldown для сигналов + +Добавлено ограничение: + +* одинаковый сигнал (по ключу) не отправляется чаще, чем раз в N секунд +* по умолчанию: + +_strong_alert_cooldown_seconds = 120 + +--- + +### 2. Уникальный ключ сигнала + +Формируется alert_key: + +symbol + strategy + signal + repeat_count + confidence + decision_status + reason + +Это позволяет: + +* различать похожие сигналы +* не блокировать новые реальные сигналы + +--- + +### 3. Хранение времени отправки + +_last_strong_alert_at_by_key: dict[str, float] + +Используется time.monotonic() для стабильного расчёта времени. + +--- + +### 4. Подавление повторных alert-ов + +Если сигнал приходит повторно в пределах cooldown: + +* Telegram-сообщение НЕ отправляется +* создаётся запись в журнале: + +event_type = auto_strong_signal_alert_suppressed + +--- + +### 5. Журналирование suppressed событий + +Фиксируется: + +* сигнал +* инструмент +* стратегия +* confidence +* repeat_count +* оставшийся cooldown + +Это позволяет: + +* дебажить поведение системы +* анализировать частоту сигналов + +--- + +## 🧠 Архитектура + +Важно: + +Signal (EventBus) + ↓ +Alert (Telegram) + ↓ +Execution (отдельно) + +Throttling применяется только к alert-слою: + +* ❌ не влияет на ExecutionEngine +* ❌ не влияет на AutoTradeService +* ❌ не ломает debug режим +* ✅ влияет только на отправку сообщений + +--- + +## 🧪 Проверка + +### Сценарий 1 — повтор сигнала + +/debug_signal BUY +/debug_signal BUY + +Ожидаемо: + +* 1-й сигнал → сообщение отправлено +* 2-й сигнал → подавлен (suppressed) + +--- + +### Сценарий 2 — другой сигнал + +/debug_signal BUY +/debug_signal SELL + +Ожидаемо: + +* оба сообщения отправлены + +--- + +### Сценарий 3 — после cooldown + +Через 120 сек: + +/debug_signal BUY + +Ожидаемо: + +* сообщение снова отправляется + +--- + +### 📊 Результат + +Система: + +* не спамит одинаковыми сигналами +* сохраняет реакцию на новые сигналы +* полностью совместима с production execution +* логирует подавленные события + +--- + +## 🔜 Следующий этап: + +07.4.3.7 — Alert Priority & Aggregation + +🧭 Обновление master-roadmap + +В блоке AutoTrade / Alerts: + +- Smart alert throttling (cooldown + suppression) — completed + +--- + +## ✅ Коммит + +git add . +git commit -m "Stage 07.4.3.6 — Smart alert throttling" \ No newline at end of file