141 lines
5.7 KiB
Python
141 lines
5.7 KiB
Python
import requests
|
|
import urllib.parse
|
|
import os
|
|
import logging
|
|
|
|
class CallMeBotNotifier:
|
|
"""
|
|
Sends WhatsApp notifications via the free CallMeBot API.
|
|
URL: https://api.callmebot.com/whatsapp.php?phone=[phone]&text=[text]&apikey=[apikey]
|
|
"""
|
|
def __init__(self, phone=None, api_key=None):
|
|
self.phone = phone or os.getenv("CALLMEBOT_PHONE")
|
|
self.api_key = api_key or os.getenv("CALLMEBOT_API_KEY")
|
|
self.base_url = "https://api.callmebot.com/whatsapp.php"
|
|
|
|
def send_signal(self, ticker: str, signal: str, reason: str):
|
|
"""
|
|
Sends a formatted trading signal to WhatsApp.
|
|
"""
|
|
if not self.phone or not self.api_key:
|
|
logging.warning("⚠️ CallMeBot Not Configured: Missing CALLMEBOT_PHONE or CALLMEBOT_API_KEY.")
|
|
return
|
|
|
|
message_text = self._format_message(ticker, signal, reason)
|
|
try:
|
|
# URL Encode
|
|
encoded_text = urllib.parse.quote(message_text)
|
|
url = f"{self.base_url}?phone={self.phone}&text={encoded_text}&apikey={self.api_key}"
|
|
response = requests.get(url, timeout=10)
|
|
if response.status_code == 200:
|
|
logging.info(f"✅ WhatsApp (CallMeBot) Notification sent for {ticker}")
|
|
else:
|
|
logging.error(f"❌ CallMeBot Failed: {response.status_code} - {response.text}")
|
|
except Exception as e:
|
|
logging.error(f"❌ CallMeBot Error: {str(e)}")
|
|
|
|
def _format_message(self, ticker, signal, reason):
|
|
emoji = "⚪"
|
|
if "BUY" in signal.upper(): emoji = "🟢"
|
|
elif "SELL" in signal.upper(): emoji = "🔴"
|
|
elif "HOLD" in signal.upper(): emoji = "🟡"
|
|
return f"{emoji} *TRADING SIGNAL: {ticker}*\n\n*Decision:* {signal}\n*Reason:* {reason}\n\n_Sent by TradingAgents 🤖_"
|
|
|
|
|
|
class TwilioNotifier:
|
|
"""
|
|
Sends WhatsApp notifications via Twilio API.
|
|
"""
|
|
def __init__(self):
|
|
self.account_sid = os.getenv("TWILIO_ACCOUNT_SID")
|
|
self.auth_token = os.getenv("TWILIO_AUTH_TOKEN")
|
|
self.from_number = os.getenv("TWILIO_FROM_NUMBER") # e.g., 'whatsapp:+14155238886'
|
|
self.to_number = os.getenv("TWILIO_TO_NUMBER") # e.g., 'whatsapp:+1234567890'
|
|
|
|
def send_signal(self, ticker: str, signal: str, reason: str):
|
|
if not self.account_sid or not self.auth_token:
|
|
logging.warning("⚠️ Twilio Not Configured: Missing SID or TOKEN.")
|
|
return
|
|
|
|
try:
|
|
from twilio.rest import Client
|
|
client = Client(self.account_sid, self.auth_token)
|
|
|
|
message_text = self._format_message(ticker, signal, reason)
|
|
|
|
message = client.messages.create(
|
|
from_=self.from_number,
|
|
body=message_text,
|
|
to=self.to_number
|
|
)
|
|
logging.info(f"✅ WhatsApp (Twilio) Notification sent! SID: {message.sid}")
|
|
|
|
except ImportError:
|
|
logging.error("❌ Twilio Library not found. Run: pip install twilio")
|
|
except Exception as e:
|
|
logging.error(f"❌ Twilio Error: {str(e)}")
|
|
|
|
def _format_message(self, ticker, signal, reason):
|
|
# Same format, maybe different max length logic if needed
|
|
emoji = "⚪"
|
|
if "BUY" in signal.upper(): emoji = "🟢"
|
|
elif "SELL" in signal.upper(): emoji = "🔴"
|
|
elif "HOLD" in signal.upper(): emoji = "🟡"
|
|
return f"{emoji} *TRADING SIGNAL: {ticker}*\n\n*Decision:* {signal}\n*Reason:* {reason}\n\n_Sent by TradingAgents 🤖_"
|
|
|
|
|
|
class TelegramNotifier:
|
|
"""
|
|
Sends notifications via Telegram Bot API.
|
|
"""
|
|
def __init__(self):
|
|
self.bot_token = os.getenv("TELEGRAM_BOT_TOKEN")
|
|
self.chat_id = os.getenv("TELEGRAM_CHAT_ID")
|
|
self.base_url = f"https://api.telegram.org/bot{self.bot_token}/sendMessage"
|
|
|
|
def send_signal(self, ticker: str, signal: str, reason: str):
|
|
if not self.bot_token or not self.chat_id:
|
|
logging.warning("⚠️ Telegram Not Configured: Missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID.")
|
|
return
|
|
|
|
message_text = self._format_message(ticker, signal, reason)
|
|
|
|
try:
|
|
payload = {
|
|
"chat_id": self.chat_id,
|
|
"text": message_text,
|
|
"parse_mode": "Markdown"
|
|
}
|
|
response = requests.post(self.base_url, json=payload, timeout=10)
|
|
|
|
if response.status_code == 200:
|
|
logging.info(f"✅ Telegram Notification sent for {ticker}")
|
|
else:
|
|
logging.error(f"❌ Telegram Failed: {response.status_code} - {response.text}")
|
|
except Exception as e:
|
|
logging.error(f"❌ Telegram Error: {str(e)}")
|
|
|
|
def _format_message(self, ticker, signal, reason):
|
|
# MarkdownV2 or HTML style can be used, but simple Markdown is safer
|
|
emoji = "⚪"
|
|
if "BUY" in signal.upper(): emoji = "🟢"
|
|
elif "SELL" in signal.upper(): emoji = "🔴"
|
|
elif "HOLD" in signal.upper(): emoji = "🟡"
|
|
# Telegram Markdown requires escaping some chars if using MarkdownV2, using generic Markdown here
|
|
return f"{emoji} *TRADING SIGNAL: {ticker}*\n\n*Decision:* {signal}\n*Reason:* {reason}\n\n_Sent by TradingAgents 🤖_"
|
|
|
|
|
|
def get_notifier():
|
|
"""Factory to return the configured notifier."""
|
|
provider = os.getenv("NOTIFICATION_PROVIDER", "callmebot").lower()
|
|
|
|
if provider == "twilio":
|
|
return TwilioNotifier()
|
|
elif provider == "telegram":
|
|
return TelegramNotifier()
|
|
elif provider == "callmebot":
|
|
return CallMeBotNotifier()
|
|
else:
|
|
logging.warning(f"⚠️ Unknown NOTIFICATION_PROVIDER: {provider}. Defaulting to CallMeBot.")
|
|
return CallMeBotNotifier()
|