#!/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}")