Forráskód Böngészése

Refactor InfoCommands and ManagementCommands for improved authorization and monitoring status

- Updated the _is_authorized method in InfoCommands to handle authorization checks using the Update object, enhancing flexibility for direct commands and callback queries.
- Improved error handling and logging in various command methods to ensure proper feedback when authorization fails or when unable to determine reply methods.
- Refactored ManagementCommands to access the market monitor's active status directly, improving clarity in system monitoring status reporting.
- Enhanced overall command structure for better readability and maintainability.
Carles Sentis 3 napja
szülő
commit
b29007221c

+ 415 - 325
src/commands/info_commands.py

@@ -33,350 +33,450 @@ class InfoCommands:
         self.trading_engine = trading_engine
         self.trading_engine = trading_engine
         self.notification_manager = notification_manager
         self.notification_manager = notification_manager
     
     
-    def _is_authorized(self, chat_id: str) -> bool:
-        """Check if the chat ID is authorized."""
-        return str(chat_id) == str(Config.TELEGRAM_CHAT_ID)
+    def _is_authorized(self, update: Update) -> bool:
+        """Check if the chat ID is authorized. Handles direct commands and callbacks."""
+        chat_id = None
+        if update.effective_chat: # For direct commands
+            chat_id = update.effective_chat.id
+        elif update.callback_query and update.callback_query.message: # For callback queries
+            chat_id = update.callback_query.message.chat_id
+        
+        if not chat_id:
+            logger.warning("Could not determine chat_id for authorization in InfoCommands.")
+            return False
+            
+        authorized = str(chat_id) == str(Config.TELEGRAM_CHAT_ID)
+        if not authorized:
+            logger.warning(f"Unauthorized access attempt in InfoCommands by chat_id: {chat_id}")
+        return authorized
     
     
     async def balance_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def balance_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /balance command."""
         """Handle the /balance command."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
-        balance = self.trading_engine.get_balance()
-        if balance:
-            balance_text = "💰 <b>Account Balance</b>\n\n"
-            
-            # Debug: Show raw balance structure (can be removed after debugging)
-            logger.debug(f"Raw balance data: {balance}")
-            
-            # CCXT balance structure includes 'free', 'used', and 'total'
-            total_balance = balance.get('total', {})
-            free_balance = balance.get('free', {})
-            used_balance = balance.get('used', {})
-            
-            # Get total portfolio value
-            total_portfolio_value = 0
-            
-            # Show USDC balance prominently
-            if 'USDC' in total_balance:
-                usdc_total = float(total_balance['USDC'])
-                usdc_free = float(free_balance.get('USDC', 0))
-                usdc_used = float(used_balance.get('USDC', 0))
+        reply_method = None
+        if update.callback_query:
+            reply_method = update.callback_query.message.reply_text
+        elif update.message:
+            reply_method = update.message.reply_text
+        else:
+            logger.error("balance_command: Cannot find a method to reply.")
+            await context.bot.send_message(chat_id=Config.TELEGRAM_CHAT_ID, text="Error: Could not determine how to reply.")
+            return
+
+        try:
+            balance = self.trading_engine.get_balance()
+            if balance:
+                balance_text = "💰 <b>Account Balance</b>\n\n"
+                
+                # Debug: Show raw balance structure (can be removed after debugging)
+                logger.debug(f"Raw balance data: {balance}")
                 
                 
-                balance_text += f"💵 <b>USDC:</b>\n"
-                balance_text += f"   📊 Total: ${usdc_total:,.2f}\n"
-                balance_text += f"   ✅ Available: ${usdc_free:,.2f}\n"
-                balance_text += f"   🔒 In Use: ${usdc_used:,.2f}\n\n"
+                # CCXT balance structure includes 'free', 'used', and 'total'
+                total_balance = balance.get('total', {})
+                free_balance = balance.get('free', {})
+                used_balance = balance.get('used', {})
                 
                 
-                total_portfolio_value += usdc_total
-            
-            # Show other non-zero balances
-            other_assets = []
-            for asset, amount in total_balance.items():
-                if asset != 'USDC' and float(amount) > 0:
-                    other_assets.append((asset, float(amount)))
-            
-            if other_assets:
-                balance_text += "📊 <b>Other Assets:</b>\n"
-                for asset, amount in other_assets:
-                    free_amount = float(free_balance.get(asset, 0))
-                    used_amount = float(used_balance.get(asset, 0))
+                # Get total portfolio value
+                total_portfolio_value = 0
+                
+                # Show USDC balance prominently
+                if 'USDC' in total_balance:
+                    usdc_total = float(total_balance['USDC'])
+                    usdc_free = float(free_balance.get('USDC', 0))
+                    usdc_used = float(used_balance.get('USDC', 0))
                     
                     
-                    balance_text += f"💵 <b>{asset}:</b>\n"
-                    balance_text += f"   📊 Total: {amount:.6f}\n"
-                    balance_text += f"   ✅ Available: {free_amount:.6f}\n"
-                    balance_text += f"   🔒 In Use: {used_amount:.6f}\n\n"
-            
-            # Portfolio summary
-            usdc_balance = float(total_balance.get('USDC', 0))
-            stats = self.trading_engine.get_stats()
-            if stats:
-                basic_stats = stats.get_basic_stats()
-                initial_balance = basic_stats.get('initial_balance', usdc_balance)
-                pnl = usdc_balance - initial_balance
-                pnl_percent = (pnl / initial_balance * 100) if initial_balance > 0 else 0
-                pnl_emoji = "🟢" if pnl >= 0 else "🔴"
+                    balance_text += f"💵 <b>USDC:</b>\n"
+                    balance_text += f"   📊 Total: ${usdc_total:,.2f}\n"
+                    balance_text += f"   ✅ Available: ${usdc_free:,.2f}\n"
+                    balance_text += f"   🔒 In Use: ${usdc_used:,.2f}\n\n"
+                    
+                    total_portfolio_value += usdc_total
                 
                 
-                balance_text += f"💼 <b>Portfolio Summary:</b>\n"
-                balance_text += f"   💰 Total Value: ${total_portfolio_value:,.2f}\n"
-                balance_text += f"   🚀 Available for Trading: ${float(free_balance.get('USDC', 0)):,.2f}\n"
-                balance_text += f"   🔒 In Active Use: ${float(used_balance.get('USDC', 0)):,.2f}\n\n"
-                balance_text += f"📊 <b>Performance:</b>\n"
-                balance_text += f"   💵 Initial: ${initial_balance:,.2f}\n"
-                balance_text += f"   {pnl_emoji} P&L: ${pnl:,.2f} ({pnl_percent:+.2f}%)\n"
-            
-            await context.bot.send_message(chat_id=chat_id, text=balance_text, parse_mode='HTML')
-        else:
-            await context.bot.send_message(chat_id=chat_id, text="❌ Could not fetch balance information")
+                # Show other non-zero balances
+                other_assets = []
+                for asset, amount in total_balance.items():
+                    if asset != 'USDC' and float(amount) > 0:
+                        other_assets.append((asset, float(amount)))
+                
+                if other_assets:
+                    balance_text += "📊 <b>Other Assets:</b>\n"
+                    for asset, amount in other_assets:
+                        free_amount = float(free_balance.get(asset, 0))
+                        used_amount = float(used_balance.get(asset, 0))
+                        
+                        balance_text += f"💵 <b>{asset}:</b>\n"
+                        balance_text += f"   📊 Total: {amount:.6f}\n"
+                        balance_text += f"   ✅ Available: {free_amount:.6f}\n"
+                        balance_text += f"   🔒 In Use: {used_amount:.6f}\n\n"
+                
+                # Portfolio summary
+                usdc_balance = float(total_balance.get('USDC', 0))
+                stats = self.trading_engine.get_stats()
+                if stats:
+                    basic_stats = stats.get_basic_stats()
+                    initial_balance = basic_stats.get('initial_balance', usdc_balance)
+                    pnl = usdc_balance - initial_balance
+                    pnl_percent = (pnl / initial_balance * 100) if initial_balance > 0 else 0
+                    pnl_emoji = "🟢" if pnl >= 0 else "🔴"
+                    
+                    balance_text += f"💼 <b>Portfolio Summary:</b>\n"
+                    balance_text += f"   💰 Total Value: ${total_portfolio_value:,.2f}\n"
+                    balance_text += f"   🚀 Available for Trading: ${float(free_balance.get('USDC', 0)):,.2f}\n"
+                    balance_text += f"   🔒 In Active Use: ${float(used_balance.get('USDC', 0)):,.2f}\n\n"
+                    balance_text += f"📊 <b>Performance:</b>\n"
+                    balance_text += f"   💵 Initial: ${initial_balance:,.2f}\n"
+                    balance_text += f"   {pnl_emoji} P&L: ${pnl:,.2f} ({pnl_percent:+.2f}%)\n"
+                
+                trading_engine_active = "✅ Active" if self.trading_engine else "❌ Inactive (Error)"
+                
+                # Construct the balance message
+                balance_text = f"""
+💰 <b>Account Balance & Info</b>
+
+💰 <b>Account Balance:</b>
+   💵 Total: ${usdc_total:,.2f}
+   ✅ Available: ${usdc_free:,.2f}
+   🔒 In Use: ${usdc_used:,.2f}
+
+📊 <b>Portfolio Summary:</b>
+   💰 Total Value: ${total_portfolio_value:,.2f}
+   🚀 Available for Trading: ${float(free_balance.get('USDC', 0)):,.2f}
+   🔒 In Active Use: ${float(used_balance.get('USDC', 0)):,.2f}
+
+⚙️ <b>System Status:</b>
+• Trading Engine: {trading_engine_active}
+• Data Source: Cached (updated on heartbeat)
+
+⏰ Last Update: {datetime.now().strftime('%H:%M:%S')}
+                """
+
+                await reply_method(text=balance_text.strip(), parse_mode='HTML')
+            else:
+                await reply_method(text="❌ Could not fetch balance information", parse_mode='HTML')
+        except Exception as e:
+            logger.error(f"Error in balance command: {e}")
+            await reply_method(text="❌ Error retrieving balance information.", parse_mode='HTML')
     
     
     async def positions_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def positions_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /positions command."""
         """Handle the /positions command."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
-        # 🧹 PHASE 4: Use unified trades table as the single source of truth
-        stats = self.trading_engine.get_stats()
-        if not stats:
-            await context.bot.send_message(chat_id=chat_id, text="❌ Trading statistics not available.")
+        reply_method = None
+        if update.callback_query:
+            reply_method = update.callback_query.message.reply_text
+        elif update.message:
+            reply_method = update.message.reply_text
+        else:
+            logger.error("positions_command: Cannot find a method to reply.")
+            await context.bot.send_message(chat_id=Config.TELEGRAM_CHAT_ID, text="Error: Could not determine how to reply.")
             return
             return
         
         
-        # 🆕 AUTO-SYNC logic removed as per user request.
-        # Assuming heartbeat updates the DB sufficiently.
-        sync_msg = "" 
-        
-        # Get open positions from unified trades table
-        open_positions = stats.get_open_positions()
-        
-        positions_text = f"📈 <b>Open Positions</b>\n\n{sync_msg}" # sync_msg will be empty
-        
-        if open_positions:
-            total_unrealized = 0
-            total_position_value = 0
-            
-            # Removed: fresh_exchange_positions = self.trading_engine.get_positions() or []
-            # Removed: exchange_data_map = {pos.get('symbol'): pos for pos in fresh_exchange_positions}
-            
-            for position_trade in open_positions:
-                symbol = position_trade['symbol']
-                # base_asset is the asset being traded, quote_asset is the settlement currency (usually USDC)
-                base_asset = symbol.split('/')[0] if '/' in symbol else symbol
-                # quote_asset = symbol.split('/')[1] if '/' in symbol else "USDC" # Not strictly needed for display here
-
-                position_side = position_trade['position_side']  # 'long' or 'short'
-                entry_price = position_trade['entry_price']
-                current_amount = position_trade['current_position_size'] # This is the size of the position
-                abs_current_amount = abs(current_amount)
-                trade_type = position_trade.get('trade_type', 'manual') # Default to manual if not specified
-                
-                mark_price = position_trade.get('mark_price', entry_price) # Default to entry if not available
+        try:
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await reply_method(text="❌ Trading statistics not available.", parse_mode='HTML')
+                return
+            
+            # 🆕 AUTO-SYNC logic removed as per user request.
+            # Assuming heartbeat updates the DB sufficiently.
+            sync_msg = "" 
+            
+            # Get open positions from unified trades table
+            open_positions = stats.get_open_positions()
+            
+            positions_text = f"📈 <b>Open Positions</b>\n\n{sync_msg}" # sync_msg will be empty
+            
+            if open_positions:
+                total_unrealized = 0
+                total_position_value = 0
                 
                 
-                # Calculate unrealized PnL
-                unrealized_pnl = position_trade.get('unrealized_pnl') # Prefer DB value if up-to-date
-                if unrealized_pnl is None: # Calculate if not directly available from DB
-                    if position_side == 'long':
-                        unrealized_pnl = (mark_price - entry_price) * abs_current_amount
-                    else:  # Short position
-                        unrealized_pnl = (entry_price - mark_price) * abs_current_amount
-                unrealized_pnl = unrealized_pnl or 0.0 # Ensure it's not None for calculations
+                for position_trade in open_positions:
+                    symbol = position_trade['symbol']
+                    # base_asset is the asset being traded, quote_asset is the settlement currency (usually USDC)
+                    base_asset = symbol.split('/')[0] if '/' in symbol else symbol
+                    # quote_asset = symbol.split('/')[1] if '/' in symbol else "USDC" # Not strictly needed for display here
 
 
-                # Tiered P&L Percentage Calculation
-                pnl_percentage = 0.0
-                exchange_pnl_percentage = position_trade.get('unrealized_pnl_percentage') # From exchange, e.g., 50.5 for 50.5%
-                margin_used = position_trade.get('margin_used')
+                    position_side = position_trade['position_side']  # 'long' or 'short'
+                    entry_price = position_trade['entry_price']
+                    current_amount = position_trade['current_position_size'] # This is the size of the position
+                    abs_current_amount = abs(current_amount)
+                    trade_type = position_trade.get('trade_type', 'manual') # Default to manual if not specified
+                    
+                    mark_price = position_trade.get('mark_price', entry_price) # Default to entry if not available
+                    
+                    # Calculate unrealized PnL
+                    unrealized_pnl = position_trade.get('unrealized_pnl') # Prefer DB value if up-to-date
+                    if unrealized_pnl is None: # Calculate if not directly available from DB
+                        if position_side == 'long':
+                            unrealized_pnl = (mark_price - entry_price) * abs_current_amount
+                        else:  # Short position
+                            unrealized_pnl = (entry_price - mark_price) * abs_current_amount
+                    unrealized_pnl = unrealized_pnl or 0.0 # Ensure it's not None for calculations
 
 
-                if exchange_pnl_percentage is not None:
-                    pnl_percentage = exchange_pnl_percentage 
-                elif margin_used is not None and margin_used > 0 and unrealized_pnl != 0:
-                    pnl_percentage = (unrealized_pnl / margin_used) * 100
-                elif entry_price != 0 and abs_current_amount != 0 and unrealized_pnl != 0:
-                    initial_value = entry_price * abs_current_amount
-                    pnl_percentage = (unrealized_pnl / initial_value) * 100
-                # else pnl_percentage remains 0.0
+                    # Tiered P&L Percentage Calculation
+                    pnl_percentage = 0.0
+                    exchange_pnl_percentage = position_trade.get('unrealized_pnl_percentage') # From exchange, e.g., 50.5 for 50.5%
+                    margin_used = position_trade.get('margin_used')
 
 
-                # Add to totals
-                current_pos_value_at_mark = abs_current_amount * mark_price
-                total_position_value += current_pos_value_at_mark
-                total_unrealized += unrealized_pnl
-                
-                # --- Position Header Formatting (Emoji, Direction, Leverage) ---
-                pos_emoji = ""
-                direction_text = ""
-                if position_side == 'long':
-                    pos_emoji = "🟢"
-                    direction_text = "LONG"
-                else:  # Short position
-                    pos_emoji = "🔴"
-                    direction_text = "SHORT"
-                
-                leverage = position_trade.get('leverage')
-                if leverage is not None:
-                    try:
-                        leverage_val = float(leverage)
-                        leverage_str = f"x{leverage_val:.1f}".rstrip('0').rstrip('.') if '.' in f"{leverage_val:.1f}" else f"x{int(leverage_val)}"
-                        direction_text = f"{direction_text} {leverage_str}"
-                    except ValueError:
-                        logger.warning(f"Could not parse leverage value: {leverage} for {symbol}")
+                    if exchange_pnl_percentage is not None:
+                        pnl_percentage = exchange_pnl_percentage 
+                    elif margin_used is not None and margin_used > 0 and unrealized_pnl != 0:
+                        pnl_percentage = (unrealized_pnl / margin_used) * 100
+                    elif entry_price != 0 and abs_current_amount != 0 and unrealized_pnl != 0:
+                        initial_value = entry_price * abs_current_amount
+                        pnl_percentage = (unrealized_pnl / initial_value) * 100
+                    # else pnl_percentage remains 0.0
 
 
-                # --- Format Output String ---
-                # Get token info for formatting prices
-                # Assuming get_formatter() is available and provides necessary precision
-                formatter = get_formatter() # Keep using if it wraps these precisions
+                    # Add to totals
+                    current_pos_value_at_mark = abs_current_amount * mark_price
+                    total_position_value += current_pos_value_at_mark
+                    total_unrealized += unrealized_pnl
+                    
+                    # --- Position Header Formatting (Emoji, Direction, Leverage) ---
+                    pos_emoji = ""
+                    direction_text = ""
+                    if position_side == 'long':
+                        pos_emoji = "🟢"
+                        direction_text = "LONG"
+                    else:  # Short position
+                        pos_emoji = "🔴"
+                        direction_text = "SHORT"
+                    
+                    leverage = position_trade.get('leverage')
+                    if leverage is not None:
+                        try:
+                            leverage_val = float(leverage)
+                            leverage_str = f"x{leverage_val:.1f}".rstrip('0').rstrip('.') if '.' in f"{leverage_val:.1f}" else f"x{int(leverage_val)}"
+                            direction_text = f"{direction_text} {leverage_str}"
+                        except ValueError:
+                            logger.warning(f"Could not parse leverage value: {leverage} for {symbol}")
 
 
-                # Get price precisions
-                entry_price_str = formatter.format_price_with_symbol(entry_price, base_asset)
-                mark_price_str = formatter.format_price_with_symbol(mark_price, base_asset)
+                    # --- Format Output String ---
+                    # Get token info for formatting prices
+                    # Assuming get_formatter() is available and provides necessary precision
+                    formatter = get_formatter() # Keep using if it wraps these precisions
 
 
-                # Get amount precision for position size
-                # base_precision = int(token_info.get('precision', {}).get('amount', 6) if token_info and token_info.get('precision') else 6) # Old way
-                # No longer need to fetch token_info separately for base_precision
-                # The formatter now handles amount precision directly.
-                size_str = formatter.format_amount(abs_current_amount, base_asset)
+                    # Get price precisions
+                    entry_price_str = formatter.format_price_with_symbol(entry_price, base_asset)
+                    mark_price_str = formatter.format_price_with_symbol(mark_price, base_asset)
 
 
-                type_indicator = ""
-                # Determine type_indicator based on trade_lifecycle_id or trade_type
-                if position_trade.get('trade_lifecycle_id'): # Primary indicator for bot managed
-                    type_indicator = " 🤖"
-                elif trade_type == 'external':
-                    type_indicator = " 🔄"
-                
-                positions_text += f"{pos_emoji} <b>{base_asset} ({direction_text}){type_indicator}</b>\n"
-                positions_text += f"   📏 Size: {size_str} {base_asset}\n" # Use the formatted size_str
-                positions_text += f"   💰 Entry: {entry_price_str}\n"
-                
-                if mark_price != 0 and abs(mark_price - entry_price) > 1e-9: # Only show mark if significantly different
-                    positions_text += f"   📈 Mark: {mark_price_str}\n"
-                
-                pnl_line_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
-                positions_text += f"   {pnl_line_emoji} P&L: ${unrealized_pnl:,.2f} ({pnl_percentage:+.2f}%)\n"
-                
-                # Show exchange-provided risk data if available
-                if margin_used is not None:
-                    positions_text += f"   💳 Margin Used: ${margin_used:,.2f}\n"
-                if position_trade.get('liquidation_price') is not None and position_trade.get('liquidation_price') > 0:
-                    liq_price_str = formatter.format_price_with_symbol(position_trade.get('liquidation_price'), base_asset)
-                    positions_text += f"   ⚠️ Liquidation: {liq_price_str}\n"
-                
-                # Show stop loss if linked
-                if position_trade.get('stop_loss_price'):
-                    sl_price = position_trade['stop_loss_price']
-                    sl_status = "Pending" if not position_trade.get('stop_loss_order_id') else "Active"
-                    positions_text += f"   🛑 Stop Loss: {formatter.format_price_with_symbol(sl_price, base_asset)} ({sl_status})\n"
+                    # Get amount precision for position size
+                    # base_precision = int(token_info.get('precision', {}).get('amount', 6) if token_info and token_info.get('precision') else 6) # Old way
+                    # No longer need to fetch token_info separately for base_precision
+                    # The formatter now handles amount precision directly.
+                    size_str = formatter.format_amount(abs_current_amount, base_asset)
+
+                    type_indicator = ""
+                    # Determine type_indicator based on trade_lifecycle_id or trade_type
+                    if position_trade.get('trade_lifecycle_id'): # Primary indicator for bot managed
+                        type_indicator = " 🤖"
+                    elif trade_type == 'external':
+                        type_indicator = " 🔄"
+                    
+                    positions_text += f"{pos_emoji} <b>{base_asset} ({direction_text}){type_indicator}</b>\n"
+                    positions_text += f"   📏 Size: {size_str} {base_asset}\n" # Use the formatted size_str
+                    positions_text += f"   💰 Entry: {entry_price_str}\n"
+                    
+                    if mark_price != 0 and abs(mark_price - entry_price) > 1e-9: # Only show mark if significantly different
+                        positions_text += f"   📈 Mark: {mark_price_str}\n"
+                    
+                    pnl_line_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
+                    positions_text += f"   {pnl_line_emoji} P&L: ${unrealized_pnl:,.2f} ({pnl_percentage:+.2f}%)\n"
+                    
+                    # Show exchange-provided risk data if available
+                    if margin_used is not None:
+                        positions_text += f"   💳 Margin Used: ${margin_used:,.2f}\n"
+                    if position_trade.get('liquidation_price') is not None and position_trade.get('liquidation_price') > 0:
+                        liq_price_str = formatter.format_price_with_symbol(position_trade.get('liquidation_price'), base_asset)
+                        positions_text += f"   ⚠️ Liquidation: {liq_price_str}\n"
+                    
+                    # Show stop loss if linked
+                    if position_trade.get('stop_loss_price'):
+                        sl_price = position_trade['stop_loss_price']
+                        sl_status = "Pending" if not position_trade.get('stop_loss_order_id') else "Active"
+                        positions_text += f"   🛑 Stop Loss: {formatter.format_price_with_symbol(sl_price, base_asset)} ({sl_status})\n"
+                    
+                    # Show take profit if linked
+                    if position_trade.get('take_profit_price'):
+                        tp_price = position_trade['take_profit_price']
+                        tp_status = "Pending" if not position_trade.get('take_profit_order_id') else "Active"
+                        positions_text += f"   🎯 Take Profit: {formatter.format_price_with_symbol(tp_price, base_asset)} ({tp_status})\n"
+                    
+                    positions_text += f"   🆔 Lifecycle ID: {position_trade['trade_lifecycle_id'][:8]}\n\n"
                 
                 
-                # Show take profit if linked
-                if position_trade.get('take_profit_price'):
-                    tp_price = position_trade['take_profit_price']
-                    tp_status = "Pending" if not position_trade.get('take_profit_order_id') else "Active"
-                    positions_text += f"   🎯 Take Profit: {formatter.format_price_with_symbol(tp_price, base_asset)} ({tp_status})\n"
+                # Portfolio summary
+                portfolio_emoji = "🟢" if total_unrealized >= 0 else "🔴"
+                positions_text += f"💼 <b>Total Portfolio:</b>\n"
+                positions_text += f"   💵 Total Value: ${total_position_value:,.2f}\n"
+                positions_text += f"   {portfolio_emoji} Total P&L: ${total_unrealized:,.2f}\n\n"
+                positions_text += f"🤖 <b>Legend:</b> 🤖 Bot-created • 🔄 External/synced\n"
+                positions_text += f"💡 Use /sl [token] [price] or /tp [token] [price] to set risk management"
                 
                 
-                positions_text += f"   🆔 Lifecycle ID: {position_trade['trade_lifecycle_id'][:8]}\n\n"
-            
-            # Portfolio summary
-            portfolio_emoji = "🟢" if total_unrealized >= 0 else "🔴"
-            positions_text += f"💼 <b>Total Portfolio:</b>\n"
-            positions_text += f"   💵 Total Value: ${total_position_value:,.2f}\n"
-            positions_text += f"   {portfolio_emoji} Total P&L: ${total_unrealized:,.2f}\n\n"
-            positions_text += f"🤖 <b>Legend:</b> 🤖 Bot-created • 🔄 External/synced\n"
-            positions_text += f"💡 Use /sl [token] [price] or /tp [token] [price] to set risk management"
+            else:
+                positions_text += "📭 No open positions\n\n"
+                positions_text += "💡 Use /long or /short to open a position"
             
             
-        else:
-            positions_text += "📭 No open positions\n\n"
-            positions_text += "💡 Use /long or /short to open a position"
-        
-        await context.bot.send_message(chat_id=chat_id, text=positions_text, parse_mode='HTML')
+            await reply_method(text=positions_text.strip(), parse_mode='HTML')
+        except Exception as e:
+            logger.error(f"Error in positions command: {e}")
+            await reply_method(text="❌ Error retrieving position information.", parse_mode='HTML')
     
     
     async def orders_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def orders_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /orders command."""
         """Handle the /orders command."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
-        orders = self.trading_engine.get_orders()
+        reply_method = None
+        if update.callback_query:
+            reply_method = update.callback_query.message.reply_text
+        elif update.message:
+            reply_method = update.message.reply_text
+        else:
+            logger.error("orders_command: Cannot find a method to reply.")
+            await context.bot.send_message(chat_id=Config.TELEGRAM_CHAT_ID, text="Error: Could not determine how to reply.")
+            return
         
         
-        if orders is not None:
-            if len(orders) > 0:
-                orders_text = "📋 <b>Open Orders</b>\n\n"
-                
-                # Group orders by symbol
-                orders_by_symbol = {}
-                for order in orders:
-                    symbol = order.get('symbol', '').replace('/USDC:USDC', '')
-                    if symbol not in orders_by_symbol:
-                        orders_by_symbol[symbol] = []
-                    orders_by_symbol[symbol].append(order)
-                
-                for symbol, symbol_orders in orders_by_symbol.items():
-                    orders_text += f"📊 <b>{symbol}</b>\n"
+        try:
+            orders = self.trading_engine.get_orders()
+            
+            if orders is not None:
+                if len(orders) > 0:
+                    orders_text = "📋 <b>Open Orders</b>\n\n"
                     
                     
-                    formatter = get_formatter()
-                    for order in symbol_orders:
-                        side = order.get('side', '').upper()
-                        amount = float(order.get('amount', 0))
-                        price = float(order.get('price', 0))
-                        order_type = order.get('type', 'unknown').title()
-                        order_id = order.get('id', 'N/A')
-                        
-                        # Order emoji
-                        side_emoji = "🟢" if side == "BUY" else "🔴"
-                        
-                        orders_text += f"   {side_emoji} {side} {amount:.6f} @ {formatter.format_price_with_symbol(price, symbol)}\n"
-                        orders_text += f"   📋 Type: {order_type} | ID: {order_id}\n"
-                        
-                        # Check for pending stop losses linked to this order
-                        stats = self.trading_engine.get_stats()
-                        if stats:
-                            # Try to find this order in our database to get its bot_order_ref_id
-                            order_in_db = stats.get_order_by_exchange_id(order_id)
-                            if order_in_db:
-                                bot_ref_id = order_in_db.get('bot_order_ref_id')
-                                if bot_ref_id:
-                                    # Look for pending stop losses with this order as parent
-                                    pending_sls = stats.get_orders_by_status(
-                                        status='pending_trigger', 
-                                        order_type_filter='stop_limit_trigger',
-                                        parent_bot_order_ref_id=bot_ref_id
-                                    )
-                                    
-                                    if pending_sls:
-                                        sl_order = pending_sls[0]  # Should only be one
-                                        sl_price = sl_order.get('price', 0)
-                                        sl_side = sl_order.get('side', '').upper()
-                                        orders_text += f"   🛑 Pending SL: {sl_side} @ {formatter.format_price_with_symbol(sl_price, symbol)} (activates when filled)\n"
+                    # Group orders by symbol
+                    orders_by_symbol = {}
+                    for order in orders:
+                        symbol = order.get('symbol', '').replace('/USDC:USDC', '')
+                        if symbol not in orders_by_symbol:
+                            orders_by_symbol[symbol] = []
+                        orders_by_symbol[symbol].append(order)
+                    
+                    for symbol, symbol_orders in orders_by_symbol.items():
+                        orders_text += f"📊 <b>{symbol}</b>\n"
                         
                         
-                        orders_text += "\n"
-                
-                orders_text += f"💼 <b>Total Orders:</b> {len(orders)}\n"
-                orders_text += f"💡 Use /coo [token] to cancel orders"
+                        formatter = get_formatter()
+                        for order in symbol_orders:
+                            side = order.get('side', '').upper()
+                            amount = float(order.get('amount', 0))
+                            price = float(order.get('price', 0))
+                            order_type = order.get('type', 'unknown').title()
+                            order_id = order.get('id', 'N/A')
+                            
+                            # Order emoji
+                            side_emoji = "🟢" if side == "BUY" else "🔴"
+                            
+                            orders_text += f"   {side_emoji} {side} {amount:.6f} @ {formatter.format_price_with_symbol(price, symbol)}\n"
+                            orders_text += f"   📋 Type: {order_type} | ID: {order_id}\n"
+                            
+                            # Check for pending stop losses linked to this order
+                            stats = self.trading_engine.get_stats()
+                            if stats:
+                                # Try to find this order in our database to get its bot_order_ref_id
+                                order_in_db = stats.get_order_by_exchange_id(order_id)
+                                if order_in_db:
+                                    bot_ref_id = order_in_db.get('bot_order_ref_id')
+                                    if bot_ref_id:
+                                        # Look for pending stop losses with this order as parent
+                                        pending_sls = stats.get_orders_by_status(
+                                            status='pending_trigger', 
+                                            order_type_filter='stop_limit_trigger',
+                                            parent_bot_order_ref_id=bot_ref_id
+                                        )
+                                        
+                                        if pending_sls:
+                                            sl_order = pending_sls[0]  # Should only be one
+                                            sl_price = sl_order.get('price', 0)
+                                            sl_side = sl_order.get('side', '').upper()
+                                            orders_text += f"   🛑 Pending SL: {sl_side} @ {formatter.format_price_with_symbol(sl_price, symbol)} (activates when filled)\n"
+                            
+                            orders_text += "\n"
+                    
+                    orders_text += f"💼 <b>Total Orders:</b> {len(orders)}\n"
+                    orders_text += f"💡 Use /coo [token] to cancel orders"
+                    
+                else:
+                    orders_text = "📋 <b>Open Orders</b>\n\n"
+                    orders_text += "📭 No open orders\n\n"
+                    orders_text += "💡 Use /long, /short, /sl, or /tp to create orders"
                 
                 
+                await reply_method(text=orders_text, parse_mode='HTML')
             else:
             else:
-                orders_text = "📋 <b>Open Orders</b>\n\n"
-                orders_text += "📭 No open orders\n\n"
-                orders_text += "💡 Use /long, /short, /sl, or /tp to create orders"
-            
-            await context.bot.send_message(chat_id=chat_id, text=orders_text, parse_mode='HTML')
-        else:
-            await context.bot.send_message(chat_id=chat_id, text="❌ Could not fetch orders")
+                await reply_method(text="❌ Could not fetch orders", parse_mode='HTML')
+        except Exception as e:
+            logger.error(f"Error in orders command: {e}")
+            await reply_method(text="❌ Error retrieving open orders.", parse_mode='HTML')
     
     
     async def stats_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def stats_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /stats command."""
         """Handle the /stats command."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
-        # Get current balance for stats
-        balance = self.trading_engine.get_balance()
-        current_balance = 0
-        if balance and balance.get('total'):
-            current_balance = float(balance['total'].get('USDC', 0))
-        
-        stats = self.trading_engine.get_stats()
-        if stats:
-            stats_message = stats.format_stats_message(current_balance)
-            await context.bot.send_message(chat_id=chat_id, text=stats_message, parse_mode='HTML')
+        reply_method = None
+        if update.callback_query:
+            reply_method = update.callback_query.message.reply_text
+        elif update.message:
+            reply_method = update.message.reply_text
         else:
         else:
-            await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
+            logger.error("stats_command: Cannot find a method to reply.")
+            await context.bot.send_message(chat_id=Config.TELEGRAM_CHAT_ID, text="Error: Could not determine how to reply.")
+            return
+
+        try:
+            # Get current balance for stats
+            balance = self.trading_engine.get_balance()
+            current_balance = 0
+            if balance and balance.get('total'):
+                current_balance = float(balance['total'].get('USDC', 0))
+            
+            stats = self.trading_engine.get_stats()
+            if stats:
+                stats_message = stats.format_stats_message(current_balance)
+                await reply_method(text=stats_message, parse_mode='HTML')
+            else:
+                await reply_method(text="❌ Could not load trading statistics", parse_mode='HTML')
+        except Exception as e:
+            logger.error(f"Error in stats command: {e}", exc_info=True) # Added exc_info for more details
+            await reply_method(text="❌ Error retrieving statistics.", parse_mode='HTML')
     
     
     async def trades_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def trades_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /trades command - Show recent trade history."""
         """Handle the /trades command - Show recent trade history."""
+        chat_id = update.effective_chat.id
         if not self._is_authorized(update):
         if not self._is_authorized(update):
             return
             return
             
             
+        reply_method = None
+        if update.callback_query:
+            reply_method = update.callback_query.message.reply_text
+        elif update.message:
+            reply_method = update.message.reply_text
+        else:
+            logger.error("trades_command: Cannot find a method to reply.")
+            # If it's a button click, the user won't see this if it's sent to Config.TELEGRAM_CHAT_ID
+            # unless that's the same chat.
+            # Consider editing the original message for callbacks if a reply_method can't be found.
+            # For now, let's try to send to the original chat if possible.
+            target_chat_id_for_error = chat_id if chat_id else Config.TELEGRAM_CHAT_ID
+            await context.bot.send_message(chat_id=target_chat_id_for_error, text="Error: Could not determine how to reply for /trades command.")
+            return
+            
         try:
         try:
             stats = self.trading_engine.get_stats()
             stats = self.trading_engine.get_stats()
             if not stats:
             if not stats:
-                await update.message.reply_text("❌ Trading statistics not available.", parse_mode='HTML')
+                await reply_method("❌ Trading statistics not available.", parse_mode='HTML')
                 return
                 return
             
             
             # Get recent trades (limit to last 20)
             # Get recent trades (limit to last 20)
             recent_trades = stats.get_recent_trades(limit=20)
             recent_trades = stats.get_recent_trades(limit=20)
             
             
             if not recent_trades:
             if not recent_trades:
-                await update.message.reply_text("📊 <b>No trades found.</b>", parse_mode='HTML')
+                await reply_method("📊 <b>No trades found.</b>", parse_mode='HTML')
                 return
                 return
             
             
             message = "📈 <b>Recent Trades (Last 20)</b>\n\n"
             message = "📈 <b>Recent Trades (Last 20)</b>\n\n"
@@ -414,17 +514,16 @@ class InfoCommands:
                 message += f"{side_emoji} <b>{side}</b> {amount} {token} @ ${price:,.2f}\n"
                 message += f"{side_emoji} <b>{side}</b> {amount} {token} @ ${price:,.2f}\n"
                 message += f"   {pnl_emoji} P&L: {pnl_str} | {time_str}\n\n"
                 message += f"   {pnl_emoji} P&L: {pnl_str} | {time_str}\n\n"
             
             
-            await update.message.reply_text(message, parse_mode='HTML')
+            await reply_method(message, parse_mode='HTML')
             
             
         except Exception as e:
         except Exception as e:
             logger.error(f"Error in trades command: {e}")
             logger.error(f"Error in trades command: {e}")
-            await update.message.reply_text("❌ Error retrieving trade history.", parse_mode='HTML')
+            await reply_method("❌ Error retrieving trade history.", parse_mode='HTML')
     
     
     async def active_trades_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def active_trades_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /active command to show active trades (using open positions)."""
         """Handle the /active command to show active trades (using open positions)."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
             
             
         try:
         try:
@@ -511,8 +610,7 @@ class InfoCommands:
     async def market_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def market_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /market command."""
         """Handle the /market command."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
         # Get token from arguments or use default
         # Get token from arguments or use default
@@ -577,8 +675,7 @@ class InfoCommands:
     async def price_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def price_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /price command."""
         """Handle the /price command."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
         # Get token from arguments or use default
         # Get token from arguments or use default
@@ -622,8 +719,7 @@ class InfoCommands:
     async def performance_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def performance_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /performance command to show token performance ranking or detailed stats."""
         """Handle the /performance command to show token performance ranking or detailed stats."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
         try:
         try:
@@ -752,19 +848,19 @@ class InfoCommands:
         # Detailed stats display
         # Detailed stats display
         pnl_emoji = "🟢" if token_stats['total_pnl'] >= 0 else "🔴"
         pnl_emoji = "🟢" if token_stats['total_pnl'] >= 0 else "🔴"
         
         
-        total_pnl_str = formatter.format_price_with_symbol(token_stats['total_pnl'], quote_asset=Config.QUOTE_CURRENCY)
-        completed_volume_str = formatter.format_price_with_symbol(token_stats['completed_volume'], quote_asset=Config.QUOTE_CURRENCY)
-        expectancy_str = formatter.format_price_with_symbol(token_stats['expectancy'], quote_asset=Config.QUOTE_CURRENCY)
-        largest_win_str = formatter.format_price_with_symbol(token_stats['largest_win'], quote_asset=Config.QUOTE_CURRENCY)
-        largest_loss_str = formatter.format_price_with_symbol(token_stats['largest_loss'], quote_asset=Config.QUOTE_CURRENCY) # Assuming loss is positive number
-        avg_win_str = formatter.format_price_with_symbol(token_stats['avg_win'], quote_asset=Config.QUOTE_CURRENCY)
-        avg_loss_str = formatter.format_price_with_symbol(token_stats['avg_loss'], quote_asset=Config.QUOTE_CURRENCY) # Assuming loss is positive number
+        total_pnl_str = formatter.format_price_with_symbol(token_stats['total_pnl'])
+        completed_volume_str = formatter.format_price_with_symbol(token_stats['completed_volume'])
+        expectancy_str = formatter.format_price_with_symbol(token_stats['expectancy'])
+        largest_win_str = formatter.format_price_with_symbol(token_stats['largest_win'])
+        largest_loss_str = formatter.format_price_with_symbol(token_stats['largest_loss']) # Assuming loss is positive number
+        avg_win_str = formatter.format_price_with_symbol(token_stats['avg_win'])
+        avg_loss_str = formatter.format_price_with_symbol(token_stats['avg_loss']) # Assuming loss is positive number
 
 
 
 
         performance_text = f"""
         performance_text = f"""
 📊 <b>{token} Detailed Performance</b>
 📊 <b>{token} Detailed Performance</b>
 
 
-💰 <b>P&L Summary:</b>
+🎯 <b>P&L Summary:</b>
 • {pnl_emoji} Total P&L: {total_pnl_str} ({token_stats['pnl_percentage']:+.2f}%)
 • {pnl_emoji} Total P&L: {total_pnl_str} ({token_stats['pnl_percentage']:+.2f}%)
 • 💵 Total Volume: {completed_volume_str}
 • 💵 Total Volume: {completed_volume_str}
 • 📈 Expectancy: {expectancy_str}
 • 📈 Expectancy: {expectancy_str}
@@ -799,11 +895,11 @@ class InfoCommands:
                 trade_base_asset = trade_symbol.split('/')[0] if '/' in trade_symbol else trade_symbol
                 trade_base_asset = trade_symbol.split('/')[0] if '/' in trade_symbol else trade_symbol
                 
                 
                 # Formatting trade value. Assuming 'value' is in quote currency.
                 # Formatting trade value. Assuming 'value' is in quote currency.
-                trade_value_str = formatter.format_price_with_symbol(trade.get('value', 0), quote_asset=Config.QUOTE_CURRENCY)
+                trade_value_str = formatter.format_price_with_symbol(trade.get('value', 0))
                 
                 
                 pnl_display_str = ""
                 pnl_display_str = ""
                 if trade.get('pnl', 0) != 0:
                 if trade.get('pnl', 0) != 0:
-                    trade_pnl_str = formatter.format_price_with_symbol(trade.get('pnl', 0), quote_asset=Config.QUOTE_CURRENCY)
+                    trade_pnl_str = formatter.format_price_with_symbol(trade.get('pnl', 0))
                     pnl_display_str = f" | P&L: {trade_pnl_str}"
                     pnl_display_str = f" | P&L: {trade_pnl_str}"
                 
                 
                 performance_text += f"• {side_emoji} {trade['side'].upper()} {trade_value_str} @ {trade_time}{pnl_display_str}\n"
                 performance_text += f"• {side_emoji} {trade['side'].upper()} {trade_value_str} @ {trade_time}{pnl_display_str}\n"
@@ -815,8 +911,7 @@ class InfoCommands:
     async def daily_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def daily_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /daily command to show daily performance stats."""
         """Handle the /daily command to show daily performance stats."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
         try:
         try:
@@ -847,7 +942,7 @@ class InfoCommands:
             for day_stats_item in daily_stats: # Renamed to avoid conflict
             for day_stats_item in daily_stats: # Renamed to avoid conflict
                 if day_stats_item['has_trades']:
                 if day_stats_item['has_trades']:
                     pnl_emoji = "🟢" if day_stats_item['pnl'] >= 0 else "🔴"
                     pnl_emoji = "🟢" if day_stats_item['pnl'] >= 0 else "🔴"
-                    pnl_str = formatter.format_price_with_symbol(day_stats_item['pnl'], quote_asset=Config.QUOTE_CURRENCY)
+                    pnl_str = formatter.format_price_with_symbol(day_stats_item['pnl'])
                     daily_text += f"📊 <b>{day_stats_item['date_formatted']}</b>\\n"
                     daily_text += f"📊 <b>{day_stats_item['date_formatted']}</b>\\n"
                     daily_text += f"   {pnl_emoji} P&L: {pnl_str} ({day_stats_item['pnl_pct']:+.1f}%)\\n"
                     daily_text += f"   {pnl_emoji} P&L: {pnl_str} ({day_stats_item['pnl_pct']:+.1f}%)\\n"
                     daily_text += f"   🔄 Trades: {day_stats_item['trades']}\\n\\n"
                     daily_text += f"   🔄 Trades: {day_stats_item['trades']}\\n\\n"
@@ -862,8 +957,8 @@ class InfoCommands:
             if trading_days_count > 0:
             if trading_days_count > 0:
                 avg_daily_pnl = total_pnl_all_days / trading_days_count
                 avg_daily_pnl = total_pnl_all_days / trading_days_count
                 avg_pnl_emoji = "🟢" if avg_daily_pnl >= 0 else "🔴"
                 avg_pnl_emoji = "🟢" if avg_daily_pnl >= 0 else "🔴"
-                total_pnl_all_days_str = formatter.format_price_with_symbol(total_pnl_all_days, quote_asset=Config.QUOTE_CURRENCY)
-                avg_daily_pnl_str = formatter.format_price_with_symbol(avg_daily_pnl, quote_asset=Config.QUOTE_CURRENCY)
+                total_pnl_all_days_str = formatter.format_price_with_symbol(total_pnl_all_days)
+                avg_daily_pnl_str = formatter.format_price_with_symbol(avg_daily_pnl)
                 
                 
                 daily_text += f"📈 <b>Period Summary:</b>\\n"
                 daily_text += f"📈 <b>Period Summary:</b>\\n"
                 daily_text += f"   {avg_pnl_emoji} Total P&L: {total_pnl_all_days_str}\\n"
                 daily_text += f"   {avg_pnl_emoji} Total P&L: {total_pnl_all_days_str}\\n"
@@ -881,8 +976,7 @@ class InfoCommands:
     async def weekly_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def weekly_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /weekly command to show weekly performance stats."""
         """Handle the /weekly command to show weekly performance stats."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
         try:
         try:
@@ -913,7 +1007,7 @@ class InfoCommands:
             for week_stats_item in weekly_stats_list: # Renamed
             for week_stats_item in weekly_stats_list: # Renamed
                 if week_stats_item['has_trades']:
                 if week_stats_item['has_trades']:
                     pnl_emoji = "🟢" if week_stats_item['pnl'] >= 0 else "🔴"
                     pnl_emoji = "🟢" if week_stats_item['pnl'] >= 0 else "🔴"
-                    pnl_str = formatter.format_price_with_symbol(week_stats_item['pnl'], quote_asset=Config.QUOTE_CURRENCY)
+                    pnl_str = formatter.format_price_with_symbol(week_stats_item['pnl'])
                     weekly_text += f"📈 <b>{week_stats_item['week_formatted']}</b>\\n"
                     weekly_text += f"📈 <b>{week_stats_item['week_formatted']}</b>\\n"
                     weekly_text += f"   {pnl_emoji} P&L: {pnl_str} ({week_stats_item['pnl_pct']:+.1f}%)\\n"
                     weekly_text += f"   {pnl_emoji} P&L: {pnl_str} ({week_stats_item['pnl_pct']:+.1f}%)\\n"
                     weekly_text += f"   🔄 Trades: {week_stats_item['trades']}\\n\\n"
                     weekly_text += f"   🔄 Trades: {week_stats_item['trades']}\\n\\n"
@@ -928,8 +1022,8 @@ class InfoCommands:
             if trading_weeks_count > 0:
             if trading_weeks_count > 0:
                 avg_weekly_pnl = total_pnl_all_weeks / trading_weeks_count
                 avg_weekly_pnl = total_pnl_all_weeks / trading_weeks_count
                 avg_pnl_emoji = "🟢" if avg_weekly_pnl >= 0 else "🔴"
                 avg_pnl_emoji = "🟢" if avg_weekly_pnl >= 0 else "🔴"
-                total_pnl_all_weeks_str = formatter.format_price_with_symbol(total_pnl_all_weeks, quote_asset=Config.QUOTE_CURRENCY)
-                avg_weekly_pnl_str = formatter.format_price_with_symbol(avg_weekly_pnl, quote_asset=Config.QUOTE_CURRENCY)
+                total_pnl_all_weeks_str = formatter.format_price_with_symbol(total_pnl_all_weeks)
+                avg_weekly_pnl_str = formatter.format_price_with_symbol(avg_weekly_pnl)
 
 
                 weekly_text += f"📅 <b>Period Summary:</b>\\n"
                 weekly_text += f"📅 <b>Period Summary:</b>\\n"
                 weekly_text += f"   {avg_pnl_emoji} Total P&L: {total_pnl_all_weeks_str}\\n"
                 weekly_text += f"   {avg_pnl_emoji} Total P&L: {total_pnl_all_weeks_str}\\n"
@@ -947,8 +1041,7 @@ class InfoCommands:
     async def monthly_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def monthly_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /monthly command to show monthly performance stats."""
         """Handle the /monthly command to show monthly performance stats."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
         try:
         try:
@@ -979,7 +1072,7 @@ class InfoCommands:
             for month_stats_item in monthly_stats_list: # Renamed
             for month_stats_item in monthly_stats_list: # Renamed
                 if month_stats_item['has_trades']:
                 if month_stats_item['has_trades']:
                     pnl_emoji = "🟢" if month_stats_item['pnl'] >= 0 else "🔴"
                     pnl_emoji = "🟢" if month_stats_item['pnl'] >= 0 else "🔴"
-                    pnl_str = formatter.format_price_with_symbol(month_stats_item['pnl'], quote_asset=Config.QUOTE_CURRENCY)
+                    pnl_str = formatter.format_price_with_symbol(month_stats_item['pnl'])
                     monthly_text += f"📅 <b>{month_stats_item['month_formatted']}</b>\\n"
                     monthly_text += f"📅 <b>{month_stats_item['month_formatted']}</b>\\n"
                     monthly_text += f"   {pnl_emoji} P&L: {pnl_str} ({month_stats_item['pnl_pct']:+.1f}%)\\n"
                     monthly_text += f"   {pnl_emoji} P&L: {pnl_str} ({month_stats_item['pnl_pct']:+.1f}%)\\n"
                     monthly_text += f"   🔄 Trades: {month_stats_item['trades']}\\n\\n"
                     monthly_text += f"   🔄 Trades: {month_stats_item['trades']}\\n\\n"
@@ -994,8 +1087,8 @@ class InfoCommands:
             if trading_months_count > 0:
             if trading_months_count > 0:
                 avg_monthly_pnl = total_pnl_all_months / trading_months_count
                 avg_monthly_pnl = total_pnl_all_months / trading_months_count
                 avg_pnl_emoji = "🟢" if avg_monthly_pnl >= 0 else "🔴"
                 avg_pnl_emoji = "🟢" if avg_monthly_pnl >= 0 else "🔴"
-                total_pnl_all_months_str = formatter.format_price_with_symbol(total_pnl_all_months, quote_asset=Config.QUOTE_CURRENCY)
-                avg_monthly_pnl_str = formatter.format_price_with_symbol(avg_monthly_pnl, quote_asset=Config.QUOTE_CURRENCY)
+                total_pnl_all_months_str = formatter.format_price_with_symbol(total_pnl_all_months)
+                avg_monthly_pnl_str = formatter.format_price_with_symbol(avg_monthly_pnl)
                 
                 
                 monthly_text += f"📈 <b>Period Summary:</b>\\n"
                 monthly_text += f"📈 <b>Period Summary:</b>\\n"
                 monthly_text += f"   {avg_pnl_emoji} Total P&L: {total_pnl_all_months_str}\\n"
                 monthly_text += f"   {avg_pnl_emoji} Total P&L: {total_pnl_all_months_str}\\n"
@@ -1013,8 +1106,7 @@ class InfoCommands:
     async def risk_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def risk_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /risk command to show advanced risk metrics."""
         """Handle the /risk command to show advanced risk metrics."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
         try:
         try:
@@ -1040,7 +1132,7 @@ class InfoCommands:
                     "📭 <b>Insufficient Data</b>\n\n"
                     "📭 <b>Insufficient Data</b>\n\n"
                     f"• Current completed trades: {basic_stats['completed_trades']}\n"
                     f"• Current completed trades: {basic_stats['completed_trades']}\n"
                     f"• Required for risk analysis: 2+ trades\n"
                     f"• Required for risk analysis: 2+ trades\n"
-                    f"• Daily balance snapshots: {len(stats.data.get('daily_balances', []))}\n\n"
+                    f"• Daily balance snapshots: {stats.get_daily_balance_record_count()}\n\n"
                     "💡 <b>To enable risk analysis:</b>\n"
                     "💡 <b>To enable risk analysis:</b>\n"
                     "• Complete more trades to generate returns data\n"
                     "• Complete more trades to generate returns data\n"
                     "• Bot automatically records daily balance snapshots\n"
                     "• Bot automatically records daily balance snapshots\n"
@@ -1116,7 +1208,7 @@ class InfoCommands:
 
 
 📈 <b>Data Based On:</b>
 📈 <b>Data Based On:</b>
 • Completed Trades: {basic_stats['completed_trades']}
 • Completed Trades: {basic_stats['completed_trades']}
-• Daily Balance Records: {len(stats.data.get('daily_balances', []))}
+• Daily Balance Records: {stats.get_daily_balance_record_count()}
 • Trading Period: {basic_stats['days_active']} days
 • Trading Period: {basic_stats['days_active']} days
 
 
 🔄 Use /stats for trading performance metrics
 🔄 Use /stats for trading performance metrics
@@ -1132,8 +1224,7 @@ class InfoCommands:
     async def balance_adjustments_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def balance_adjustments_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /balance_adjustments command to show deposit/withdrawal history."""
         """Handle the /balance_adjustments command to show deposit/withdrawal history."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
         try:
         try:
@@ -1214,8 +1305,7 @@ class InfoCommands:
     async def commands_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def commands_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /commands and /c command with quick action buttons."""
         """Handle the /commands and /c command with quick action buttons."""
         chat_id = update.effective_chat.id
         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.")
+        if not self._is_authorized(update):
             return
             return
         
         
         commands_text = """
         commands_text = """

+ 3 - 3
src/commands/management_commands.py

@@ -62,7 +62,7 @@ class ManagementCommands:
         formatter = get_formatter()
         formatter = get_formatter()
         
         
         # Safety checks for monitoring attributes
         # Safety checks for monitoring attributes
-        monitoring_active = self.market_monitor.is_running
+        monitoring_active = self.market_monitor._monitoring_active
         
         
         status_text = f"""
         status_text = f"""
 🔄 <b>System Monitoring Status</b>
 🔄 <b>System Monitoring Status</b>
@@ -372,7 +372,7 @@ Will trigger when {token} price moves {alarm['direction']} {target_price_str}
 • Client Connected: {'✅ Yes' if self.trading_engine.client else '❌ No'}
 • Client Connected: {'✅ Yes' if self.trading_engine.client else '❌ No'}
 
 
 🔄 <b>Market Monitor:</b>
 🔄 <b>Market Monitor:</b>
-• Running: {'✅ Yes' if self.market_monitor.is_running else '❌ No'}
+• Running: {'✅ Yes' if self.market_monitor._monitoring_active else '❌ No'}
 
 
 📁 <b>State Files:</b>
 📁 <b>State Files:</b>
 • Price Alarms: {'✅ Exists' if os.path.exists('data/price_alarms.json') else '❌ Missing'}
 • Price Alarms: {'✅ Exists' if os.path.exists('data/price_alarms.json') else '❌ Missing'}
@@ -469,7 +469,7 @@ Will trigger when {token} price moves {alarm['direction']} {target_price_str}
 • Start Date: {basic_stats['start_date']}
 • Start Date: {basic_stats['start_date']}
 
 
 🔄 <b>Monitoring Status:</b>
 🔄 <b>Monitoring Status:</b>
-• Market Monitor: {'✅ Active' if self.market_monitor.is_running else '❌ Inactive'}
+• Market Monitor: {'✅ Active' if self.market_monitor._monitoring_active else '❌ Inactive'}
 • External Trades: ✅ Active
 • External Trades: ✅ Active
 • Price Alarms: ✅ Active ({self.alarm_manager.get_statistics()['total_active']} active)
 • Price Alarms: ✅ Active ({self.alarm_manager.get_statistics()['total_active']} active)
 
 

+ 7 - 6
src/migrations/migrate_db.py

@@ -84,15 +84,15 @@ def add_missing_columns(conn: sqlite3.Connection, table_name: str, expected_sche
     else:
     else:
         logger.info(f"Schema migration: Table '{table_name}' is already up to date.")
         logger.info(f"Schema migration: Table '{table_name}' is already up to date.")
 
 
-def run_migrations():
+def run_migrations(db_path_to_migrate: str):
     """
     """
-    Runs all database migrations.
+    Runs all database migrations on the specified database file.
     Currently, it only checks and adds missing columns to the 'trades' table.
     Currently, it only checks and adds missing columns to the 'trades' table.
     """
     """
-    logger.info(f"Connecting to database at {DB_PATH} for migration check...")
+    logger.info(f"Connecting to database at {db_path_to_migrate} for migration check...")
     
     
     # Ensure the data directory exists before trying to connect
     # Ensure the data directory exists before trying to connect
-    data_dir = os.path.dirname(DB_PATH)
+    data_dir = os.path.dirname(db_path_to_migrate)
     if data_dir and not os.path.exists(data_dir):
     if data_dir and not os.path.exists(data_dir):
         try:
         try:
             os.makedirs(data_dir)
             os.makedirs(data_dir)
@@ -104,7 +104,7 @@ def run_migrations():
 
 
     conn = None
     conn = None
     try:
     try:
-        conn = sqlite3.connect(DB_PATH)
+        conn = sqlite3.connect(db_path_to_migrate)
         # Check and update 'trades' table
         # Check and update 'trades' table
         # First, ensure the table exists (it should be created by TradingStats._create_tables if new)
         # First, ensure the table exists (it should be created by TradingStats._create_tables if new)
         cursor = conn.cursor()
         cursor = conn.cursor()
@@ -130,5 +130,6 @@ if __name__ == '__main__':
     # Configure logging for direct script execution (optional)
     # Configure logging for direct script execution (optional)
     logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
     logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
     logger.info("Running migrations directly...")
     logger.info("Running migrations directly...")
-    run_migrations()
+    # When running directly, use the globally defined DB_PATH
+    run_migrations(DB_PATH)
     logger.info("Migrations script finished.") 
     logger.info("Migrations script finished.") 

+ 6 - 1
src/trading/trading_stats.py

@@ -44,7 +44,7 @@ class TradingStats:
         # This ensures the schema is up-to-date when the connection is made
         # This ensures the schema is up-to-date when the connection is made
         # and tables are potentially created for the first time.
         # and tables are potentially created for the first time.
         logger.info("Running database migrations if needed...")
         logger.info("Running database migrations if needed...")
-        run_db_migrations() # Uses DB_PATH defined in migrate_db.py, which should be the same
+        run_db_migrations(self.db_path) # Pass the correct db_path
         logger.info("Database migration check complete.")
         logger.info("Database migration check complete.")
         
         
         self.conn = sqlite3.connect(self.db_path, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
         self.conn = sqlite3.connect(self.db_path, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
@@ -1656,3 +1656,8 @@ class TradingStats:
             return False
             return False
 
 
     # --- End Trade Lifecycle Management ---
     # --- End Trade Lifecycle Management ---
+
+    def get_daily_balance_record_count(self) -> int:
+        """Get the total number of daily balance records."""
+        row = self._fetchone_query("SELECT COUNT(*) as count FROM daily_balances")
+        return row['count'] if row and 'count' in row else 0