|
@@ -6,6 +6,7 @@ Notification Manager - Handles all bot notifications and messages.
|
|
|
import logging
|
|
|
from typing import Optional, Dict, Any, List
|
|
|
from datetime import datetime
|
|
|
+from src.utils.price_formatter import get_formatter
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
@@ -20,291 +21,131 @@ class NotificationManager:
|
|
|
"""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 placement."""
|
|
|
- order_id = order.get('id', 'N/A')
|
|
|
- order_type = order.get('type', 'Unknown').title()
|
|
|
-
|
|
|
- # Handle None actual_price for market orders
|
|
|
- if actual_price is None:
|
|
|
- actual_price = 0.0
|
|
|
- price_display = "Market Price"
|
|
|
- price_value = "TBD"
|
|
|
- else:
|
|
|
- price_display = "Limit Price" if order_type == "Limit" else "Est. Price"
|
|
|
- price_value = f"${actual_price:,.2f}"
|
|
|
-
|
|
|
- status_message = "ORDER PLACED" if order_type == "Limit" else "ORDER SUBMITTED"
|
|
|
-
|
|
|
- success_message = f"""
|
|
|
-✅ <b>Long Order Placed Successfully!</b>
|
|
|
-
|
|
|
-📊 <b>Order Details:</b>
|
|
|
-• Token: {token}
|
|
|
-• Direction: LONG (Buy)
|
|
|
-• Amount: {token_amount:.6f} {token}
|
|
|
-• {price_display}: {price_value}
|
|
|
-• Order Type: {order_type}
|
|
|
-• Order ID: <code>{order_id}</code>
|
|
|
-
|
|
|
-💰 <b>Order Summary:</b>"""
|
|
|
-
|
|
|
- if actual_price > 0:
|
|
|
- success_message += f"""
|
|
|
-• Order Value: ${token_amount * actual_price:,.2f}"""
|
|
|
- else:
|
|
|
- success_message += f"""
|
|
|
-• Order Value: Market execution"""
|
|
|
+ async def send_long_success_notification(self, query, token, amount, price, order_details, stop_loss_price=None, trade_lifecycle_id=None):
|
|
|
+ """Send notification for successful long order."""
|
|
|
+ try:
|
|
|
+ # Use PriceFormatter for consistent formatting
|
|
|
+ formatter = get_formatter() # Get formatter
|
|
|
|
|
|
- success_message += f"""
|
|
|
-• Status: {status_message} ✅
|
|
|
-• Time: {datetime.now().strftime('%H:%M:%S')}"""
|
|
|
-
|
|
|
- if order_type == "Market":
|
|
|
- success_message += f"""
|
|
|
-
|
|
|
-💡 <b>Note:</b> Market order submitted for execution
|
|
|
-• Actual fill price will be determined by market"""
|
|
|
- else:
|
|
|
- success_message += f"""
|
|
|
-
|
|
|
-💡 <b>Note:</b> Limit order placed on exchange
|
|
|
-• Will fill when market price reaches {price_value}"""
|
|
|
+ price_str = formatter.format_price_with_symbol(price, token)
|
|
|
+ amount_str = f"{amount:.6f} {token}"
|
|
|
+ value_str = formatter.format_price_with_symbol(amount * price, token)
|
|
|
+ order_id_str = order_details.get('id', 'N/A')
|
|
|
|
|
|
- if stop_loss_price:
|
|
|
- success_message += f"""
|
|
|
-
|
|
|
-🛑 <b>Pending Stop Loss:</b> ${stop_loss_price:,.2f}
|
|
|
-• Status: Will activate when main order fills
|
|
|
-• Protection: Automatic position closure"""
|
|
|
- else:
|
|
|
- success_message += f"""
|
|
|
-
|
|
|
-💡 Consider setting a stop loss with /sl {token} [price] after order fills"""
|
|
|
-
|
|
|
- success_message += f"""
|
|
|
-
|
|
|
-📊 Use /orders to monitor order status | /positions to view positions
|
|
|
- """
|
|
|
-
|
|
|
- await query.edit_message_text(success_message, parse_mode='HTML')
|
|
|
-
|
|
|
- log_price = f"${actual_price:,.2f}" if actual_price > 0 else "Market"
|
|
|
- logger.info(f"Long order placed: {token_amount:.6f} {token} @ {log_price} ({order_type})")
|
|
|
-
|
|
|
- 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 placement."""
|
|
|
- order_id = order.get('id', 'N/A')
|
|
|
- order_type = order.get('type', 'Unknown').title()
|
|
|
-
|
|
|
- # Handle None actual_price for market orders
|
|
|
- if actual_price is None:
|
|
|
- actual_price = 0.0
|
|
|
- price_display = "Market Price"
|
|
|
- price_value = "TBD"
|
|
|
- else:
|
|
|
- price_display = "Limit Price" if order_type == "Limit" else "Est. Price"
|
|
|
- price_value = f"${actual_price:,.2f}"
|
|
|
-
|
|
|
- status_message = "ORDER PLACED" if order_type == "Limit" else "ORDER SUBMITTED"
|
|
|
-
|
|
|
- success_message = f"""
|
|
|
-✅ <b>Short Order Placed Successfully!</b>
|
|
|
-
|
|
|
-📊 <b>Order Details:</b>
|
|
|
-• Token: {token}
|
|
|
-• Direction: SHORT (Sell)
|
|
|
-• Amount: {token_amount:.6f} {token}
|
|
|
-• {price_display}: {price_value}
|
|
|
-• Order Type: {order_type}
|
|
|
-• Order ID: <code>{order_id}</code>
|
|
|
-
|
|
|
-💰 <b>Order Summary:</b>"""
|
|
|
-
|
|
|
- if actual_price > 0:
|
|
|
- success_message += f"""
|
|
|
-• Order Value: ${token_amount * actual_price:,.2f}"""
|
|
|
- else:
|
|
|
- success_message += f"""
|
|
|
-• Order Value: Market execution"""
|
|
|
+ message = (
|
|
|
+ f"✅ Successfully opened <b>LONG</b> position for {amount_str} at ~{price_str}\n\n"
|
|
|
+ f"💰 Value: {value_str}\n"
|
|
|
+ f"🆔 Order ID: <code>{order_id_str}</code>"
|
|
|
+ )
|
|
|
+ if trade_lifecycle_id:
|
|
|
+ message += f"\n🆔 Lifecycle ID: <code>{trade_lifecycle_id[:8]}...</code>"
|
|
|
+
|
|
|
+ if stop_loss_price:
|
|
|
+ sl_price_str = formatter.format_price_with_symbol(stop_loss_price, token)
|
|
|
+ message += f"\n🛑 Stop Loss pending at {sl_price_str}"
|
|
|
|
|
|
- success_message += f"""
|
|
|
-• Status: {status_message} ✅
|
|
|
-• Time: {datetime.now().strftime('%H:%M:%S')}"""
|
|
|
-
|
|
|
- if order_type == "Market":
|
|
|
- success_message += f"""
|
|
|
-
|
|
|
-💡 <b>Note:</b> Market order submitted for execution
|
|
|
-• Actual fill price will be determined by market"""
|
|
|
- else:
|
|
|
- success_message += f"""
|
|
|
+ await query.edit_message_text(text=message, parse_mode='HTML')
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"Error sending long success notification: {e}")
|
|
|
|
|
|
-💡 <b>Note:</b> Limit order placed on exchange
|
|
|
-• Will fill when market price reaches {price_value}"""
|
|
|
+ async def send_short_success_notification(self, query, token, amount, price, order_details, stop_loss_price=None, trade_lifecycle_id=None):
|
|
|
+ """Send notification for successful short order."""
|
|
|
+ try:
|
|
|
+ formatter = get_formatter() # Get formatter
|
|
|
|
|
|
- if stop_loss_price:
|
|
|
- success_message += f"""
|
|
|
-
|
|
|
-🛑 <b>Pending Stop Loss:</b> ${stop_loss_price:,.2f}
|
|
|
-• Status: Will activate when main order fills
|
|
|
-• Protection: Automatic position closure"""
|
|
|
- else:
|
|
|
- success_message += f"""
|
|
|
-
|
|
|
-💡 Consider setting a stop loss with /sl {token} [price] after order fills"""
|
|
|
-
|
|
|
- success_message += f"""
|
|
|
+ price_str = formatter.format_price_with_symbol(price, token)
|
|
|
+ amount_str = f"{amount:.6f} {token}"
|
|
|
+ value_str = formatter.format_price_with_symbol(amount * price, token)
|
|
|
+ order_id_str = order_details.get('id', 'N/A')
|
|
|
+
|
|
|
+ message = (
|
|
|
+ f"✅ Successfully opened <b>SHORT</b> position for {amount_str} at ~{price_str}\n\n"
|
|
|
+ f"💰 Value: {value_str}\n"
|
|
|
+ f"🆔 Order ID: <code>{order_id_str}</code>"
|
|
|
+ )
|
|
|
+ if trade_lifecycle_id:
|
|
|
+ message += f"\n🆔 Lifecycle ID: <code>{trade_lifecycle_id[:8]}...</code>"
|
|
|
+
|
|
|
+ if stop_loss_price:
|
|
|
+ sl_price_str = formatter.format_price_with_symbol(stop_loss_price, token)
|
|
|
+ message += f"\n🛑 Stop Loss pending at {sl_price_str}"
|
|
|
+
|
|
|
+ await query.edit_message_text(text=message, parse_mode='HTML')
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"Error sending short success notification: {e}")
|
|
|
|
|
|
-📊 Use /orders to monitor order status | /positions to view positions
|
|
|
- """
|
|
|
-
|
|
|
- await query.edit_message_text(success_message, parse_mode='HTML')
|
|
|
-
|
|
|
- log_price = f"${actual_price:,.2f}" if actual_price > 0 else "Market"
|
|
|
- logger.info(f"Short order placed: {token_amount:.6f} {token} @ {log_price} ({order_type})")
|
|
|
-
|
|
|
- async def send_exit_success_notification(self, query, token: str, position_type: str,
|
|
|
- contracts: float, actual_price: float,
|
|
|
- pnl: float, order: Dict[str, Any]):
|
|
|
+ async def send_exit_success_notification(self, query, token, position_type, amount, price, pnl, order_details, trade_lifecycle_id=None):
|
|
|
"""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)
|
|
|
-
|
|
|
- # Check if this is a market order without actual price yet
|
|
|
- is_market_order_pending = actual_price == 0
|
|
|
-
|
|
|
- success_message = f"""
|
|
|
-✅ <b>{position_type} Position Exit Order Placed!</b>
|
|
|
-
|
|
|
-📊 <b>Exit Details:</b>
|
|
|
-• Token: {token}
|
|
|
-• Position: {position_type}
|
|
|
-• Action: {action} (Close)
|
|
|
-• Amount: {contracts:.6f} {token}
|
|
|
-• Order Type: Market Order
|
|
|
-• Order ID: <code>{order_id}</code>"""
|
|
|
-
|
|
|
- if is_market_order_pending:
|
|
|
- success_message += f"""
|
|
|
-
|
|
|
-⏳ <b>Order Status:</b>
|
|
|
-• Status: SUBMITTED ✅
|
|
|
-• Execution: Pending market fill
|
|
|
-• Time: {datetime.now().strftime('%H:%M:%S')}
|
|
|
-
|
|
|
-💡 <b>Note:</b> Actual execution price and P&L will be shown when the order fills."""
|
|
|
- else:
|
|
|
- success_message += f"""
|
|
|
-• Exit Price: ${actual_price:,.2f}
|
|
|
-
|
|
|
-💰 <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"""
|
|
|
-
|
|
|
- if is_market_order_pending:
|
|
|
- success_message += f"""
|
|
|
-
|
|
|
-📊 Position exit order submitted successfully
|
|
|
-💡 Use /orders to monitor order status"""
|
|
|
- else:
|
|
|
- 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')
|
|
|
-
|
|
|
- log_message = f"Exit order placed: {contracts:.6f} {token}"
|
|
|
- if not is_market_order_pending:
|
|
|
- log_message += f" @ ${actual_price:,.2f} (P&L: ${pnl:,.2f})"
|
|
|
- if cancelled_sls > 0:
|
|
|
- log_message += f" | Cancelled {cancelled_sls} SLs"
|
|
|
-
|
|
|
- logger.info(log_message)
|
|
|
-
|
|
|
- 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>
|
|
|
+ try:
|
|
|
+ formatter = get_formatter() # Get formatter
|
|
|
+
|
|
|
+ # Price is the execution price, PnL is calculated based on it
|
|
|
+ # For market orders, price might be approximate or from fill later
|
|
|
+ price_str = formatter.format_price_with_symbol(price, token) if price > 0 else "Market Price"
|
|
|
+ amount_str = f"{amount:.6f} {token}"
|
|
|
+ pnl_str = formatter.format_price_with_symbol(pnl)
|
|
|
+ pnl_emoji = "🟢" if pnl >= 0 else "🔴"
|
|
|
+ order_id_str = order_details.get('id', 'N/A')
|
|
|
+ cancelled_sl_count = order_details.get('cancelled_stop_losses', 0)
|
|
|
+
|
|
|
+ message = (
|
|
|
+ f"✅ Successfully closed <b>{position_type}</b> position for {amount_str}\n\n"
|
|
|
+ f"🆔 Exit Order ID: <code>{order_id_str}</code>\n"
|
|
|
+ # P&L and price are more reliably determined when MarketMonitor processes the fill.
|
|
|
+ # This notification confirms the exit order was PLACED.
|
|
|
+ f"⏳ Awaiting fill confirmation for final price and P&L."
|
|
|
+ )
|
|
|
+ if trade_lifecycle_id:
|
|
|
+ message += f"\n🆔 Lifecycle ID: <code>{trade_lifecycle_id[:8]}...</code> (Closed)"
|
|
|
+
|
|
|
+ if cancelled_sl_count > 0:
|
|
|
+ message += f"\n⚠️ Cancelled {cancelled_sl_count} linked stop loss order(s)."
|
|
|
+
|
|
|
+ await query.edit_message_text(text=message, parse_mode='HTML')
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"Error sending exit success notification: {e}")
|
|
|
|
|
|
-📊 <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>
|
|
|
+ async def send_sl_success_notification(self, query, token, position_type, amount, stop_price, order_details, trade_lifecycle_id=None):
|
|
|
+ """Send notification for successful stop loss order setup."""
|
|
|
+ try:
|
|
|
+ formatter = get_formatter() # Get formatter
|
|
|
+
|
|
|
+ stop_price_str = formatter.format_price_with_symbol(stop_price, token)
|
|
|
+ amount_str = f"{amount:.6f} {token}"
|
|
|
+ order_id_str = order_details.get('exchange_order_id', 'N/A') # From order_placed_details
|
|
|
+
|
|
|
+ message = (
|
|
|
+ f"🛑 Successfully set <b>STOP LOSS</b> for {position_type} {amount_str}\n\n"
|
|
|
+ f"🎯 Trigger Price: {stop_price_str}\n"
|
|
|
+ f"🆔 SL Order ID: <code>{order_id_str}</code>"
|
|
|
+ )
|
|
|
+ if trade_lifecycle_id:
|
|
|
+ message += f"\n🆔 Linked to Lifecycle ID: <code>{trade_lifecycle_id[:8]}...</code>"
|
|
|
+
|
|
|
+ await query.edit_message_text(text=message, parse_mode='HTML')
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"Error sending SL success notification: {e}")
|
|
|
|
|
|
-📊 <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_tp_success_notification(self, query, token, position_type, amount, tp_price, order_details, trade_lifecycle_id=None):
|
|
|
+ """Send notification for successful take profit order setup."""
|
|
|
+ try:
|
|
|
+ formatter = get_formatter() # Get formatter
|
|
|
+
|
|
|
+ tp_price_str = formatter.format_price_with_symbol(tp_price, token)
|
|
|
+ amount_str = f"{amount:.6f} {token}"
|
|
|
+ order_id_str = order_details.get('exchange_order_id', 'N/A') # From order_placed_details
|
|
|
+
|
|
|
+ message = (
|
|
|
+ f"🎯 Successfully set <b>TAKE PROFIT</b> for {position_type} {amount_str}\n\n"
|
|
|
+ f"💰 Target Price: {tp_price_str}\n"
|
|
|
+ f"🆔 TP Order ID: <code>{order_id_str}</code>"
|
|
|
+ )
|
|
|
+ if trade_lifecycle_id:
|
|
|
+ message += f"\n🆔 Linked to Lifecycle ID: <code>{trade_lifecycle_id[:8]}...</code>"
|
|
|
+
|
|
|
+ await query.edit_message_text(text=message, parse_mode='HTML')
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"Error sending TP success notification: {e}")
|
|
|
|
|
|
async def send_coo_success_notification(self, query, token: str, cancelled_count: int,
|
|
|
failed_count: int, cancelled_linked_sls: int = 0,
|