"""
monitoring/alerts.py — Telegram + email alert system.
Fires on circuit breakers, safe mode, orphans, WebSocket disconnects, etc.
"""
from __future__ import annotations
import logging
import threading
import requests
import config
logger = logging.getLogger("trading_system.alerts")
_send_lock = threading.Lock()
def send_telegram(message: str) -> bool:
"""Send a message via Telegram bot API.
Returns True if sent successfully.
"""
if not config.TELEGRAM_BOT_TOKEN or not config.TELEGRAM_CHAT_ID:
logger.debug("Telegram not configured, skipping alert")
return False
url = f"https://api.telegram.org/bot{config.TELEGRAM_BOT_TOKEN}/sendMessage"
payload = {
"chat_id": config.TELEGRAM_CHAT_ID,
"text": message,
"parse_mode": "HTML",
}
with _send_lock:
try:
resp = requests.post(url, json=payload, timeout=10)
if resp.status_code == 200:
logger.debug("Telegram alert sent: %s", message[:80])
return True
else:
logger.error(
"Telegram API error %d: %s", resp.status_code, resp.text[:200]
)
return False
except Exception as e:
logger.error("Failed to send Telegram alert: %s", e)
return False
def alert(message: str):
"""Send an alert via all configured channels."""
logger.info("ALERT: %s", message)
send_telegram(message)
def alert_circuit_breaker(reason: str):
alert(f"🛑 CIRCUIT BREAKER\n{reason}")
def alert_safe_mode(reason: str):
alert(f"🚨 SAFE MODE ACTIVATED\n{reason}")
def alert_orphaned_order(symbol: str, entry_id: str):
alert(f"🚨 ORPHANED ORDER\n{symbol} entry {entry_id} — missing stop/TP legs")
def alert_websocket_disconnect(duration_sec: float):
alert(f"⚠️ WebSocket Disconnected\nDuration: {duration_sec:.0f}s")
def alert_corporate_action(symbol: str, action_type: str, ratio: float, date: str):
alert(f"🔔 CORPORATE ACTION\n{symbol} {action_type} ratio={ratio:.4f} on {date}")
def alert_sentiment_stale(symbol: str, minutes: float):
alert(f"⚠️ Sentiment Stale\n{symbol} last refresh {minutes:.0f} min ago")
def alert_end_of_day_close(positions: list[dict]):
lines = ["📊 End-of-Day Close"]
for p in positions:
pnl = p.get("pnl", 0)
lines.append(f" {p['symbol']}: ${pnl:.2f}")
alert("\n".join(lines))
def alert_shutdown(final_pnl: float):
alert(f"🔌 Bot Shut Down Gracefully\nFinal P&L: ${final_pnl:.2f}")