Sfoglia il codice sorgente

Refactor active trades command in InfoCommands to utilize open positions instead of all active trades. Updated messaging and logging for clarity, improved user notifications, and enhanced error handling. Adjusted MarketMonitor to log external position actions for better traceability.

Carles Sentis 4 giorni fa
parent
commit
a750fec4e8

+ 55 - 62
src/commands/info_commands.py

@@ -616,7 +616,7 @@ class InfoCommands:
             logger.error(f"Error in cycles command: {e}")
     
     async def active_trades_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
-        """Handle the /active command to show active trades (Phase 1 testing)."""
+        """Handle the /active command to show active trades (using open positions)."""
         chat_id = update.effective_chat.id
         if not self._is_authorized(chat_id):
             await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
@@ -628,87 +628,80 @@ class InfoCommands:
                 await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
                 return
             
-            # Get all active trades
-            all_active_trades = stats.get_all_active_trades()
+            # Get open positions from unified trades table (current active trades)
+            open_positions = stats.get_open_positions()
             
-            if not all_active_trades:
+            if not open_positions:
                 await context.bot.send_message(
                     chat_id=chat_id, 
-                    text="📊 <b>Active Trades (Phase 1)</b>\n\n📭 No active trades found.",
+                    text="📊 <b>Active Positions</b>\n\n📭 No active positions found.\n\n💡 Use /long or /short to open positions.",
                     parse_mode='HTML'
                 )
                 return
             
-            # Group by status
-            active_trades_by_status = {}
-            for trade in all_active_trades:
-                status = trade['status']
-                if status not in active_trades_by_status:
-                    active_trades_by_status[status] = []
-                active_trades_by_status[status].append(trade)
-            
-            message_text = "📊 <b>Active Trades (Phase 1)</b>\n\n"
-            
-            # Show each status group
-            for status, trades in active_trades_by_status.items():
-                status_emoji = {
-                    'pending': '⏳',
-                    'active': '🟢', 
-                    'closed': '✅',
-                    'cancelled': '❌'
-                }.get(status, '📊')
+            message_text = "📊 <b>Active Positions</b>\n\n"
+            
+            # Show each position
+            for position in open_positions:
+                symbol = position['symbol']
+                token = symbol.split('/')[0] if '/' in symbol else symbol
+                position_side = position['position_side']  # 'long' or 'short'
+                entry_price = position['entry_price']
+                current_amount = position['current_position_size']
+                trade_type = position.get('trade_type', 'manual')
                 
-                message_text += f"{status_emoji} <b>{status.upper()}</b> ({len(trades)} trades):\n"
+                # Position emoji and formatting
+                if position_side == 'long':
+                    pos_emoji = "🟢"
+                    direction = "LONG"
+                else:  # Short position
+                    pos_emoji = "🔴"
+                    direction = "SHORT"
                 
-                for trade in trades[:5]:  # Limit to 5 per status to avoid long messages
-                    symbol = trade['symbol']
-                    token = symbol.split('/')[0] if '/' in symbol else symbol
-                    side = trade['side'].upper()
-                    entry_price = trade.get('entry_price')
-                    entry_amount = trade.get('entry_amount')
-                    realized_pnl = trade.get('realized_pnl', 0)
-                    
-                    message_text += f"  • {side} {token}"
-                    
-                    if entry_price and entry_amount:
-                        message_text += f" | {entry_amount:.6f} @ ${entry_price:.2f}"
-                        
-                    if status == 'closed' and realized_pnl != 0:
-                        pnl_emoji = "🟢" if realized_pnl >= 0 else "🔴"
-                        message_text += f" | {pnl_emoji} ${realized_pnl:.2f}"
-                        
-                    if trade.get('stop_loss_price'):
-                        message_text += f" | SL: ${trade['stop_loss_price']:.2f}"
-                        
-                    message_text += "\n"
+                # Trade type indicator
+                type_indicator = ""
+                if trade_type == 'external':
+                    type_indicator = " 🔄"  # External/synced position
+                elif trade_type == 'bot':
+                    type_indicator = " 🤖"  # Bot-created position
                 
-                if len(trades) > 5:
-                    message_text += f"  ... and {len(trades) - 5} more\n"
-                    
-                message_text += "\n"
+                message_text += f"{pos_emoji} <b>{token} ({direction}){type_indicator}</b>\n"
+                message_text += f"   📏 Size: {abs(current_amount):.6f} {token}\n"
+                message_text += f"   💰 Entry: ${entry_price:.4f}\n"
+                
+                # Show stop loss if linked
+                if position.get('stop_loss_price'):
+                    sl_price = position['stop_loss_price']
+                    sl_status = "Pending" if not position.get('stop_loss_order_id') else "Active"
+                    message_text += f"   🛑 Stop Loss: ${sl_price:.4f} ({sl_status})\n"
+                
+                # Show take profit if linked
+                if position.get('take_profit_price'):
+                    tp_price = position['take_profit_price']
+                    tp_status = "Pending" if not position.get('take_profit_order_id') else "Active"
+                    message_text += f"   🎯 Take Profit: ${tp_price:.4f} ({tp_status})\n"
+                
+                message_text += f"   🆔 Lifecycle ID: {position['trade_lifecycle_id'][:8]}\n\n"
             
             # Add summary
-            total_trades = len(all_active_trades)
-            pending_count = len(active_trades_by_status.get('pending', []))
-            active_count = len(active_trades_by_status.get('active', []))
-            closed_count = len(active_trades_by_status.get('closed', []))
-            cancelled_count = len(active_trades_by_status.get('cancelled', []))
+            total_positions = len(open_positions)
+            bot_positions = len([p for p in open_positions if p.get('trade_type') == 'bot'])
+            external_positions = len([p for p in open_positions if p.get('trade_type') == 'external'])
             
             message_text += f"📈 <b>Summary:</b>\n"
-            message_text += f"  Total: {total_trades} | "
-            message_text += f"Pending: {pending_count} | "
-            message_text += f"Active: {active_count} | "
-            message_text += f"Closed: {closed_count} | "
-            message_text += f"Cancelled: {cancelled_count}\n\n"
+            message_text += f"  Total: {total_positions} | "
+            message_text += f"Bot: {bot_positions} | "
+            message_text += f"External: {external_positions}\n\n"
             
-            message_text += f"💡 This is Phase 1 testing - active trades run parallel to trade cycles"
+            message_text += f"🤖 <b>Legend:</b> 🤖 Bot-created • 🔄 External/synced\n"
+            message_text += f"💡 Use /positions for detailed P&L information"
             
             await context.bot.send_message(chat_id=chat_id, text=message_text.strip(), parse_mode='HTML')
             
         except Exception as e:
-            error_message = f"❌ Error processing active trades command: {str(e)}"
+            error_message = f"❌ Error processing active positions command: {str(e)}"
             await context.bot.send_message(chat_id=chat_id, text=error_message)
-            logger.error(f"Error in active trades command: {e}")
+            logger.error(f"Error in active positions command: {e}")
     
     async def market_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /market command."""

+ 9 - 63
src/monitoring/market_monitor.py

@@ -696,69 +696,15 @@ class MarketMonitor:
                                     order_data = stats.get_order_by_db_id(linked_order_db_id)
                                     if order_data:
                                         exchange_order_id = order_data.get('exchange_order_id')
-                                        
-                                        # Find active trade by entry order ID
-                                        all_active_trades = stats.get_all_active_trades()
-                                        for at in all_active_trades:
-                                            if at.get('entry_order_id') == exchange_order_id:
-                                                active_trade_id = at['id']
-                                                current_status = at['status']
-                                                
-                                                if current_status == 'pending' and action_type in ['long_opened', 'short_opened']:
-                                                    # Entry order filled - update active trade to active
-                                                    stats.update_active_trade_opened(
-                                                        active_trade_id, price, amount, timestamp_dt.isoformat()
-                                                    )
-                                                    logger.info(f"🆕 Active trade {active_trade_id} opened via fill {trade_id}")
-                                                    
-                                                elif current_status == 'active' and action_type in ['long_closed', 'short_closed']:
-                                                    # Exit order filled - calculate P&L and close active trade
-                                                    entry_price = at.get('entry_price', 0)
-                                                    active_trade_side = at.get('side')
-                                                    
-                                                    # Calculate realized P&L
-                                                    if active_trade_side == 'buy':  # Long position
-                                                        realized_pnl = amount * (price - entry_price)
-                                                    else:  # Short position  
-                                                        realized_pnl = amount * (entry_price - price)
-                                                    
-                                                    stats.update_active_trade_closed(
-                                                        active_trade_id, realized_pnl, timestamp_dt.isoformat()
-                                                    )
-                                                    logger.info(f"🆕 Active trade {active_trade_id} closed via fill {trade_id} - P&L: ${realized_pnl:.2f}")
-                                                break
-                                
-                                elif action_type in ['long_opened', 'short_opened']:
-                                    # External trade that opened a position - create external active trade
-                                    active_trade_id = stats.create_active_trade(
-                                        symbol=full_symbol,
-                                        side=side.lower(),
-                                        entry_order_id=None,  # External order
-                                        trade_type='external'
-                                    )
-                                    if active_trade_id:
-                                        stats.update_active_trade_opened(
-                                            active_trade_id, price, amount, timestamp_dt.isoformat()
-                                        )
-                                        logger.info(f"🆕 Created external active trade {active_trade_id} for {side.upper()} {full_symbol}")
-                                
-                                elif action_type in ['long_closed', 'short_closed']:
-                                    # External closure - close any active trade for this symbol
-                                    active_trade = stats.get_active_trade_by_symbol(full_symbol, status='active')
-                                    if active_trade:
-                                        entry_price = active_trade.get('entry_price', 0)
-                                        active_trade_side = active_trade.get('side')
-                                        
-                                        # Calculate realized P&L
-                                        if active_trade_side == 'buy':  # Long position
-                                            realized_pnl = amount * (price - entry_price)
-                                        else:  # Short position  
-                                            realized_pnl = amount * (entry_price - price)
-                                        
-                                        stats.update_active_trade_closed(
-                                            active_trade['id'], realized_pnl, timestamp_dt.isoformat()
-                                        )
-                                        logger.info(f"🆕 External closure: Active trade {active_trade['id']} closed - P&L: ${realized_pnl:.2f}")
+                                        logger.debug(f"📊 Bot order fill detected for exchange order {exchange_order_id} (handled by trade lifecycle system)")
+                            
+                            elif action_type in ['long_opened', 'short_opened']:
+                                # External trade that opened a position - handled by auto-sync in positions command
+                                logger.debug(f"📊 External position open detected: {side.upper()} {full_symbol} @ ${price:.2f} (handled by auto-sync)")
+                            
+                            elif action_type in ['long_closed', 'short_closed']:
+                                # External closure - handled by auto-sync in positions command  
+                                logger.debug(f"📊 External position close detected: {side.upper()} {full_symbol} @ ${price:.2f} (handled by auto-sync)")
                                 
                                 # Track symbol for potential stop loss activation
                                 symbols_with_fills.add(token)

+ 52 - 20
src/notifications/notification_manager.py

@@ -23,13 +23,19 @@ class NotificationManager:
     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."""
+        """Send notification for successful long order placement."""
         order_id = order.get('id', 'N/A')
-        order_type = "Market" if not order.get('price') else "Limit"
+        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}"
         
-        # For the new system, actual_price is the requested price for limit orders
-        # or estimated price for market orders
-        price_label = "Limit Price" if order_type == "Limit" else "Est. Price"
         status_message = "ORDER PLACED" if order_type == "Limit" else "ORDER SUBMITTED"
         
         success_message = f"""
@@ -39,12 +45,20 @@ class NotificationManager:
 • Token: {token}
 • Direction: LONG (Buy)
 • Amount: {token_amount:.6f} {token}
-• {price_label}: ${actual_price:,.2f}
+• {price_display}: {price_value}
 • Order Type: {order_type}
 • Order ID: <code>{order_id}</code>
 
-💰 <b>Order Summary:</b>
-• Order Value: ${token_amount * actual_price:,.2f}
+💰 <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"""
+            
+        success_message += f"""
 • Status: {status_message} ✅
 • Time: {datetime.now().strftime('%H:%M:%S')}"""
 
@@ -57,7 +71,7 @@ class NotificationManager:
             success_message += f"""
 
 💡 <b>Note:</b> Limit order placed on exchange
-• Will fill when market price reaches ${actual_price:,.2f}"""
+• Will fill when market price reaches {price_value}"""
             
         if stop_loss_price:
             success_message += f"""
@@ -76,18 +90,26 @@ class NotificationManager:
         """
         
         await query.edit_message_text(success_message, parse_mode='HTML')
-        logger.info(f"Long order placed: {token_amount:.6f} {token} @ ${actual_price:,.2f} ({order_type})")
+        
+        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."""
+        """Send notification for successful short order placement."""
         order_id = order.get('id', 'N/A')
-        order_type = "Market" if not order.get('price') else "Limit"
+        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}"
         
-        # For the new system, actual_price is the requested price for limit orders
-        # or estimated price for market orders
-        price_label = "Limit Price" if order_type == "Limit" else "Est. Price"
         status_message = "ORDER PLACED" if order_type == "Limit" else "ORDER SUBMITTED"
         
         success_message = f"""
@@ -97,12 +119,20 @@ class NotificationManager:
 • Token: {token}
 • Direction: SHORT (Sell)
 • Amount: {token_amount:.6f} {token}
-• {price_label}: ${actual_price:,.2f}
+• {price_display}: {price_value}
 • Order Type: {order_type}
 • Order ID: <code>{order_id}</code>
 
-💰 <b>Order Summary:</b>
-• Order Value: ${token_amount * actual_price:,.2f}
+💰 <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"""
+            
+        success_message += f"""
 • Status: {status_message} ✅
 • Time: {datetime.now().strftime('%H:%M:%S')}"""
 
@@ -115,7 +145,7 @@ class NotificationManager:
             success_message += f"""
 
 💡 <b>Note:</b> Limit order placed on exchange
-• Will fill when market price reaches ${actual_price:,.2f}"""
+• Will fill when market price reaches {price_value}"""
             
         if stop_loss_price:
             success_message += f"""
@@ -134,7 +164,9 @@ class NotificationManager:
         """
         
         await query.edit_message_text(success_message, parse_mode='HTML')
-        logger.info(f"Short order placed: {token_amount:.6f} {token} @ ${actual_price:,.2f} ({order_type})")
+        
+        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, 

+ 2 - 2
src/trading/trading_engine.py

@@ -139,9 +139,9 @@ class TradingEngine:
                         '_source': 'trades_table_phase4'
                     }
         
-        # 🔄 Fallback: Check exchange position data
+        # 🔄 Fallback: Check cached exchange position data
         try:
-            positions = self.client.get_positions()
+            positions = self.get_positions()  # Use cached positions method instead of direct client call
             if positions:
                 for pos in positions:
                     if pos.get('symbol') == symbol and pos.get('contracts', 0) != 0: