Browse Source

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 days ago
parent
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.price import PriceCommands
 from src.commands.info.balance_adjustments import BalanceAdjustmentsCommands
 from src.commands.info.balance_adjustments import BalanceAdjustmentsCommands
 from src.commands.info.commands import CommandsInfo
 from src.commands.info.commands import CommandsInfo
+from src.commands.copy_trading_commands import CopyTradingCommands
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -70,6 +71,7 @@ class TelegramTradingBot:
         self.price_cmds = PriceCommands(self.trading_engine, self.notification_manager)
         self.price_cmds = PriceCommands(self.trading_engine, self.notification_manager)
         self.balance_adjustments_cmds = BalanceAdjustmentsCommands(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.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
         # Create a class to hold all info commands
         class InfoCommandsHandler:
         class InfoCommandsHandler:
@@ -150,6 +152,11 @@ class TelegramTradingBot:
         self.application.add_handler(CommandHandler("risk", self.risk_cmds.risk_command))
         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("balance_adjustments", self.balance_adjustments_cmds.balance_adjustments_command))
         self.application.add_handler(CommandHandler("commands", self.commands_cmds.commands_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
         self.application.add_handler(CommandHandler("c", self.commands_cmds.commands_command))  # Alias
         
         
         # Management commands
         # Management commands
@@ -167,7 +174,7 @@ class TelegramTradingBot:
         # Callback and message handlers
         # Callback and message handlers
         self.application.add_handler(CallbackQueryHandler(self.trading_commands.button_callback))
         self.application.add_handler(CallbackQueryHandler(self.trading_commands.button_callback))
         self.application.add_handler(MessageHandler(
         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
             self.handle_keyboard_command
         ))
         ))
         
         
@@ -202,6 +209,9 @@ class TelegramTradingBot:
             "DEBUG": self.management_commands.debug_command,
             "DEBUG": self.management_commands.debug_command,
             "VERSION": self.management_commands.version_command,
             "VERSION": self.management_commands.version_command,
             "KEYBOARD": self.management_commands.keyboard_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)
         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 {Config.DEFAULT_TRADING_TOKEN} - Show all {Config.DEFAULT_TRADING_TOKEN} alarms
 • /alarm 3 - Remove alarm ID 3
 • /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>
 <b>🔄 Automatic Monitoring:</b>
 • Real-time order fill alerts
 • Real-time order fill alerts
 • Position opened/closed notifications  
 • Position opened/closed notifications  
@@ -382,6 +397,11 @@ For support, contact your bot administrator.
 • /debug - Bot internal state (troubleshooting)
 • /debug - Bot internal state (troubleshooting)
 • /sync force - Clear local data and resync with exchange
 • /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.
 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.")
             await query.edit_message_text("❌ Order cancelled.")
         elif callback_data.startswith("leverage_confirm"):
         elif callback_data.startswith("leverage_confirm"):
             await self._execute_leverage_callback(query, callback_data)
             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):
     async def _execute_long_callback(self, query, callback_data):
         """Execute long order from callback."""
         """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 datetime import datetime, timedelta
 from typing import Dict, List, Optional, Any
 from typing import Dict, List, Optional, Any
 from dataclasses import dataclass
 from dataclasses import dataclass
-import requests
+import aiohttp
 import json
 import json
 from decimal import Decimal, ROUND_DOWN
 from decimal import Decimal, ROUND_DOWN
 
 
@@ -190,38 +190,38 @@ class CopyTradingMonitor:
                 "user": self.target_address
                 "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
             return positions
             
             
@@ -439,13 +439,13 @@ class CopyTradingMonitor:
                 "user": self.target_address
                 "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:
         except Exception as e:
             self.logger.error(f"Error getting target account balance: {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 .risk_manager import RiskManager
 from .alarm_manager import AlarmManager
 from .alarm_manager import AlarmManager
 from .exchange_order_sync import ExchangeOrderSync
 from .exchange_order_sync import ExchangeOrderSync
-# from .copy_trading_monitor import CopyTradingMonitor
 # DrawdownMonitor and RsiMonitor will be lazy-loaded to avoid circular imports
 # DrawdownMonitor and RsiMonitor will be lazy-loaded to avoid circular imports
 
 
 logger = logging.getLogger(__name__)
 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:
 class MonitoringCoordinator:
     """
     """
     Simplified monitoring coordinator that manages all monitoring components.
     Simplified monitoring coordinator that manages all monitoring components.
@@ -33,7 +39,17 @@ class MonitoringCoordinator:
         self.pending_orders_manager = PendingOrdersManager(hl_client, notification_manager)
         self.pending_orders_manager = PendingOrdersManager(hl_client, notification_manager)
         self.risk_manager = RiskManager(hl_client, notification_manager, config)
         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.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)
         # Exchange order synchronization (will be initialized with trading stats)
         self.exchange_order_sync = None
         self.exchange_order_sync = None
@@ -60,9 +76,13 @@ class MonitoringCoordinator:
             await self.risk_manager.start()
             await self.risk_manager.start()
             # AlarmManager doesn't have start() method - it's always ready
             # 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
             # Initialize exchange order sync with trading stats
             self._init_exchange_order_sync()
             self._init_exchange_order_sync()
@@ -100,9 +120,13 @@ class MonitoringCoordinator:
         await self.risk_manager.stop()
         await self.risk_manager.stop()
         # AlarmManager doesn't have stop() method - nothing to 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
         # Stop optional monitors if they exist and have stop methods
         if self.drawdown_monitor and hasattr(self.drawdown_monitor, 'stop'):
         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,
                     'pending_orders_manager': self.pending_orders_manager.is_running,
                     'risk_manager': self.risk_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,
                     '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,
                     '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
                     '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
 from pathlib import Path
 
 
 # Bot version
 # Bot version
-BOT_VERSION = "3.0.315"
+BOT_VERSION = "3.0.316"
 
 
 # Add src directory to Python path
 # Add src directory to Python path
 sys.path.insert(0, str(Path(__file__).parent / "src"))
 sys.path.insert(0, str(Path(__file__).parent / "src"))