|
@@ -168,6 +168,54 @@ 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 start_copy_trading_async(self, target_address: str) -> None:
|
|
|
|
+ """Start copy trading for a target address (async version)"""
|
|
|
|
+ # Ensure state is loaded first (using async version)
|
|
|
|
+ await self._ensure_initialized_async()
|
|
|
|
+
|
|
|
|
+ current_time = int(time.time() * 1000)
|
|
|
|
+
|
|
|
|
+ # If starting fresh or changing target, reset state
|
|
|
|
+ if (not self.state.enabled or
|
|
|
|
+ self.state.target_address != target_address or
|
|
|
|
+ self.state.start_timestamp is None):
|
|
|
|
+
|
|
|
|
+ logger.info(f"🚀 Starting fresh copy trading session for {target_address}")
|
|
|
|
+ self.state = CopyTradingState(
|
|
|
|
+ enabled=True,
|
|
|
|
+ target_address=target_address,
|
|
|
|
+ start_timestamp=current_time,
|
|
|
|
+ session_start_time=current_time,
|
|
|
|
+ tracked_positions={},
|
|
|
|
+ copied_trades=set()
|
|
|
|
+ )
|
|
|
|
+ else:
|
|
|
|
+ # Resuming existing session
|
|
|
|
+ logger.info(f"▶️ Resuming copy trading session for {target_address}")
|
|
|
|
+ self.state.enabled = True
|
|
|
|
+ self.state.session_start_time = current_time
|
|
|
|
+
|
|
|
|
+ self.state.last_check_timestamp = current_time
|
|
|
|
+ await self.save_state_async()
|
|
|
|
+
|
|
|
|
+ async def _ensure_initialized_async(self) -> None:
|
|
|
|
+ """Ensure state is loaded (async version)"""
|
|
|
|
+ if self._initialized:
|
|
|
|
+ return
|
|
|
|
+
|
|
|
|
+ try:
|
|
|
|
+ # Ensure data directory exists
|
|
|
|
+ self.state_file.parent.mkdir(exist_ok=True)
|
|
|
|
+
|
|
|
|
+ # Load existing state using async method
|
|
|
|
+ await self.load_state()
|
|
|
|
+ self._initialized = True
|
|
|
|
+
|
|
|
|
+ except Exception as e:
|
|
|
|
+ logger.error(f"Error during async state initialization: {e}")
|
|
|
|
+ self.state = CopyTradingState()
|
|
|
|
+ self._initialized = True
|
|
|
|
+
|
|
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
|
|
# Ensure state is loaded first
|
|
@@ -207,6 +255,13 @@ class CopyTradingStateManager:
|
|
self.save_state()
|
|
self.save_state()
|
|
logger.info("⏹️ Copy trading stopped (state preserved)")
|
|
logger.info("⏹️ Copy trading stopped (state preserved)")
|
|
|
|
|
|
|
|
+ async def stop_copy_trading_async(self) -> None:
|
|
|
|
+ """Stop copy trading but preserve state (async version)"""
|
|
|
|
+ self.state.enabled = False
|
|
|
|
+ self.state.last_check_timestamp = int(time.time() * 1000)
|
|
|
|
+ await self.save_state_async()
|
|
|
|
+ logger.info("⏹️ Copy trading stopped (state preserved)")
|
|
|
|
+
|
|
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()
|
|
self._ensure_initialized()
|
|
@@ -249,6 +304,17 @@ class CopyTradingStateManager:
|
|
}
|
|
}
|
|
self.save_state()
|
|
self.save_state()
|
|
|
|
|
|
|
|
+ async def update_tracked_position_async(self, coin: str, position_data: Dict) -> None:
|
|
|
|
+ """Update our tracking of a position (async version)"""
|
|
|
|
+ self.state.tracked_positions[coin] = {
|
|
|
|
+ 'size': position_data.get('size', 0),
|
|
|
|
+ 'side': position_data.get('side'),
|
|
|
|
+ 'entry_price': position_data.get('entry_price', 0),
|
|
|
|
+ 'leverage': position_data.get('leverage', 1),
|
|
|
|
+ 'last_updated': int(time.time() * 1000)
|
|
|
|
+ }
|
|
|
|
+ await self.save_state_async()
|
|
|
|
+
|
|
def remove_tracked_position(self, coin: str) -> None:
|
|
def remove_tracked_position(self, coin: str) -> None:
|
|
"""Remove a position from tracking (when closed)"""
|
|
"""Remove a position from tracking (when closed)"""
|
|
with self._lock:
|
|
with self._lock:
|
|
@@ -257,6 +323,13 @@ class CopyTradingStateManager:
|
|
self.save_state()
|
|
self.save_state()
|
|
logger.info(f"🗑️ Removed {coin} from tracked positions")
|
|
logger.info(f"🗑️ Removed {coin} from tracked positions")
|
|
|
|
|
|
|
|
+ async def remove_tracked_position_async(self, coin: str) -> None:
|
|
|
|
+ """Remove a position from tracking (when closed) (async version)"""
|
|
|
|
+ if coin in self.state.tracked_positions:
|
|
|
|
+ del self.state.tracked_positions[coin]
|
|
|
|
+ await self.save_state_async()
|
|
|
|
+ logger.info(f"🗑️ Removed {coin} from tracked positions")
|
|
|
|
+
|
|
def add_copied_trade(self, trade_id: str) -> None:
|
|
def add_copied_trade(self, trade_id: str) -> None:
|
|
"""Mark a trade as copied"""
|
|
"""Mark a trade as copied"""
|
|
with self._lock:
|
|
with self._lock:
|
|
@@ -268,6 +341,16 @@ class CopyTradingStateManager:
|
|
self.state.copied_trades.discard(trade_id)
|
|
self.state.copied_trades.discard(trade_id)
|
|
self.save_state()
|
|
self.save_state()
|
|
|
|
|
|
|
|
+ async def add_copied_trade_async(self, trade_id: str) -> None:
|
|
|
|
+ """Mark a trade as copied (async version)"""
|
|
|
|
+ self.state.copied_trades.add(trade_id)
|
|
|
|
+ # Keep only last 1000 trade IDs to prevent unbounded growth
|
|
|
|
+ if len(self.state.copied_trades) > 1000:
|
|
|
|
+ oldest_trades = sorted(self.state.copied_trades)[:500]
|
|
|
|
+ for trade_id in oldest_trades:
|
|
|
|
+ self.state.copied_trades.discard(trade_id)
|
|
|
|
+ await self.save_state_async()
|
|
|
|
+
|
|
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()
|
|
self._ensure_initialized()
|
|
@@ -280,6 +363,11 @@ class CopyTradingStateManager:
|
|
self.state.last_check_timestamp = int(time.time() * 1000)
|
|
self.state.last_check_timestamp = int(time.time() * 1000)
|
|
self.save_state()
|
|
self.save_state()
|
|
|
|
|
|
|
|
+ async def update_last_check_async(self) -> None:
|
|
|
|
+ """Update the last check timestamp (async version)"""
|
|
|
|
+ self.state.last_check_timestamp = int(time.time() * 1000)
|
|
|
|
+ await self.save_state_async()
|
|
|
|
+
|
|
def initialize_tracked_positions(self, current_positions: Dict) -> None:
|
|
def initialize_tracked_positions(self, current_positions: Dict) -> None:
|
|
"""Initialize tracking with current target positions (on first start)"""
|
|
"""Initialize tracking with current target positions (on first start)"""
|
|
with self._lock:
|
|
with self._lock:
|
|
@@ -297,6 +385,22 @@ class CopyTradingStateManager:
|
|
|
|
|
|
self.save_state()
|
|
self.save_state()
|
|
|
|
|
|
|
|
+ async def initialize_tracked_positions_async(self, current_positions: Dict) -> None:
|
|
|
|
+ """Initialize tracking with current target positions (on first start) (async version)"""
|
|
|
|
+ logger.info(f"🔄 Initializing position tracking with {len(current_positions)} existing positions")
|
|
|
|
+
|
|
|
|
+ for coin, position in current_positions.items():
|
|
|
|
+ self.state.tracked_positions[coin] = {
|
|
|
|
+ 'size': position.size,
|
|
|
|
+ 'side': position.side,
|
|
|
|
+ 'entry_price': position.entry_price,
|
|
|
|
+ 'leverage': position.leverage,
|
|
|
|
+ 'last_updated': int(time.time() * 1000)
|
|
|
|
+ }
|
|
|
|
+ logger.info(f" 📊 Tracking existing {position.side.upper()} {coin}: {position.size}")
|
|
|
|
+
|
|
|
|
+ await self.save_state_async()
|
|
|
|
+
|
|
def get_session_info(self) -> Dict[str, Any]:
|
|
def get_session_info(self) -> Dict[str, Any]:
|
|
"""Get information about current session"""
|
|
"""Get information about current session"""
|
|
with self._lock:
|
|
with self._lock:
|