Przeglądaj źródła

Integrate copy trading functionality into the TelegramTradingBot and monitoring system

- Added CopyTradingCommands to the TelegramTradingBot, enabling commands for copy trading status, start, and stop.
- Updated the trading command handler to include copy trading callbacks.
- Refactored CopyTradingMonitor to use aiohttp for asynchronous HTTP requests, improving performance and responsiveness.
- Enhanced MonitoringCoordinator to conditionally initialize and manage the copy trading monitor based on availability and user settings.
Carles Sentis 5 dni temu
rodzic
commit
ca5e69d5d7

+ 21 - 1
src/bot/core.py

@@ -31,6 +31,7 @@ from src.commands.info.risk import RiskCommands
 from src.commands.info.price import PriceCommands
 from src.commands.info.balance_adjustments import BalanceAdjustmentsCommands
 from src.commands.info.commands import CommandsInfo
+from src.commands.copy_trading_commands import CopyTradingCommands
 
 logger = logging.getLogger(__name__)
 
@@ -70,6 +71,7 @@ class TelegramTradingBot:
         self.price_cmds = PriceCommands(self.trading_engine, self.notification_manager)
         self.balance_adjustments_cmds = BalanceAdjustmentsCommands(self.trading_engine, self.notification_manager)
         self.commands_cmds = CommandsInfo(self.trading_engine, self.notification_manager)
+        self.copy_trading_cmds = CopyTradingCommands(self.monitoring_coordinator)
 
         # Create a class to hold all info commands
         class InfoCommandsHandler:
@@ -150,6 +152,11 @@ class TelegramTradingBot:
         self.application.add_handler(CommandHandler("risk", self.risk_cmds.risk_command))
         self.application.add_handler(CommandHandler("balance_adjustments", self.balance_adjustments_cmds.balance_adjustments_command))
         self.application.add_handler(CommandHandler("commands", self.commands_cmds.commands_command))
+        
+        # Copy trading commands
+        self.application.add_handler(CommandHandler("copy_status", self.copy_trading_cmds.copy_status_command))
+        self.application.add_handler(CommandHandler("copy_start", self.copy_trading_cmds.copy_start_command))
+        self.application.add_handler(CommandHandler("copy_stop", self.copy_trading_cmds.copy_stop_command))
         self.application.add_handler(CommandHandler("c", self.commands_cmds.commands_command))  # Alias
         
         # Management commands
@@ -167,7 +174,7 @@ class TelegramTradingBot:
         # Callback and message handlers
         self.application.add_handler(CallbackQueryHandler(self.trading_commands.button_callback))
         self.application.add_handler(MessageHandler(
-            filters.Regex(r'^(LONG|SHORT|EXIT|SL|TP|LEVERAGE|BALANCE|POSITIONS|ORDERS|STATS|MARKET|PERFORMANCE|DAILY|WEEKLY|MONTHLY|RISK|ALARM|MONITORING|LOGS|DEBUG|VERSION|COMMANDS|KEYBOARD|COO)'),
+            filters.Regex(r'^(LONG|SHORT|EXIT|SL|TP|LEVERAGE|BALANCE|POSITIONS|ORDERS|STATS|MARKET|PERFORMANCE|DAILY|WEEKLY|MONTHLY|RISK|ALARM|MONITORING|LOGS|DEBUG|VERSION|COMMANDS|KEYBOARD|COO|COPY_STATUS|COPY_START|COPY_STOP)'),
             self.handle_keyboard_command
         ))
         
@@ -202,6 +209,9 @@ class TelegramTradingBot:
             "DEBUG": self.management_commands.debug_command,
             "VERSION": self.management_commands.version_command,
             "KEYBOARD": self.management_commands.keyboard_command,
+            "COPY_STATUS": self.copy_trading_cmds.copy_status_command,
+            "COPY_START": self.copy_trading_cmds.copy_start_command,
+            "COPY_STOP": self.copy_trading_cmds.copy_stop_command,
         }
 
         command_func = command_map.get(command_text)
@@ -287,6 +297,11 @@ class TelegramTradingBot:
 • /alarm {Config.DEFAULT_TRADING_TOKEN} - Show all {Config.DEFAULT_TRADING_TOKEN} alarms
 • /alarm 3 - Remove alarm ID 3
 
+<b>🔄 Copy Trading:</b>
+• /copy_status - View copy trading status & session info
+• /copy_start [address] - Start copying a target trader
+• /copy_stop - Stop copy trading (preserves session)
+
 <b>🔄 Automatic Monitoring:</b>
 • Real-time order fill alerts
 • Position opened/closed notifications  
@@ -382,6 +397,11 @@ For support, contact your bot administrator.
 • /debug - Bot internal state (troubleshooting)
 • /sync force - Clear local data and resync with exchange
 
+🔄 <b>Copy Trading:</b>
+• /copy_status - View copy trading status & configuration
+• /copy_start [address] - Start copying a target trader's positions
+• /copy_stop - Stop copy trading (session data preserved)
+
 For support or issues, check the logs or contact the administrator.
         """
         

+ 334 - 0
src/commands/copy_trading_commands.py

@@ -0,0 +1,334 @@
+#!/usr/bin/env python3
+"""
+Copy Trading Commands - Handles copy trading related Telegram commands.
+"""
+
+import logging
+from typing import Optional
+from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
+from telegram.ext import ContextTypes
+
+from ..config.config import Config
+from ..monitoring.monitoring_coordinator import MonitoringCoordinator
+
+logger = logging.getLogger(__name__)
+
+class CopyTradingCommands:
+    """Handles copy trading related Telegram commands."""
+    
+    def __init__(self, monitoring_coordinator: MonitoringCoordinator):
+        """Initialize copy trading commands."""
+        self.monitoring_coordinator = monitoring_coordinator
+    
+    def _is_authorized(self, chat_id: str) -> bool:
+        """Check if the chat ID is authorized."""
+        return str(chat_id) == str(Config.TELEGRAM_CHAT_ID)
+    
+    async def copy_status_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /copy_status 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.")
+            return
+        
+        try:
+            copy_monitor = self.monitoring_coordinator.copy_trading_monitor
+            
+            if not copy_monitor:
+                await context.bot.send_message(
+                    chat_id=chat_id, 
+                    text="❌ Copy trading monitor not available.",
+                    parse_mode='HTML'
+                )
+                return
+            
+            status = copy_monitor.get_status()
+            
+            # Format status message
+            status_emoji = "🟢" if status['enabled'] else "🔴"
+            status_text = "ACTIVE" if status['enabled'] else "STOPPED"
+            
+            message = f"""
+🔄 <b>Copy Trading Status: {status_emoji} {status_text}</b>
+
+📊 <b>Configuration:</b>
+• Target Address: <code>{status['target_address'][:10] + '...' if status['target_address'] else 'Not Set'}</code>
+• Portfolio Allocation: <b>{status['portfolio_percentage']:.1%}</b>
+• Copy Mode: <b>{status['copy_mode']}</b>
+• Max Leverage: <b>{status['max_leverage']}x</b>
+
+📈 <b>Current State:</b>
+• Target Positions: <b>{status['target_positions']}</b>
+• Our Positions: <b>{status['our_positions']}</b>
+• Tracked Positions: <b>{status['tracked_positions']}</b>
+• Copied Trades: <b>{status['copied_trades']}</b>
+
+⏰ <b>Session Info:</b>
+• Start Time: {status['session_start_time'].strftime('%Y-%m-%d %H:%M:%S') if status['session_start_time'] else 'N/A'}
+• Duration: {f"{status['session_duration_hours']:.1f} hours" if status['session_duration_hours'] else 'N/A'}
+• Last Check: {status['last_check'].strftime('%H:%M:%S') if status['last_check'] else 'N/A'}
+            """
+            
+            # Add control buttons
+            keyboard = []
+            if status['enabled']:
+                keyboard.append([InlineKeyboardButton("🛑 Stop Copy Trading", callback_data="copy_stop")])
+            else:
+                keyboard.append([InlineKeyboardButton("🚀 Start Copy Trading", callback_data="copy_start_prompt")])
+            
+            keyboard.append([InlineKeyboardButton("🔄 Refresh Status", callback_data="copy_status")])
+            
+            reply_markup = InlineKeyboardMarkup(keyboard)
+            
+            await context.bot.send_message(
+                chat_id=chat_id, 
+                text=message,
+                parse_mode='HTML',
+                reply_markup=reply_markup
+            )
+            
+        except Exception as e:
+            logger.error(f"Error in copy_status command: {e}")
+            await context.bot.send_message(
+                chat_id=chat_id, 
+                text=f"❌ Error retrieving copy trading status: {e}"
+            )
+    
+    async def copy_start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /copy_start 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.")
+            return
+        
+        try:
+            copy_monitor = self.monitoring_coordinator.copy_trading_monitor
+            
+            if not copy_monitor:
+                await context.bot.send_message(
+                    chat_id=chat_id, 
+                    text="❌ Copy trading monitor not available."
+                )
+                return
+            
+            # Check if already running
+            if copy_monitor.enabled and copy_monitor.state_manager.is_enabled():
+                await context.bot.send_message(
+                    chat_id=chat_id, 
+                    text="⚠️ Copy trading is already running. Use /copy_stop to stop it first."
+                )
+                return
+            
+            # Check if target address is provided
+            if context.args and len(context.args) > 0:
+                target_address = context.args[0]
+                
+                # Validate Ethereum address format
+                if not target_address.startswith('0x') or len(target_address) != 42:
+                    await context.bot.send_message(
+                        chat_id=chat_id, 
+                        text="❌ Invalid Ethereum address format. Address must start with '0x' and be 42 characters long."
+                    )
+                    return
+                
+                # Update target address
+                copy_monitor.target_address = target_address
+                copy_monitor.config.COPY_TRADING_TARGET_ADDRESS = target_address
+            
+            # Check if target address is set
+            if not copy_monitor.target_address:
+                await context.bot.send_message(
+                    chat_id=chat_id, 
+                    text=(
+                        "❌ No target address configured.\n\n"
+                        "Usage: /copy_start [target_address]\n"
+                        "Example: /copy_start 0x1234567890abcdef1234567890abcdef12345678"
+                    )
+                )
+                return
+            
+            # Create confirmation message
+            message = f"""
+🚀 <b>Start Copy Trading Confirmation</b>
+
+🎯 <b>Target Trader:</b> <code>{copy_monitor.target_address}</code>
+💰 <b>Portfolio Allocation:</b> {copy_monitor.portfolio_percentage:.1%}
+📊 <b>Copy Mode:</b> {copy_monitor.copy_mode}
+⚡ <b>Max Leverage:</b> {copy_monitor.max_leverage}x
+💵 <b>Min Position Size:</b> ${copy_monitor.min_position_size}
+⏱️ <b>Execution Delay:</b> {copy_monitor.execution_delay}s
+
+⚠️ <b>Are you sure you want to start copy trading?</b>
+
+This will:
+• Monitor the target trader's positions
+• Automatically copy NEW trades (existing positions ignored)
+• Allocate {copy_monitor.portfolio_percentage:.1%} of your portfolio per trade
+• Send notifications for each copied trade
+            """
+            
+            keyboard = [
+                [
+                    InlineKeyboardButton("✅ Start Copy Trading", callback_data=f"copy_start_confirm_{copy_monitor.target_address}"),
+                    InlineKeyboardButton("❌ Cancel", callback_data="copy_cancel")
+                ]
+            ]
+            reply_markup = InlineKeyboardMarkup(keyboard)
+            
+            await context.bot.send_message(
+                chat_id=chat_id, 
+                text=message,
+                parse_mode='HTML',
+                reply_markup=reply_markup
+            )
+            
+        except Exception as e:
+            logger.error(f"Error in copy_start command: {e}")
+            await context.bot.send_message(
+                chat_id=chat_id, 
+                text=f"❌ Error starting copy trading: {e}"
+            )
+    
+    async def copy_stop_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /copy_stop 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.")
+            return
+        
+        try:
+            copy_monitor = self.monitoring_coordinator.copy_trading_monitor
+            
+            if not copy_monitor:
+                await context.bot.send_message(
+                    chat_id=chat_id, 
+                    text="❌ Copy trading monitor not available."
+                )
+                return
+            
+            # Check if already stopped
+            if not copy_monitor.enabled or not copy_monitor.state_manager.is_enabled():
+                await context.bot.send_message(
+                    chat_id=chat_id, 
+                    text="⚠️ Copy trading is already stopped."
+                )
+                return
+            
+            # Get session info before stopping
+            session_info = copy_monitor.state_manager.get_session_info()
+            
+            # Create confirmation message
+            message = f"""
+🛑 <b>Stop Copy Trading Confirmation</b>
+
+🎯 <b>Current Target:</b> <code>{copy_monitor.target_address[:10]}...</code>
+📊 <b>Session Stats:</b>
+• Tracked Positions: {session_info['tracked_positions_count']}
+• Copied Trades: {session_info['copied_trades_count']}
+• Duration: {f"{session_info['session_duration_seconds'] / 3600:.1f} hours" if session_info['session_duration_seconds'] else 'N/A'}
+
+⚠️ <b>Are you sure you want to stop copy trading?</b>
+
+This will:
+• Stop monitoring the target trader
+• Preserve session data for later resumption
+• Keep existing positions (won't auto-close)
+• You can restart later with /copy_start
+            """
+            
+            keyboard = [
+                [
+                    InlineKeyboardButton("✅ Stop Copy Trading", callback_data="copy_stop_confirm"),
+                    InlineKeyboardButton("❌ Keep Running", callback_data="copy_cancel")
+                ]
+            ]
+            reply_markup = InlineKeyboardMarkup(keyboard)
+            
+            await context.bot.send_message(
+                chat_id=chat_id, 
+                text=message,
+                parse_mode='HTML',
+                reply_markup=reply_markup
+            )
+            
+        except Exception as e:
+            logger.error(f"Error in copy_stop command: {e}")
+            await context.bot.send_message(
+                chat_id=chat_id, 
+                text=f"❌ Error stopping copy trading: {e}"
+            )
+    
+    async def button_callback(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle button callbacks for copy trading commands."""
+        query = update.callback_query
+        await query.answer()
+        
+        callback_data = query.data
+        
+        try:
+            copy_monitor = self.monitoring_coordinator.copy_trading_monitor
+            
+            if callback_data == "copy_status":
+                # Refresh status
+                await self.copy_status_command(update, context)
+                
+            elif callback_data == "copy_start_prompt":
+                await query.edit_message_text(
+                    text=(
+                        "🚀 <b>Start Copy Trading</b>\n\n"
+                        "Please provide the target trader's address:\n\n"
+                        "Usage: /copy_start [target_address]\n"
+                        "Example: /copy_start 0x1234567890abcdef1234567890abcdef12345678"
+                    ),
+                    parse_mode='HTML'
+                )
+                
+            elif callback_data.startswith("copy_start_confirm_"):
+                target_address = callback_data.replace("copy_start_confirm_", "")
+                
+                await query.edit_message_text("🚀 Starting copy trading...")
+                
+                if copy_monitor:
+                    # Enable copy trading
+                    copy_monitor.enabled = True
+                    copy_monitor.target_address = target_address
+                    
+                    # Start monitoring in background
+                    import asyncio
+                    asyncio.create_task(copy_monitor.start_monitoring())
+                    
+                    await query.edit_message_text(
+                        f"✅ Copy trading started!\n\n"
+                        f"🎯 Target: {target_address[:10]}...\n"
+                        f"📊 Monitoring active positions and new trades\n\n"
+                        f"Use /copy_status to check progress.",
+                        parse_mode='HTML'
+                    )
+                else:
+                    await query.edit_message_text("❌ Copy trading monitor not available.")
+                    
+            elif callback_data == "copy_stop_confirm":
+                await query.edit_message_text("🛑 Stopping copy trading...")
+                
+                if copy_monitor:
+                    await copy_monitor.stop_monitoring()
+                    
+                    await query.edit_message_text(
+                        "✅ Copy trading stopped!\n\n"
+                        "📊 Session data preserved\n"
+                        "🔄 Use /copy_start to resume later",
+                        parse_mode='HTML'
+                    )
+                else:
+                    await query.edit_message_text("❌ Copy trading monitor not available.")
+                    
+            elif callback_data == "copy_stop":
+                # Show stop confirmation
+                await self.copy_stop_command(update, context)
+                
+            elif callback_data == "copy_cancel":
+                await query.edit_message_text("❌ Copy trading action cancelled.")
+                
+        except Exception as e:
+            logger.error(f"Error in copy trading button callback: {e}")
+            await query.edit_message_text(f"❌ Error: {e}") 

+ 7 - 0
src/commands/trading_commands.py

@@ -717,6 +717,13 @@ This action cannot be undone.
             await query.edit_message_text("❌ Order cancelled.")
         elif callback_data.startswith("leverage_confirm"):
             await self._execute_leverage_callback(query, callback_data)
+        elif callback_data.startswith("copy_"):
+            # Handle copy trading callbacks - delegate to copy trading commands
+            if hasattr(self, 'info_commands_handler') and hasattr(self.info_commands_handler, 'bot'):
+                copy_trading_cmds = self.info_commands_handler.bot.copy_trading_cmds
+                await copy_trading_cmds.button_callback(update, context)
+            else:
+                await query.edit_message_text("❌ Copy trading commands not available.")
     
     async def _execute_long_callback(self, query, callback_data):
         """Execute long order from callback."""

+ 38 - 38
src/monitoring/copy_trading_monitor.py

@@ -8,7 +8,7 @@ import asyncio
 from datetime import datetime, timedelta
 from typing import Dict, List, Optional, Any
 from dataclasses import dataclass
-import requests
+import aiohttp
 import json
 from decimal import Decimal, ROUND_DOWN
 
@@ -190,38 +190,38 @@ class CopyTradingMonitor:
                 "user": self.target_address
             }
             
-            response = requests.post(self.info_url, json=payload)
-            
-            if response.status_code != 200:
-                self.logger.error(f"Failed to get target positions: {response.status_code}")
-                return None
-                
-            data = response.json()
-            positions = {}
-            
-            # Parse asset positions
-            for asset_pos in data.get('assetPositions', []):
-                if asset_pos.get('type') == 'oneWay':
-                    pos = asset_pos['position']
-                    coin = pos['coin']
-                    size = float(pos['szi'])
-                    
-                    if abs(size) < 0.001:  # Skip dust positions
-                        continue
+            async with aiohttp.ClientSession() as session:
+                async with session.post(self.info_url, json=payload) as response:
+                    if response.status != 200:
+                        self.logger.error(f"Failed to get target positions: {response.status}")
+                        return None
                         
-                    side = 'long' if size > 0 else 'short'
+                    data = await response.json()
+                    positions = {}
                     
-                    positions[coin] = TraderPosition(
-                        coin=coin,
-                        size=abs(size),
-                        side=side,
-                        entry_price=float(pos['entryPx']),
-                        leverage=float(pos['leverage']['value']),
-                        position_value=float(pos['positionValue']),
-                        unrealized_pnl=float(pos['unrealizedPnl']),
-                        margin_used=float(pos['marginUsed']),
-                        timestamp=int(time.time() * 1000)
-                    )
+                    # Parse asset positions
+                    for asset_pos in data.get('assetPositions', []):
+                        if asset_pos.get('type') == 'oneWay':
+                            pos = asset_pos['position']
+                            coin = pos['coin']
+                            size = float(pos['szi'])
+                            
+                            if abs(size) < 0.001:  # Skip dust positions
+                                continue
+                                
+                            side = 'long' if size > 0 else 'short'
+                            
+                            positions[coin] = TraderPosition(
+                                coin=coin,
+                                size=abs(size),
+                                side=side,
+                                entry_price=float(pos['entryPx']),
+                                leverage=float(pos['leverage']['value']),
+                                position_value=float(pos['positionValue']),
+                                unrealized_pnl=float(pos['unrealizedPnl']),
+                                margin_used=float(pos['marginUsed']),
+                                timestamp=int(time.time() * 1000)
+                            )
             
             return positions
             
@@ -439,13 +439,13 @@ class CopyTradingMonitor:
                 "user": self.target_address
             }
             
-            response = requests.post(self.info_url, json=payload)
-            
-            if response.status_code == 200:
-                data = response.json()
-                return float(data.get('marginSummary', {}).get('accountValue', 0))
-            else:
-                return 0.0
+            async with aiohttp.ClientSession() as session:
+                async with session.post(self.info_url, json=payload) as response:
+                    if response.status == 200:
+                        data = await response.json()
+                        return float(data.get('marginSummary', {}).get('accountValue', 0))
+                    else:
+                        return 0.0
                 
         except Exception as e:
             self.logger.error(f"Error getting target account balance: {e}")

+ 33 - 9
src/monitoring/monitoring_coordinator.py

@@ -11,11 +11,17 @@ from .pending_orders_manager import PendingOrdersManager
 from .risk_manager import RiskManager
 from .alarm_manager import AlarmManager
 from .exchange_order_sync import ExchangeOrderSync
-# from .copy_trading_monitor import CopyTradingMonitor
 # DrawdownMonitor and RsiMonitor will be lazy-loaded to avoid circular imports
 
 logger = logging.getLogger(__name__)
 
+try:
+    from .copy_trading_monitor import CopyTradingMonitor
+    COPY_TRADING_AVAILABLE = True
+except ImportError as e:
+    logger.warning(f"Copy trading monitor not available: {e}")
+    COPY_TRADING_AVAILABLE = False
+
 class MonitoringCoordinator:
     """
     Simplified monitoring coordinator that manages all monitoring components.
@@ -33,7 +39,17 @@ class MonitoringCoordinator:
         self.pending_orders_manager = PendingOrdersManager(hl_client, notification_manager)
         self.risk_manager = RiskManager(hl_client, notification_manager, config)
         self.alarm_manager = AlarmManager()  # AlarmManager only needs alarms_file (defaults to data/price_alarms.json)
-        # self.copy_trading_monitor = CopyTradingMonitor(hl_client, notification_manager)
+        
+        # Initialize copy trading monitor if available
+        if COPY_TRADING_AVAILABLE:
+            try:
+                self.copy_trading_monitor = CopyTradingMonitor(hl_client, notification_manager)
+                logger.info("✅ Copy trading monitor initialized")
+            except Exception as e:
+                logger.error(f"❌ Failed to initialize copy trading monitor: {e}")
+                self.copy_trading_monitor = None
+        else:
+            self.copy_trading_monitor = None
         
         # Exchange order synchronization (will be initialized with trading stats)
         self.exchange_order_sync = None
@@ -60,9 +76,13 @@ class MonitoringCoordinator:
             await self.risk_manager.start()
             # AlarmManager doesn't have start() method - it's always ready
             
-            # # Start copy trading monitor if enabled
-            # if self.copy_trading_monitor.enabled:
-            #     asyncio.create_task(self.copy_trading_monitor.start_monitoring())
+            # Start copy trading monitor if enabled
+            if self.copy_trading_monitor and hasattr(self.copy_trading_monitor, 'enabled') and self.copy_trading_monitor.enabled:
+                try:
+                    asyncio.create_task(self.copy_trading_monitor.start_monitoring())
+                    logger.info("🔄 Copy trading monitor started")
+                except Exception as e:
+                    logger.error(f"❌ Failed to start copy trading monitor: {e}")
             
             # Initialize exchange order sync with trading stats
             self._init_exchange_order_sync()
@@ -100,9 +120,13 @@ class MonitoringCoordinator:
         await self.risk_manager.stop()
         # AlarmManager doesn't have stop() method - nothing to stop
         
-        # # Stop copy trading monitor
-        # if self.copy_trading_monitor:
-        #     await self.copy_trading_monitor.stop_monitoring()
+        # Stop copy trading monitor
+        if self.copy_trading_monitor and hasattr(self.copy_trading_monitor, 'stop_monitoring'):
+            try:
+                await self.copy_trading_monitor.stop_monitoring()
+                logger.info("🛑 Copy trading monitor stopped")
+            except Exception as e:
+                logger.error(f"❌ Error stopping copy trading monitor: {e}")
         
         # Stop optional monitors if they exist and have stop methods
         if self.drawdown_monitor and hasattr(self.drawdown_monitor, 'stop'):
@@ -228,7 +252,7 @@ class MonitoringCoordinator:
                     'pending_orders_manager': self.pending_orders_manager.is_running,
                     'risk_manager': self.risk_manager.is_running,
                     'alarm_manager': self.alarm_manager.is_running if hasattr(self.alarm_manager, 'is_running') else True,
-                    # 'copy_trading_monitor': self.copy_trading_monitor.enabled,
+                    'copy_trading_monitor': self.copy_trading_monitor.enabled if self.copy_trading_monitor else False,
                     'drawdown_monitor': self.drawdown_monitor.is_running if hasattr(self.drawdown_monitor, 'is_running') else True,
                     'rsi_monitor': self.rsi_monitor.is_running if hasattr(self.rsi_monitor, 'is_running') else True
                 },

+ 1 - 1
trading_bot.py

@@ -14,7 +14,7 @@ from datetime import datetime
 from pathlib import Path
 
 # Bot version
-BOT_VERSION = "3.0.315"
+BOT_VERSION = "3.0.316"
 
 # Add src directory to Python path
 sys.path.insert(0, str(Path(__file__).parent / "src"))