فهرست منبع

Update bot version and modify copy trading functionality

- Incremented BOT_VERSION to 3.0.319.
- Added aiofiles to requirements for asynchronous file handling.
- Temporarily disabled copy trading commands in TelegramTradingBot to prevent blocking I/O.
- Updated CopyTradingStateManager to support asynchronous state loading and saving, improving performance and responsiveness.
- Disabled copy trading monitor initialization in MonitoringCoordinator due to ongoing blocking issues.
Carles Sentis 5 روز پیش
والد
کامیت
e3ad624e50
5فایلهای تغییر یافته به همراه102 افزوده شده و 28 حذف شده
  1. 1 0
      requirements.txt
  2. 5 11
      src/bot/core.py
  3. 90 14
      src/monitoring/copy_trading_state.py
  4. 5 2
      src/monitoring/monitoring_coordinator.py
  5. 1 1
      trading_bot.py

+ 1 - 0
requirements.txt

@@ -5,4 +5,5 @@ pandas
 psutil
 requests
 aiohttp
+aiofiles
 hyperliquid

+ 5 - 11
src/bot/core.py

@@ -31,7 +31,8 @@ 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
+# TEMPORARY: Disable copy trading commands to prevent blocking I/O
+# from src.commands.copy_trading_commands import CopyTradingCommands
 
 logger = logging.getLogger(__name__)
 
@@ -71,7 +72,8 @@ 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)
+        # TEMPORARY: Disable copy trading commands to prevent blocking I/O
+        # self.copy_trading_cmds = CopyTradingCommands(self.monitoring_coordinator)
 
         # Create a class to hold all info commands
         class InfoCommandsHandler:
@@ -152,11 +154,6 @@ 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
@@ -174,7 +171,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|COPY_STATUS|COPY_START|COPY_STOP)'),
+            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)'),
             self.handle_keyboard_command
         ))
         
@@ -209,9 +206,6 @@ 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)

+ 90 - 14
src/monitoring/copy_trading_state.py

@@ -5,6 +5,8 @@ Copy Trading State Management - Handles persistence and tracking of copy trading
 import json
 import logging
 import time
+import asyncio
+import aiofiles
 from datetime import datetime
 from pathlib import Path
 from typing import Dict, Set, Optional, Any
@@ -39,17 +41,32 @@ class CopyTradingStateManager:
         self.state_file = Path(state_file)
         self.state = CopyTradingState()
         self._lock = Lock()
+        self._initialized = False
         
-        # Ensure data directory exists
-        self.state_file.parent.mkdir(exist_ok=True)
-        
-        # Load existing state
-        self.load_state()
-        
-        logger.info(f"Copy trading state manager initialized - State file: {self.state_file}")
+        # Don't do file I/O during __init__ to prevent blocking
+        # File operations will be deferred until first use
+        logger.info(f"Copy trading state manager created - State file: {self.state_file}")
     
-    def load_state(self) -> None:
-        """Load state from file"""
+    def _ensure_initialized(self) -> None:
+        """Ensure state is loaded (non-blocking, called when needed)"""
+        if self._initialized:
+            return
+            
+        try:
+            # Ensure data directory exists
+            self.state_file.parent.mkdir(exist_ok=True)
+            
+            # Load existing state
+            self._load_state_sync()
+            self._initialized = True
+            
+        except Exception as e:
+            logger.error(f"Error during state initialization: {e}")
+            self.state = CopyTradingState()
+            self._initialized = True
+    
+    def _load_state_sync(self) -> None:
+        """Load state from file (synchronous version for internal use)"""
         try:
             if self.state_file.exists():
                 with open(self.state_file, 'r') as f:
@@ -78,8 +95,34 @@ class CopyTradingStateManager:
             logger.info("Starting with fresh state")
             self.state = CopyTradingState()
     
+    async def load_state(self) -> None:
+        """Load state from file (async version)"""
+        try:
+            if self.state_file.exists():
+                async with aiofiles.open(self.state_file, 'r') as f:
+                    content = await f.read()
+                    data = json.loads(content)
+                
+                # Convert copied_trades back to set
+                if 'copied_trades' in data and isinstance(data['copied_trades'], list):
+                    data['copied_trades'] = set(data['copied_trades'])
+                
+                # Update state with loaded data
+                for key, value in data.items():
+                    if hasattr(self.state, key):
+                        setattr(self.state, key, value)
+                
+                logger.info(f"✅ Loaded copy trading state from {self.state_file}")
+                
+            else:
+                logger.info(f"No existing state file found, starting fresh")
+                
+        except Exception as e:
+            logger.error(f"Error loading copy trading state: {e}")
+            self.state = CopyTradingState()
+    
     def save_state(self) -> None:
-        """Save current state to file"""
+        """Save current state to file (synchronous version)"""
         try:
             with self._lock:
                 # Convert state to dict
@@ -102,8 +145,34 @@ class CopyTradingStateManager:
         except Exception as e:
             logger.error(f"Error saving copy trading state: {e}")
     
+    async def save_state_async(self) -> None:
+        """Save current state to file (async version)"""
+        try:
+            # Convert state to dict
+            state_dict = asdict(self.state)
+            
+            # Convert set to list for JSON serialization
+            if 'copied_trades' in state_dict:
+                state_dict['copied_trades'] = list(state_dict['copied_trades'])
+            
+            # Write to temporary file first, then rename (atomic operation)
+            temp_file = self.state_file.with_suffix('.tmp')
+            async with aiofiles.open(temp_file, 'w') as f:
+                await f.write(json.dumps(state_dict, indent=2))
+            
+            # Atomic rename (still sync, but very fast)
+            temp_file.rename(self.state_file)
+            
+            logger.debug(f"💾 Saved copy trading state to {self.state_file}")
+            
+        except Exception as e:
+            logger.error(f"Error saving copy trading state: {e}")
+    
     def start_copy_trading(self, target_address: str) -> None:
         """Start copy trading for a target address"""
+        # Ensure state is loaded first
+        self._ensure_initialized()
+        
         with self._lock:
             current_time = int(time.time() * 1000)
             
@@ -140,6 +209,7 @@ class CopyTradingStateManager:
     
     def should_copy_position(self, coin: str, position_data: Dict) -> bool:
         """Determine if we should copy a position"""
+        self._ensure_initialized()
         with self._lock:
             # If we haven't seen this position before, it's new
             if coin not in self.state.tracked_positions:
@@ -200,6 +270,7 @@ class CopyTradingStateManager:
     
     def has_copied_trade(self, trade_id: str) -> bool:
         """Check if we've already copied a trade"""
+        self._ensure_initialized()
         with self._lock:
             return trade_id in self.state.copied_trades
     
@@ -256,14 +327,19 @@ class CopyTradingStateManager:
             logger.info("🔄 Copy trading state reset")
     
     def get_tracked_positions(self) -> Dict[str, Dict]:
-        """Get current tracked positions"""
+        """Get currently tracked positions"""
+        self._ensure_initialized()
         with self._lock:
             return self.state.tracked_positions.copy()
     
     def is_enabled(self) -> bool:
         """Check if copy trading is enabled"""
-        return self.state.enabled
+        self._ensure_initialized()
+        with self._lock:
+            return self.state.enabled
     
     def get_target_address(self) -> Optional[str]:
-        """Get current target address"""
-        return self.state.target_address 
+        """Get the target address"""
+        self._ensure_initialized()
+        with self._lock:
+            return self.state.target_address 

+ 5 - 2
src/monitoring/monitoring_coordinator.py

@@ -43,8 +43,11 @@ class MonitoringCoordinator:
         # 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 (non-blocking version)")
+                # DISABLE: Still causing blocking issues despite timeouts
+                # self.copy_trading_monitor = CopyTradingMonitor(hl_client, notification_manager)
+                # logger.info("✅ Copy trading monitor initialized (non-blocking version)")
+                self.copy_trading_monitor = None
+                logger.info("🚫 Copy trading monitor disabled - still causing blocking issues")
             except Exception as e:
                 logger.error(f"❌ Failed to initialize copy trading monitor: {e}")
                 self.copy_trading_monitor = None

+ 1 - 1
trading_bot.py

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