123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- #!/usr/bin/env python3
- """
- Notification Manager - Handles all bot notifications and messages.
- """
- import logging
- from typing import Optional, Dict, Any, List
- from datetime import datetime
- logger = logging.getLogger(__name__)
- class NotificationManager:
- """Handles all notification logic for the trading bot."""
-
- def __init__(self):
- """Initialize the notification manager."""
- self.bot_application = None
-
- def set_bot_application(self, application):
- """Set the bot application for sending messages."""
- self.bot_application = application
-
- async def send_long_success_notification(self, query, token: str, token_amount: float,
- actual_price: float, order: Dict[str, Any],
- stop_loss_price: Optional[float] = None):
- """Send notification for successful long order."""
- order_id = order.get('id', 'N/A')
- order_type = "Market" if not order.get('price') else "Limit"
-
- success_message = f"""
- ✅ <b>Long Position Opened Successfully!</b>
- 📊 <b>Order Details:</b>
- • Token: {token}
- • Direction: LONG (Buy)
- • Amount: {token_amount:.6f} {token}
- • Entry Price: ${actual_price:,.2f}
- • Order Type: {order_type}
- • Order ID: <code>{order_id}</code>
- 💰 <b>Trade Summary:</b>
- • Position Value: ${token_amount * actual_price:,.2f}
- • Status: FILLED ✅
- • Time: {datetime.now().strftime('%H:%M:%S')}
- {f"🛑 <b>Pending Stop Loss:</b> ${stop_loss_price:,.2f}\n• Status: Will activate when order fills\n• Protection: Automatic position closure" if stop_loss_price else "💡 Consider setting a stop loss with /sl {token} [price]"}
- 📊 Use /positions to view your open positions.
- """
-
- await query.edit_message_text(success_message, parse_mode='HTML')
- logger.info(f"Long order executed: {token_amount:.6f} {token} @ ${actual_price:,.2f}")
-
- async def send_short_success_notification(self, query, token: str, token_amount: float,
- actual_price: float, order: Dict[str, Any],
- stop_loss_price: Optional[float] = None):
- """Send notification for successful short order."""
- order_id = order.get('id', 'N/A')
- order_type = "Market" if not order.get('price') else "Limit"
-
- success_message = f"""
- ✅ <b>Short Position Opened Successfully!</b>
- 📊 <b>Order Details:</b>
- • Token: {token}
- • Direction: SHORT (Sell)
- • Amount: {token_amount:.6f} {token}
- • Entry Price: ${actual_price:,.2f}
- • Order Type: {order_type}
- • Order ID: <code>{order_id}</code>
- 💰 <b>Trade Summary:</b>
- • Position Value: ${token_amount * actual_price:,.2f}
- • Status: FILLED ✅
- • Time: {datetime.now().strftime('%H:%M:%S')}
- {f"🛑 <b>Pending Stop Loss:</b> ${stop_loss_price:,.2f}\n• Status: Will activate when order fills\n• Protection: Automatic position closure" if stop_loss_price else "💡 Consider setting a stop loss with /sl {token} [price]"}
- 📊 Use /positions to view your open positions.
- """
-
- await query.edit_message_text(success_message, parse_mode='HTML')
- logger.info(f"Short order executed: {token_amount:.6f} {token} @ ${actual_price:,.2f}")
-
- async def send_exit_success_notification(self, query, token: str, position_type: str,
- contracts: float, actual_price: float,
- pnl: float, order: Dict[str, Any]):
- """Send notification for successful exit order."""
- order_id = order.get('id', 'N/A')
- pnl_emoji = "🟢" if pnl >= 0 else "🔴"
- action = "SELL" if position_type == "LONG" else "BUY"
-
- # Check if stop losses were cancelled
- cancelled_sls = order.get('cancelled_stop_losses', 0)
-
- success_message = f"""
- ✅ <b>{position_type} Position Closed Successfully!</b>
- 📊 <b>Exit Details:</b>
- • Token: {token}
- • Position: {position_type}
- • Action: {action} (Close)
- • Amount: {contracts:.6f} {token}
- • Exit Price: ${actual_price:,.2f}
- • Order ID: <code>{order_id}</code>
- 💰 <b>Trade Summary:</b>
- • Exit Value: ${contracts * actual_price:,.2f}
- • {pnl_emoji} Realized P&L: ${pnl:,.2f}
- • Status: FILLED ✅
- • Time: {datetime.now().strftime('%H:%M:%S')}"""
-
- if cancelled_sls > 0:
- success_message += f"""
- 🛑 <b>Cleanup:</b>
- • Cancelled {cancelled_sls} pending stop loss order(s)
- • All protective orders removed"""
-
- success_message += f"""
- 📊 <b>Result:</b> Position fully closed
- 💡 Use /stats to view updated performance metrics.
- """
-
- await query.edit_message_text(success_message, parse_mode='HTML')
- logger.info(f"Exit order executed: {contracts:.6f} {token} @ ${actual_price:,.2f} (P&L: ${pnl:,.2f}){f' | Cancelled {cancelled_sls} SLs' if cancelled_sls > 0 else ''}")
-
- async def send_sl_success_notification(self, query, token: str, position_type: str,
- contracts: float, stop_price: float,
- order: Dict[str, Any]):
- """Send notification for successful stop loss order."""
- order_id = order.get('id', 'N/A')
- action = "SELL" if position_type == "LONG" else "BUY"
-
- success_message = f"""
- ✅ <b>Stop Loss Order Set Successfully!</b>
- 📊 <b>Stop Loss Details:</b>
- • Token: {token}
- • Position: {position_type}
- • Size: {contracts:.6f} contracts
- • Stop Price: ${stop_price:,.2f}
- • Action: {action} (Close {position_type})
- • Order Type: Limit Order
- • Order ID: <code>{order_id}</code>
- 🛑 <b>Risk Management:</b>
- • Status: ACTIVE ✅
- • Trigger: When price reaches ${stop_price:,.2f}
- • Protection: Automatic position closure
- • Time: {datetime.now().strftime('%H:%M:%S')}
- 💡 <b>Note:</b> The stop loss order will execute automatically when the market price reaches your stop price.
- 📊 Use /orders to view all active orders.
- """
-
- await query.edit_message_text(success_message, parse_mode='HTML')
- logger.info(f"Stop loss set: {token} @ ${stop_price:,.2f}")
-
- async def send_tp_success_notification(self, query, token: str, position_type: str,
- contracts: float, tp_price: float,
- order: Dict[str, Any]):
- """Send notification for successful take profit order."""
- order_id = order.get('id', 'N/A')
- action = "SELL" if position_type == "LONG" else "BUY"
-
- success_message = f"""
- ✅ <b>Take Profit Order Set Successfully!</b>
- 📊 <b>Take Profit Details:</b>
- • Token: {token}
- • Position: {position_type}
- • Size: {contracts:.6f} contracts
- • Take Profit Price: ${tp_price:,.2f}
- • Action: {action} (Close {position_type})
- • Order Type: Limit Order
- • Order ID: <code>{order_id}</code>
- 🎯 <b>Profit Management:</b>
- • Status: ACTIVE ✅
- • Trigger: When price reaches ${tp_price:,.2f}
- • Action: Automatic profit taking
- • Time: {datetime.now().strftime('%H:%M:%S')}
- 💡 <b>Note:</b> The take profit order will execute automatically when the market price reaches your target price.
- 📊 Use /orders to view all active orders.
- """
-
- await query.edit_message_text(success_message, parse_mode='HTML')
- logger.info(f"Take profit set: {token} @ ${tp_price:,.2f}")
-
- async def send_coo_success_notification(self, query, token: str, cancelled_count: int,
- failed_count: int, cancelled_linked_sls: int = 0,
- cancelled_orders: List[Dict[str, Any]] = None):
- """Send notification for successful cancel all orders operation."""
-
- success_message = f"""
- ✅ <b>Cancel Orders Results</b>
- 📊 <b>Summary:</b>
- • Token: {token}
- • Cancelled: {cancelled_count} orders
- • Failed: {failed_count} orders
- • Total Attempted: {cancelled_count + failed_count} orders"""
-
- if cancelled_linked_sls > 0:
- success_message += f"""
- • 🛑 Linked Stop Losses Cancelled: {cancelled_linked_sls}"""
-
- # Show details of cancelled orders if available
- if cancelled_orders and len(cancelled_orders) > 0:
- success_message += f"""
- 🗑️ <b>Successfully Cancelled:</b>"""
- for order in cancelled_orders:
- side = order.get('side', 'Unknown')
- amount = order.get('amount', 0)
- price = order.get('price', 0)
- side_emoji = "🟢" if side.lower() == 'buy' else "🔴"
- success_message += f"""
- {side_emoji} {side.upper()} {amount} @ ${price:,.2f}"""
-
- # Overall status
- if cancelled_count == (cancelled_count + failed_count) and failed_count == 0:
- success_message += f"""
- 🎉 All {token} orders successfully cancelled!"""
- elif cancelled_count > 0:
- success_message += f"""
- ⚠️ Some orders cancelled. {failed_count} failed."""
- else:
- success_message += f"""
- ❌ Could not cancel any {token} orders."""
-
- success_message += f"""
- ⏰ <b>Time:</b> {datetime.now().strftime('%H:%M:%S')}
- 📊 Use /orders to verify no pending orders remain.
- """
-
- await query.edit_message_text(success_message, parse_mode='HTML')
- logger.info(f"Cancel orders complete: {token} - {cancelled_count} cancelled, {failed_count} failed{f', {cancelled_linked_sls} linked SLs cancelled' if cancelled_linked_sls > 0 else ''}")
-
- async def send_alarm_triggered_notification(self, token: str, target_price: float,
- current_price: float, direction: str):
- """Send notification when a price alarm is triggered."""
- if not self.bot_application:
- logger.warning("Bot application not set, cannot send alarm notification")
- return
-
- direction_emoji = "📈" if direction == 'above' else "📉"
-
- alarm_message = f"""
- 🔔 <b>Price Alarm Triggered!</b>
- {direction_emoji} <b>Alert Details:</b>
- • Token: {token}
- • Target Price: ${target_price:,.2f}
- • Current Price: ${current_price:,.2f}
- • Direction: {direction.upper()}
- ⏰ <b>Trigger Time:</b> {datetime.now().strftime('%H:%M:%S')}
- 💡 <b>Quick Actions:</b>
- • /market {token} - View market data
- • /price {token} - Quick price check
- • /long {token} [amount] - Open long position
- • /short {token} [amount] - Open short position
- """
-
- try:
- from src.config.config import Config
- if Config.TELEGRAM_CHAT_ID:
- await self.bot_application.bot.send_message(
- chat_id=Config.TELEGRAM_CHAT_ID,
- text=alarm_message,
- parse_mode='HTML'
- )
- logger.info(f"Alarm notification sent: {token} {direction} ${target_price}")
- except Exception as e:
- logger.error(f"Failed to send alarm notification: {e}")
-
- async def send_external_trade_notification(self, symbol: str, side: str, amount: float,
- price: float, action_type: str, timestamp: str):
- """Send notification for external trades detected."""
- if not self.bot_application:
- logger.warning("Bot application not set, cannot send external trade notification")
- return
-
- # Extract token from symbol
- token = symbol.split('/')[0] if '/' in symbol else symbol
-
- # Format timestamp
- try:
- trade_time = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
- time_str = trade_time.strftime('%H:%M:%S')
- except:
- time_str = "Unknown"
-
- # Format message based on action type
- if action_type == "position_opened":
- message = f"""
- 🚀 <b>Position Opened (External)</b>
- 📊 <b>Trade Details:</b>
- • Token: {token}
- • Direction: {side.upper()}
- • Size: {amount} {token}
- • Entry Price: ${price:,.2f}
- • Value: ${amount * price:,.2f}
- ✅ <b>Status:</b> New position opened externally
- ⏰ <b>Time:</b> {time_str}
- 📱 Use /positions to view all positions
- """
- elif action_type == "position_closed":
- message = f"""
- 🎯 <b>Position Closed (External)</b>
- 📊 <b>Trade Details:</b>
- • Token: {token}
- • Direction: {side.upper()}
- • Size: {amount} {token}
- • Exit Price: ${price:,.2f}
- • Value: ${amount * price:,.2f}
- ✅ <b>Status:</b> Position closed externally
- ⏰ <b>Time:</b> {time_str}
- 📊 Use /stats to view updated performance
- """
- elif action_type == "position_increased":
- message = f"""
- 📈 <b>Position Increased (External)</b>
- 📊 <b>Trade Details:</b>
- • Token: {token}
- • Direction: {side.upper()}
- • Added Size: {amount} {token}
- • Price: ${price:,.2f}
- • Value: ${amount * price:,.2f}
- ✅ <b>Status:</b> Position size increased externally
- ⏰ <b>Time:</b> {time_str}
- 📈 Use /positions to view current position
- """
- else:
- # Generic external trade notification
- side_emoji = "🟢" if side.lower() == 'buy' else "🔴"
- message = f"""
- 🔄 <b>External Trade Detected</b>
- 📊 <b>Trade Details:</b>
- • Token: {token}
- • Side: {side.upper()}
- • Amount: {amount} {token}
- • Price: ${price:,.2f}
- • Value: ${amount * price:,.2f}
- {side_emoji} <b>Source:</b> External Platform Trade
- ⏰ <b>Time:</b> {time_str}
- 📈 <b>Note:</b> This trade was executed outside the Telegram bot
- 📊 Stats have been automatically updated
- """
-
- try:
- from src.config.config import Config
- if Config.TELEGRAM_CHAT_ID:
- await self.bot_application.bot.send_message(
- chat_id=Config.TELEGRAM_CHAT_ID,
- text=message,
- parse_mode='HTML'
- )
- logger.info(f"External trade notification sent: {action_type} for {token}")
- except Exception as e:
- logger.error(f"Failed to send external trade notification: {e}")
-
- async def send_generic_notification(self, message: str):
- """Send a generic notification message."""
- if not self.bot_application:
- logger.warning("Bot application not set, cannot send generic notification")
- return
-
- try:
- from src.config.config import Config
- if Config.TELEGRAM_CHAT_ID:
- await self.bot_application.bot.send_message(
- chat_id=Config.TELEGRAM_CHAT_ID,
- text=message,
- parse_mode='HTML'
- )
- logger.info("Generic notification sent")
- except Exception as e:
- logger.error(f"Failed to send generic notification: {e}")
|