浏览代码

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

+ 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 json
 import logging
 import logging
 import time
 import time
+import asyncio
+import aiofiles
 from datetime import datetime
 from datetime import datetime
 from pathlib import Path
 from pathlib import Path
 from typing import Dict, Set, Optional, Any
 from typing import Dict, Set, Optional, Any
@@ -39,17 +41,32 @@ class CopyTradingStateManager:
         self.state_file = Path(state_file)
         self.state_file = Path(state_file)
         self.state = CopyTradingState()
         self.state = CopyTradingState()
         self._lock = Lock()
         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:
         try:
             if self.state_file.exists():
             if self.state_file.exists():
                 with open(self.state_file, 'r') as f:
                 with open(self.state_file, 'r') as f:
@@ -78,8 +95,34 @@ class CopyTradingStateManager:
             logger.info("Starting with fresh state")
             logger.info("Starting with fresh state")
             self.state = CopyTradingState()
             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:
     def save_state(self) -> None:
-        """Save current state to file"""
+        """Save current state to file (synchronous version)"""
         try:
         try:
             with self._lock:
             with self._lock:
                 # Convert state to dict
                 # Convert state to dict
@@ -102,8 +145,34 @@ class CopyTradingStateManager:
         except Exception as e:
         except Exception as e:
             logger.error(f"Error saving copy trading state: {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:
     def start_copy_trading(self, target_address: str) -> None:
         """Start copy trading for a target address"""
         """Start copy trading for a target address"""
+        # Ensure state is loaded first
+        self._ensure_initialized()
+        
         with self._lock:
         with self._lock:
             current_time = int(time.time() * 1000)
             current_time = int(time.time() * 1000)
             
             
@@ -140,6 +209,7 @@ class CopyTradingStateManager:
     
     
     def should_copy_position(self, coin: str, position_data: Dict) -> bool:
     def should_copy_position(self, coin: str, position_data: Dict) -> bool:
         """Determine if we should copy a position"""
         """Determine if we should copy a position"""
+        self._ensure_initialized()
         with self._lock:
         with self._lock:
             # If we haven't seen this position before, it's new
             # If we haven't seen this position before, it's new
             if coin not in self.state.tracked_positions:
             if coin not in self.state.tracked_positions:
@@ -200,6 +270,7 @@ class CopyTradingStateManager:
     
     
     def has_copied_trade(self, trade_id: str) -> bool:
     def has_copied_trade(self, trade_id: str) -> bool:
         """Check if we've already copied a trade"""
         """Check if we've already copied a trade"""
+        self._ensure_initialized()
         with self._lock:
         with self._lock:
             return trade_id in self.state.copied_trades
             return trade_id in self.state.copied_trades
     
     
@@ -256,14 +327,19 @@ class CopyTradingStateManager:
             logger.info("🔄 Copy trading state reset")
             logger.info("🔄 Copy trading state reset")
     
     
     def get_tracked_positions(self) -> Dict[str, Dict]:
     def get_tracked_positions(self) -> Dict[str, Dict]:
-        """Get current tracked positions"""
+        """Get currently tracked positions"""
+        self._ensure_initialized()
         with self._lock:
         with self._lock:
             return self.state.tracked_positions.copy()
             return self.state.tracked_positions.copy()
     
     
     def is_enabled(self) -> bool:
     def is_enabled(self) -> bool:
         """Check if copy trading is enabled"""
         """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]:
     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
         # Initialize copy trading monitor if available
         if COPY_TRADING_AVAILABLE:
         if COPY_TRADING_AVAILABLE:
             try:
             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:
             except Exception as e:
                 logger.error(f"❌ Failed to initialize copy trading monitor: {e}")
                 logger.error(f"❌ Failed to initialize copy trading monitor: {e}")
                 self.copy_trading_monitor = None
                 self.copy_trading_monitor = None

+ 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.318"
+BOT_VERSION = "3.0.319"
 
 
 # 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"))