Эх сурвалжийг харах

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 өдөр өмнө
parent
commit
b29007221c

+ 415 - 325
src/commands/info_commands.py

@@ -33,350 +33,450 @@ class InfoCommands:
         self.trading_engine = trading_engine
         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:
         """Handle the /balance command."""
         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
         
-        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:
         """Handle the /positions command."""
         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
         
-        # 🧹 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
         
-        # 🆕 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:
         """Handle the /orders command."""
         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
         
-        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:
-                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:
         """Handle the /stats command."""
         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
         
-        # 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:
-            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:
         """Handle the /trades command - Show recent trade history."""
+        chat_id = update.effective_chat.id
         if not self._is_authorized(update):
             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:
             stats = self.trading_engine.get_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
             
             # Get recent trades (limit to last 20)
             recent_trades = stats.get_recent_trades(limit=20)
             
             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
             
             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"   {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:
             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:
         """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.")
+        if not self._is_authorized(update):
             return
             
         try:
@@ -511,8 +610,7 @@ class InfoCommands:
     async def market_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /market command."""
         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
         
         # 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:
         """Handle the /price command."""
         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
         
         # 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:
         """Handle the /performance command to show token performance ranking or detailed stats."""
         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
         
         try:
@@ -752,19 +848,19 @@ class InfoCommands:
         # Detailed stats display
         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"""
 📊 <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}%)
 • 💵 Total Volume: {completed_volume_str}
 • 📈 Expectancy: {expectancy_str}
@@ -799,11 +895,11 @@ class InfoCommands:
                 trade_base_asset = trade_symbol.split('/')[0] if '/' in trade_symbol else trade_symbol
                 
                 # 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 = ""
                 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}"
                 
                 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:
         """Handle the /daily command to show daily performance stats."""
         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
         
         try:
@@ -847,7 +942,7 @@ class InfoCommands:
             for day_stats_item in daily_stats: # Renamed to avoid conflict
                 if day_stats_item['has_trades']:
                     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"   {pnl_emoji} P&L: {pnl_str} ({day_stats_item['pnl_pct']:+.1f}%)\\n"
                     daily_text += f"   🔄 Trades: {day_stats_item['trades']}\\n\\n"
@@ -862,8 +957,8 @@ class InfoCommands:
             if trading_days_count > 0:
                 avg_daily_pnl = total_pnl_all_days / trading_days_count
                 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"   {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:
         """Handle the /weekly command to show weekly performance stats."""
         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
         
         try:
@@ -913,7 +1007,7 @@ class InfoCommands:
             for week_stats_item in weekly_stats_list: # Renamed
                 if week_stats_item['has_trades']:
                     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"   {pnl_emoji} P&L: {pnl_str} ({week_stats_item['pnl_pct']:+.1f}%)\\n"
                     weekly_text += f"   🔄 Trades: {week_stats_item['trades']}\\n\\n"
@@ -928,8 +1022,8 @@ class InfoCommands:
             if trading_weeks_count > 0:
                 avg_weekly_pnl = total_pnl_all_weeks / trading_weeks_count
                 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"   {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:
         """Handle the /monthly command to show monthly performance stats."""
         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
         
         try:
@@ -979,7 +1072,7 @@ class InfoCommands:
             for month_stats_item in monthly_stats_list: # Renamed
                 if month_stats_item['has_trades']:
                     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"   {pnl_emoji} P&L: {pnl_str} ({month_stats_item['pnl_pct']:+.1f}%)\\n"
                     monthly_text += f"   🔄 Trades: {month_stats_item['trades']}\\n\\n"
@@ -994,8 +1087,8 @@ class InfoCommands:
             if trading_months_count > 0:
                 avg_monthly_pnl = total_pnl_all_months / trading_months_count
                 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"   {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:
         """Handle the /risk command to show advanced risk metrics."""
         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
         
         try:
@@ -1040,7 +1132,7 @@ class InfoCommands:
                     "📭 <b>Insufficient Data</b>\n\n"
                     f"• Current completed trades: {basic_stats['completed_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"
                     "• Complete more trades to generate returns data\n"
                     "• Bot automatically records daily balance snapshots\n"
@@ -1116,7 +1208,7 @@ class InfoCommands:
 
 📈 <b>Data Based On:</b>
 • 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
 
 🔄 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:
         """Handle the /balance_adjustments command to show deposit/withdrawal history."""
         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
         
         try:
@@ -1214,8 +1305,7 @@ class InfoCommands:
     async def commands_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /commands and /c command with quick action buttons."""
         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
         
         commands_text = """

+ 3 - 3
src/commands/management_commands.py

@@ -62,7 +62,7 @@ class ManagementCommands:
         formatter = get_formatter()
         
         # Safety checks for monitoring attributes
-        monitoring_active = self.market_monitor.is_running
+        monitoring_active = self.market_monitor._monitoring_active
         
         status_text = f"""
 🔄 <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'}
 
 🔄 <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>
 • 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']}
 
 🔄 <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
 • 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:
         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.
     """
-    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
-    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):
         try:
             os.makedirs(data_dir)
@@ -104,7 +104,7 @@ def run_migrations():
 
     conn = None
     try:
-        conn = sqlite3.connect(DB_PATH)
+        conn = sqlite3.connect(db_path_to_migrate)
         # Check and update 'trades' table
         # First, ensure the table exists (it should be created by TradingStats._create_tables if new)
         cursor = conn.cursor()
@@ -130,5 +130,6 @@ if __name__ == '__main__':
     # Configure logging for direct script execution (optional)
     logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
     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.") 

+ 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
         # and tables are potentially created for the first time.
         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.")
         
         self.conn = sqlite3.connect(self.db_path, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
@@ -1656,3 +1656,8 @@ class TradingStats:
             return False
 
     # --- 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