#!/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""" āœ… Long Position Opened Successfully! šŸ“Š Order Details: • Token: {token} • Direction: LONG (Buy) • Amount: {token_amount:.6f} {token} • Entry Price: ${actual_price:,.2f} • Order Type: {order_type} • Order ID: {order_id} šŸ’° Trade Summary: • Position Value: ${token_amount * actual_price:,.2f} • Status: FILLED āœ… • Time: {datetime.now().strftime('%H:%M:%S')} {f"šŸ›‘ Pending Stop Loss: ${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""" āœ… Short Position Opened Successfully! šŸ“Š Order Details: • Token: {token} • Direction: SHORT (Sell) • Amount: {token_amount:.6f} {token} • Entry Price: ${actual_price:,.2f} • Order Type: {order_type} • Order ID: {order_id} šŸ’° Trade Summary: • Position Value: ${token_amount * actual_price:,.2f} • Status: FILLED āœ… • Time: {datetime.now().strftime('%H:%M:%S')} {f"šŸ›‘ Pending Stop Loss: ${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""" āœ… {position_type} Position Closed Successfully! šŸ“Š Exit Details: • Token: {token} • Position: {position_type} • Action: {action} (Close) • Amount: {contracts:.6f} {token} • Exit Price: ${actual_price:,.2f} • Order ID: {order_id} šŸ’° Trade Summary: • 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""" šŸ›‘ Cleanup: • Cancelled {cancelled_sls} pending stop loss order(s) • All protective orders removed""" success_message += f""" šŸ“Š Result: 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""" āœ… Stop Loss Order Set Successfully! šŸ“Š Stop Loss Details: • 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: {order_id} šŸ›‘ Risk Management: • Status: ACTIVE āœ… • Trigger: When price reaches ${stop_price:,.2f} • Protection: Automatic position closure • Time: {datetime.now().strftime('%H:%M:%S')} šŸ’” Note: 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""" āœ… Take Profit Order Set Successfully! šŸ“Š Take Profit Details: • 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: {order_id} šŸŽÆ Profit Management: • Status: ACTIVE āœ… • Trigger: When price reaches ${tp_price:,.2f} • Action: Automatic profit taking • Time: {datetime.now().strftime('%H:%M:%S')} šŸ’” Note: 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""" āœ… Cancel Orders Results šŸ“Š Summary: • 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""" šŸ—‘ļø Successfully Cancelled:""" 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""" ā° Time: {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""" šŸ”” Price Alarm Triggered! {direction_emoji} Alert Details: • Token: {token} • Target Price: ${target_price:,.2f} • Current Price: ${current_price:,.2f} • Direction: {direction.upper()} ā° Trigger Time: {datetime.now().strftime('%H:%M:%S')} šŸ’” Quick Actions: • /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""" šŸš€ Position Opened (External) šŸ“Š Trade Details: • Token: {token} • Direction: {side.upper()} • Size: {amount} {token} • Entry Price: ${price:,.2f} • Value: ${amount * price:,.2f} āœ… Status: New position opened externally ā° Time: {time_str} šŸ“± Use /positions to view all positions """ elif action_type == "position_closed": message = f""" šŸŽÆ Position Closed (External) šŸ“Š Trade Details: • Token: {token} • Direction: {side.upper()} • Size: {amount} {token} • Exit Price: ${price:,.2f} • Value: ${amount * price:,.2f} āœ… Status: Position closed externally ā° Time: {time_str} šŸ“Š Use /stats to view updated performance """ elif action_type == "position_increased": message = f""" šŸ“ˆ Position Increased (External) šŸ“Š Trade Details: • Token: {token} • Direction: {side.upper()} • Added Size: {amount} {token} • Price: ${price:,.2f} • Value: ${amount * price:,.2f} āœ… Status: Position size increased externally ā° Time: {time_str} šŸ“ˆ Use /positions to view current position """ else: # Generic external trade notification side_emoji = "🟢" if side.lower() == 'buy' else "šŸ”“" message = f""" šŸ”„ External Trade Detected šŸ“Š Trade Details: • Token: {token} • Side: {side.upper()} • Amount: {amount} {token} • Price: ${price:,.2f} • Value: ${amount * price:,.2f} {side_emoji} Source: External Platform Trade ā° Time: {time_str} šŸ“ˆ Note: 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}")