|
@@ -80,6 +80,15 @@ class CopyTradingMonitor:
|
|
self.info_url = "https://api.hyperliquid.xyz/info"
|
|
self.info_url = "https://api.hyperliquid.xyz/info"
|
|
|
|
|
|
self.logger.info(f"Copy Trading Monitor initialized - Target: {self.target_address}")
|
|
self.logger.info(f"Copy Trading Monitor initialized - Target: {self.target_address}")
|
|
|
|
+ self.logger.info(f"📊 Configuration:")
|
|
|
|
+ self.logger.info(f" - Enabled: {self.enabled}")
|
|
|
|
+ self.logger.info(f" - Target Address: {self.target_address}")
|
|
|
|
+ self.logger.info(f" - Portfolio %: {self.portfolio_percentage:.1%}")
|
|
|
|
+ self.logger.info(f" - Copy Mode: {self.copy_mode}")
|
|
|
|
+ self.logger.info(f" - Max Leverage: {self.max_leverage}x")
|
|
|
|
+ self.logger.info(f" - Min Position Size: ${self.min_position_size:.2f}")
|
|
|
|
+ self.logger.info(f" - Execution Delay: {self.execution_delay}s")
|
|
|
|
+ self.logger.info(f" - Notifications: {self.notifications_enabled}")
|
|
|
|
|
|
# Load previous session info if available
|
|
# Load previous session info if available
|
|
session_info = self.state_manager.get_session_info()
|
|
session_info = self.state_manager.get_session_info()
|
|
@@ -384,23 +393,42 @@ class CopyTradingMonitor:
|
|
self.logger.debug(f"Skipping already copied trade: {trade.original_trade_hash}")
|
|
self.logger.debug(f"Skipping already copied trade: {trade.original_trade_hash}")
|
|
return
|
|
return
|
|
|
|
|
|
- # Calculate our position size
|
|
|
|
- our_size = await self.calculate_position_size(trade)
|
|
|
|
-
|
|
|
|
- if our_size < self.min_position_size:
|
|
|
|
- self.logger.info(f"Skipping {trade.coin} trade - size too small: ${our_size:.2f}")
|
|
|
|
- # Still mark as copied to avoid retrying
|
|
|
|
|
|
+ # Get current price for the asset
|
|
|
|
+ symbol = f"{trade.coin}/USDC:USDC"
|
|
|
|
+ try:
|
|
|
|
+ market_data = await asyncio.to_thread(self.client.get_market_data, symbol)
|
|
|
|
+ if not market_data or not market_data.get('ticker'):
|
|
|
|
+ self.logger.error(f"❌ Could not get market data for {trade.coin}")
|
|
|
|
+ await self.state_manager.add_copied_trade_async(trade.original_trade_hash)
|
|
|
|
+ return
|
|
|
|
+
|
|
|
|
+ current_price = float(market_data['ticker'].get('last', 0))
|
|
|
|
+ if current_price <= 0:
|
|
|
|
+ self.logger.error(f"❌ Invalid price for {trade.coin}: {current_price}")
|
|
|
|
+ await self.state_manager.add_copied_trade_async(trade.original_trade_hash)
|
|
|
|
+ return
|
|
|
|
+ except Exception as e:
|
|
|
|
+ self.logger.error(f"❌ Error getting price for {trade.coin}: {e}")
|
|
await self.state_manager.add_copied_trade_async(trade.original_trade_hash)
|
|
await self.state_manager.add_copied_trade_async(trade.original_trade_hash)
|
|
return
|
|
return
|
|
|
|
|
|
# Apply leverage limit
|
|
# Apply leverage limit
|
|
leverage = min(trade.leverage, self.max_leverage)
|
|
leverage = min(trade.leverage, self.max_leverage)
|
|
|
|
|
|
|
|
+ # Calculate our position size with proper leverage handling
|
|
|
|
+ position_calc = await self.calculate_position_size(trade, current_price, leverage)
|
|
|
|
+
|
|
|
|
+ if position_calc['margin_to_use'] < self.min_position_size:
|
|
|
|
+ self.logger.info(f"Skipping {trade.coin} trade - margin too small: ${position_calc['margin_to_use']:.2f} (min: ${self.min_position_size:.2f})")
|
|
|
|
+ # Still mark as copied to avoid retrying
|
|
|
|
+ await self.state_manager.add_copied_trade_async(trade.original_trade_hash)
|
|
|
|
+ return
|
|
|
|
+
|
|
# Add execution delay
|
|
# Add execution delay
|
|
await asyncio.sleep(self.execution_delay)
|
|
await asyncio.sleep(self.execution_delay)
|
|
|
|
|
|
# Execute the trade
|
|
# Execute the trade
|
|
- success = await self._execute_hyperliquid_trade(trade, our_size, leverage)
|
|
|
|
|
|
+ success = await self._execute_hyperliquid_trade(trade, position_calc, leverage)
|
|
|
|
|
|
# Mark trade as copied (whether successful or not to avoid retrying)
|
|
# Mark trade as copied (whether successful or not to avoid retrying)
|
|
await self.state_manager.add_copied_trade_async(trade.original_trade_hash)
|
|
await self.state_manager.add_copied_trade_async(trade.original_trade_hash)
|
|
@@ -412,8 +440,10 @@ class CopyTradingMonitor:
|
|
f"🔄 Copy Trade {status}\n"
|
|
f"🔄 Copy Trade {status}\n"
|
|
f"Action: {trade.action}\n"
|
|
f"Action: {trade.action}\n"
|
|
f"Asset: {trade.coin}\n"
|
|
f"Asset: {trade.coin}\n"
|
|
- f"Size: ${our_size:.2f}\n"
|
|
|
|
- f"Leverage: {leverage}x\n"
|
|
|
|
|
|
+ f"💳 Margin Used: ${position_calc['margin_to_use']:.2f}\n"
|
|
|
|
+ f"🏦 Position Value: ${position_calc['position_value']:.2f}\n"
|
|
|
|
+ f"🪙 Token Amount: {position_calc['token_amount']:.6f}\n"
|
|
|
|
+ f"⚖️ Leverage: {leverage}x\n"
|
|
f"Target: {trade.target_trader_address[:10]}...\n"
|
|
f"Target: {trade.target_trader_address[:10]}...\n"
|
|
f"Trade ID: {trade.original_trade_hash[:16]}..."
|
|
f"Trade ID: {trade.original_trade_hash[:16]}..."
|
|
)
|
|
)
|
|
@@ -433,53 +463,130 @@ class CopyTradingMonitor:
|
|
f"Trade ID: {trade.original_trade_hash[:16]}..."
|
|
f"Trade ID: {trade.original_trade_hash[:16]}..."
|
|
)
|
|
)
|
|
|
|
|
|
- async def calculate_position_size(self, trade: CopyTrade) -> float:
|
|
|
|
- """Calculate our position size based on the copy trading mode"""
|
|
|
|
|
|
+ async def calculate_position_size(self, trade: CopyTrade, current_price: float, leverage: float) -> Dict[str, float]:
|
|
|
|
+ """
|
|
|
|
+ Calculate our position size based on the copy trading mode.
|
|
|
|
+ Returns margin allocation and converts to actual token amount.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ trade: The copy trade to execute
|
|
|
|
+ current_price: Current price of the asset
|
|
|
|
+ leverage: Leverage to use for the trade
|
|
|
|
+
|
|
|
|
+ Returns:
|
|
|
|
+ Dict with 'margin_to_use', 'position_value', 'token_amount'
|
|
|
|
+ """
|
|
try:
|
|
try:
|
|
# Get our current account balance
|
|
# Get our current account balance
|
|
our_balance = await self.get_our_account_balance()
|
|
our_balance = await self.get_our_account_balance()
|
|
|
|
+ self.logger.info(f"📊 Our account balance: ${our_balance:.2f}")
|
|
|
|
+ self.logger.info(f"🎯 Portfolio percentage: {self.portfolio_percentage:.1%}")
|
|
|
|
+ self.logger.info(f"📈 Copy mode: {self.copy_mode}")
|
|
|
|
+ self.logger.info(f"💰 Current {trade.coin} price: ${current_price:.4f}")
|
|
|
|
+ self.logger.info(f"⚖️ Leverage to use: {leverage:.1f}x")
|
|
|
|
+
|
|
|
|
+ # Calculate the MARGIN we want to allocate (risk-based)
|
|
|
|
+ margin_to_use = 0.0
|
|
|
|
|
|
if self.copy_mode == 'FIXED':
|
|
if self.copy_mode == 'FIXED':
|
|
- # Fixed percentage of our account
|
|
|
|
- return our_balance * self.portfolio_percentage
|
|
|
|
|
|
+ # Fixed percentage of our account as margin
|
|
|
|
+ margin_to_use = our_balance * self.portfolio_percentage
|
|
|
|
+ self.logger.info(f"🔢 FIXED mode - margin to allocate: ${margin_to_use:.2f}")
|
|
|
|
|
|
elif self.copy_mode == 'PROPORTIONAL':
|
|
elif self.copy_mode == 'PROPORTIONAL':
|
|
# Get target trader's account balance
|
|
# Get target trader's account balance
|
|
target_balance = await self.get_target_account_balance()
|
|
target_balance = await self.get_target_account_balance()
|
|
|
|
+ self.logger.info(f"🎯 Target balance: ${target_balance:.2f}")
|
|
|
|
|
|
if target_balance <= 0:
|
|
if target_balance <= 0:
|
|
- return our_balance * self.portfolio_percentage
|
|
|
|
-
|
|
|
|
- # Calculate target trader's position percentage
|
|
|
|
- target_pos = self.target_positions.get(trade.coin)
|
|
|
|
- if not target_pos:
|
|
|
|
- return our_balance * self.portfolio_percentage
|
|
|
|
-
|
|
|
|
- target_position_percentage = target_pos.margin_used / target_balance
|
|
|
|
-
|
|
|
|
- # Apply same percentage to our account
|
|
|
|
- our_position_size = our_balance * target_position_percentage
|
|
|
|
-
|
|
|
|
- # Cap at our portfolio percentage limit
|
|
|
|
- max_size = our_balance * self.portfolio_percentage
|
|
|
|
- return min(our_position_size, max_size)
|
|
|
|
-
|
|
|
|
|
|
+ margin_to_use = our_balance * self.portfolio_percentage
|
|
|
|
+ self.logger.info(f"🔢 PROPORTIONAL mode (fallback) - margin: ${margin_to_use:.2f}")
|
|
|
|
+ else:
|
|
|
|
+ # Calculate target trader's margin percentage
|
|
|
|
+ target_pos = self.target_positions.get(trade.coin)
|
|
|
|
+ if not target_pos:
|
|
|
|
+ margin_to_use = our_balance * self.portfolio_percentage
|
|
|
|
+ self.logger.info(f"🔢 PROPORTIONAL mode (no target pos) - margin: ${margin_to_use:.2f}")
|
|
|
|
+ else:
|
|
|
|
+ target_margin_percentage = target_pos.margin_used / target_balance
|
|
|
|
+ self.logger.info(f"📊 Target margin percentage: {target_margin_percentage:.1%}")
|
|
|
|
+
|
|
|
|
+ # Apply same margin percentage to our account
|
|
|
|
+ our_proportional_margin = our_balance * target_margin_percentage
|
|
|
|
+
|
|
|
|
+ # Cap at our portfolio percentage limit
|
|
|
|
+ max_margin = our_balance * self.portfolio_percentage
|
|
|
|
+ margin_to_use = min(our_proportional_margin, max_margin)
|
|
|
|
+
|
|
|
|
+ self.logger.info(f"🔢 PROPORTIONAL mode - uncapped: ${our_proportional_margin:.2f}, max: ${max_margin:.2f}, final: ${margin_to_use:.2f}")
|
|
|
|
+
|
|
else:
|
|
else:
|
|
- return our_balance * self.portfolio_percentage
|
|
|
|
|
|
+ margin_to_use = our_balance * self.portfolio_percentage
|
|
|
|
+ self.logger.info(f"🔢 Unknown mode (fallback) - margin: ${margin_to_use:.2f}")
|
|
|
|
+
|
|
|
|
+ # Calculate position value with leverage
|
|
|
|
+ position_value = margin_to_use * leverage
|
|
|
|
+
|
|
|
|
+ # Calculate token amount based on current price
|
|
|
|
+ token_amount = position_value / current_price
|
|
|
|
+
|
|
|
|
+ result = {
|
|
|
|
+ 'margin_to_use': margin_to_use,
|
|
|
|
+ 'position_value': position_value,
|
|
|
|
+ 'token_amount': token_amount
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ self.logger.info(f"📊 Position calculation:")
|
|
|
|
+ self.logger.info(f" 💳 Margin to use: ${margin_to_use:.2f}")
|
|
|
|
+ self.logger.info(f" 🏦 Position value (with {leverage:.1f}x): ${position_value:.2f}")
|
|
|
|
+ self.logger.info(f" 🪙 Token amount: {token_amount:.6f} {trade.coin}")
|
|
|
|
+
|
|
|
|
+ return result
|
|
|
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
self.logger.error(f"Error calculating position size: {e}")
|
|
self.logger.error(f"Error calculating position size: {e}")
|
|
# Fallback to fixed percentage
|
|
# Fallback to fixed percentage
|
|
our_balance = await self.get_our_account_balance()
|
|
our_balance = await self.get_our_account_balance()
|
|
- return our_balance * self.portfolio_percentage
|
|
|
|
|
|
+ fallback_margin = our_balance * self.portfolio_percentage
|
|
|
|
+ fallback_value = fallback_margin * leverage
|
|
|
|
+ fallback_tokens = fallback_value / current_price
|
|
|
|
+
|
|
|
|
+ result = {
|
|
|
|
+ 'margin_to_use': fallback_margin,
|
|
|
|
+ 'position_value': fallback_value,
|
|
|
|
+ 'token_amount': fallback_tokens
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ self.logger.info(f"🔢 Error fallback - margin: ${fallback_margin:.2f}, tokens: {fallback_tokens:.6f}")
|
|
|
|
+ return result
|
|
|
|
|
|
async def get_our_account_balance(self) -> float:
|
|
async def get_our_account_balance(self) -> float:
|
|
"""Get our account balance"""
|
|
"""Get our account balance"""
|
|
try:
|
|
try:
|
|
- balance_info = self.client.get_balance()
|
|
|
|
|
|
+ balance_info = await asyncio.to_thread(self.client.get_balance)
|
|
|
|
+ self.logger.info(f"🔍 Raw balance info: {balance_info}")
|
|
|
|
+
|
|
if balance_info:
|
|
if balance_info:
|
|
- return float(balance_info.get('accountValue', 0))
|
|
|
|
|
|
+ # Use the same approach as the /balance command
|
|
|
|
+ usdc_total = 0.0
|
|
|
|
+ usdc_free = 0.0
|
|
|
|
+ usdc_used = 0.0
|
|
|
|
+
|
|
|
|
+ if 'USDC' in balance_info.get('total', {}):
|
|
|
|
+ usdc_total = float(balance_info['total']['USDC'])
|
|
|
|
+ usdc_free = float(balance_info.get('free', {}).get('USDC', 0))
|
|
|
|
+ usdc_used = float(balance_info.get('used', {}).get('USDC', 0))
|
|
|
|
+
|
|
|
|
+ self.logger.info(f"💰 USDC Balance - Total: ${usdc_total:.2f}, Free: ${usdc_free:.2f}, Used: ${usdc_used:.2f}")
|
|
|
|
+
|
|
|
|
+ if usdc_total > 0:
|
|
|
|
+ self.logger.info(f"📊 Using total USDC balance: ${usdc_total:.2f}")
|
|
|
|
+ return usdc_total
|
|
|
|
+ else:
|
|
|
|
+ self.logger.warning(f"⚠️ No USDC balance found - raw response: {balance_info}")
|
|
|
|
+ return 0.0
|
|
else:
|
|
else:
|
|
|
|
+ self.logger.warning("⚠️ No balance info returned")
|
|
return 0.0
|
|
return 0.0
|
|
except Exception as e:
|
|
except Exception as e:
|
|
self.logger.error(f"Error getting our account balance: {e}")
|
|
self.logger.error(f"Error getting our account balance: {e}")
|
|
@@ -510,58 +617,105 @@ class CopyTradingMonitor:
|
|
self.logger.error(f"Error getting target account balance: {e}")
|
|
self.logger.error(f"Error getting target account balance: {e}")
|
|
return 0.0
|
|
return 0.0
|
|
|
|
|
|
- async def _execute_hyperliquid_trade(self, trade: CopyTrade, size: float, leverage: float) -> bool:
|
|
|
|
|
|
+ async def _execute_hyperliquid_trade(self, trade: CopyTrade, position_calc: Dict[str, float], leverage: float) -> bool:
|
|
"""Execute trade on Hyperliquid"""
|
|
"""Execute trade on Hyperliquid"""
|
|
try:
|
|
try:
|
|
# Determine if this is a buy or sell order
|
|
# Determine if this is a buy or sell order
|
|
is_buy = 'long' in trade.action or ('close' in trade.action and 'short' in trade.action)
|
|
is_buy = 'long' in trade.action or ('close' in trade.action and 'short' in trade.action)
|
|
-
|
|
|
|
- # For position opening/closing
|
|
|
|
- if 'open' in trade.action:
|
|
|
|
- # Open new position
|
|
|
|
- result = await self.client.place_order(
|
|
|
|
- symbol=trade.coin,
|
|
|
|
- side='buy' if is_buy else 'sell',
|
|
|
|
- size=size,
|
|
|
|
- leverage=leverage,
|
|
|
|
- order_type='market'
|
|
|
|
|
|
+ side = 'buy' if is_buy else 'sell'
|
|
|
|
+
|
|
|
|
+ # Extract values from position calculation
|
|
|
|
+ token_amount = position_calc['token_amount']
|
|
|
|
+ margin_used = position_calc['margin_to_use']
|
|
|
|
+ position_value = position_calc['position_value']
|
|
|
|
+
|
|
|
|
+ self.logger.info(f"🔄 Executing {trade.action} for {trade.coin}:")
|
|
|
|
+ self.logger.info(f" 📊 Side: {side}")
|
|
|
|
+ self.logger.info(f" 🪙 Token Amount: {token_amount:.6f} {trade.coin}")
|
|
|
|
+ self.logger.info(f" 💳 Margin: ${margin_used:.2f}")
|
|
|
|
+ self.logger.info(f" 🏦 Position Value: ${position_value:.2f}")
|
|
|
|
+ self.logger.info(f" ⚖️ Leverage: {leverage}x")
|
|
|
|
+
|
|
|
|
+ # For position opening/closing/modifying - all use market orders
|
|
|
|
+ if 'open' in trade.action or 'add' in trade.action:
|
|
|
|
+ # Open new position or add to existing position
|
|
|
|
+ symbol = f"{trade.coin}/USDC:USDC"
|
|
|
|
+ result, error = await asyncio.to_thread(
|
|
|
|
+ self.client.place_market_order,
|
|
|
|
+ symbol=symbol,
|
|
|
|
+ side=side,
|
|
|
|
+ amount=token_amount
|
|
)
|
|
)
|
|
|
|
+ if error:
|
|
|
|
+ self.logger.error(f"❌ Market order failed: {error}")
|
|
|
|
+ return False
|
|
|
|
+
|
|
elif 'close' in trade.action:
|
|
elif 'close' in trade.action:
|
|
- # Close existing position
|
|
|
|
- result = await self.client.close_position(
|
|
|
|
- symbol=trade.coin,
|
|
|
|
- size=size
|
|
|
|
- )
|
|
|
|
- elif 'add' in trade.action:
|
|
|
|
- # Add to existing position
|
|
|
|
- result = await self.client.place_order(
|
|
|
|
- symbol=trade.coin,
|
|
|
|
- side='buy' if is_buy else 'sell',
|
|
|
|
- size=size,
|
|
|
|
- leverage=leverage,
|
|
|
|
- order_type='market'
|
|
|
|
|
|
+ # Close existing position - we need to place an opposite market order
|
|
|
|
+ # Get current position to determine the exact size to close
|
|
|
|
+ symbol = f"{trade.coin}/USDC:USDC"
|
|
|
|
+ positions = await asyncio.to_thread(self.client.get_positions, symbol=symbol)
|
|
|
|
+ if not positions:
|
|
|
|
+ self.logger.warning(f"⚠️ No position found for {trade.coin} to close")
|
|
|
|
+ return False
|
|
|
|
+
|
|
|
|
+ # Find the position to close
|
|
|
|
+ position_to_close = None
|
|
|
|
+ for pos in positions:
|
|
|
|
+ if pos.get('symbol') == symbol and float(pos.get('contracts', 0)) != 0:
|
|
|
|
+ position_to_close = pos
|
|
|
|
+ break
|
|
|
|
+
|
|
|
|
+ if not position_to_close:
|
|
|
|
+ self.logger.warning(f"⚠️ No open position found for {trade.coin}")
|
|
|
|
+ return False
|
|
|
|
+
|
|
|
|
+ # Determine the opposite side to close the position
|
|
|
|
+ current_side = 'long' if float(position_to_close.get('contracts', 0)) > 0 else 'short'
|
|
|
|
+ close_side = 'sell' if current_side == 'long' else 'buy'
|
|
|
|
+ close_size = abs(float(position_to_close.get('contracts', 0)))
|
|
|
|
+
|
|
|
|
+ self.logger.info(f"📉 Closing {current_side} position: {close_side} {close_size} {trade.coin}")
|
|
|
|
+
|
|
|
|
+ result, error = await asyncio.to_thread(
|
|
|
|
+ self.client.place_market_order,
|
|
|
|
+ symbol=symbol,
|
|
|
|
+ side=close_side,
|
|
|
|
+ amount=close_size
|
|
)
|
|
)
|
|
|
|
+ if error:
|
|
|
|
+ self.logger.error(f"❌ Close order failed: {error}")
|
|
|
|
+ return False
|
|
|
|
+
|
|
elif 'reduce' in trade.action:
|
|
elif 'reduce' in trade.action:
|
|
# Reduce existing position
|
|
# Reduce existing position
|
|
- result = await self.client.place_order(
|
|
|
|
- symbol=trade.coin,
|
|
|
|
- side='sell' if 'long' in trade.action else 'buy',
|
|
|
|
- size=size,
|
|
|
|
- order_type='market'
|
|
|
|
|
|
+ reduce_side = 'sell' if 'long' in trade.action else 'buy'
|
|
|
|
+ symbol = f"{trade.coin}/USDC:USDC"
|
|
|
|
+ result, error = await asyncio.to_thread(
|
|
|
|
+ self.client.place_market_order,
|
|
|
|
+ symbol=symbol,
|
|
|
|
+ side=reduce_side,
|
|
|
|
+ amount=token_amount
|
|
)
|
|
)
|
|
|
|
+ if error:
|
|
|
|
+ self.logger.error(f"❌ Reduce order failed: {error}")
|
|
|
|
+ return False
|
|
|
|
+
|
|
else:
|
|
else:
|
|
- self.logger.error(f"Unknown trade action: {trade.action}")
|
|
|
|
|
|
+ self.logger.error(f"❌ Unknown trade action: {trade.action}")
|
|
return False
|
|
return False
|
|
|
|
|
|
- if result and result.get('success'):
|
|
|
|
- self.logger.info(f"Successfully executed copy trade: {trade.action} {size} {trade.coin}")
|
|
|
|
|
|
+ # Check if result indicates success
|
|
|
|
+ if result:
|
|
|
|
+ self.logger.info(f"✅ Successfully executed copy trade: {trade.action}")
|
|
|
|
+ self.logger.info(f" 🪙 {token_amount:.6f} {trade.coin} (${position_value:.2f} value, ${margin_used:.2f} margin)")
|
|
return True
|
|
return True
|
|
else:
|
|
else:
|
|
- self.logger.error(f"Failed to execute copy trade: {result}")
|
|
|
|
|
|
+ self.logger.error(f"❌ Failed to execute copy trade - no result returned")
|
|
return False
|
|
return False
|
|
|
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
- self.logger.error(f"Error executing Hyperliquid trade: {e}")
|
|
|
|
|
|
+ self.logger.error(f"❌ Error executing Hyperliquid trade: {e}")
|
|
return False
|
|
return False
|
|
|
|
|
|
async def sync_positions(self):
|
|
async def sync_positions(self):
|
|
@@ -603,6 +757,60 @@ class CopyTradingMonitor:
|
|
f"\n\n💾 State saved - can resume later"
|
|
f"\n\n💾 State saved - can resume later"
|
|
)
|
|
)
|
|
|
|
|
|
|
|
+ async def test_balance_fetching(self) -> Dict[str, Any]:
|
|
|
|
+ """Test balance fetching and position sizing for debugging purposes"""
|
|
|
|
+ try:
|
|
|
|
+ our_balance = await self.get_our_account_balance()
|
|
|
|
+ target_balance = await self.get_target_account_balance()
|
|
|
|
+
|
|
|
|
+ # Test with a mock trade for SOL
|
|
|
|
+ test_price = 150.0 # Mock SOL price
|
|
|
|
+ test_leverage = min(10.0, self.max_leverage) # Test leverage
|
|
|
|
+
|
|
|
|
+ mock_trade = CopyTrade(
|
|
|
|
+ coin="SOL",
|
|
|
|
+ action="open_long",
|
|
|
|
+ size=100,
|
|
|
|
+ leverage=test_leverage,
|
|
|
|
+ original_trade_hash="test_hash",
|
|
|
|
+ target_trader_address=self.target_address or "test_address",
|
|
|
|
+ timestamp=int(time.time() * 1000)
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ # Calculate position with leverage
|
|
|
|
+ position_calc = await self.calculate_position_size(mock_trade, test_price, test_leverage)
|
|
|
|
+
|
|
|
|
+ result = {
|
|
|
|
+ 'our_balance': our_balance,
|
|
|
|
+ 'target_balance': target_balance,
|
|
|
|
+ 'portfolio_percentage': self.portfolio_percentage,
|
|
|
|
+ 'test_price': test_price,
|
|
|
|
+ 'test_leverage': test_leverage,
|
|
|
|
+ 'margin_to_use': position_calc['margin_to_use'],
|
|
|
|
+ 'position_value': position_calc['position_value'],
|
|
|
|
+ 'token_amount': position_calc['token_amount'],
|
|
|
|
+ 'min_position_size': self.min_position_size,
|
|
|
|
+ 'would_execute': position_calc['margin_to_use'] >= self.min_position_size,
|
|
|
|
+ 'config_enabled': self.enabled,
|
|
|
|
+ 'state_enabled': self.state_manager.is_enabled()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ self.logger.info(f"🧪 Balance & Leverage test results:")
|
|
|
|
+ self.logger.info(f" 💰 Our balance: ${our_balance:.2f}")
|
|
|
|
+ self.logger.info(f" 🎯 Target balance: ${target_balance:.2f}")
|
|
|
|
+ self.logger.info(f" 📊 Portfolio allocation: {self.portfolio_percentage:.1%}")
|
|
|
|
+ self.logger.info(f" ⚖️ Test leverage: {test_leverage:.1f}x")
|
|
|
|
+ self.logger.info(f" 💳 Margin to use: ${position_calc['margin_to_use']:.2f}")
|
|
|
|
+ self.logger.info(f" 🏦 Position value: ${position_calc['position_value']:.2f}")
|
|
|
|
+ self.logger.info(f" 🪙 Token amount: {position_calc['token_amount']:.6f} SOL")
|
|
|
|
+ self.logger.info(f" ✅ Would execute: {position_calc['margin_to_use'] >= self.min_position_size}")
|
|
|
|
+
|
|
|
|
+ return result
|
|
|
|
+
|
|
|
|
+ except Exception as e:
|
|
|
|
+ self.logger.error(f"❌ Error in balance test: {e}")
|
|
|
|
+ return {'error': str(e)}
|
|
|
|
+
|
|
def get_status(self) -> Dict[str, Any]:
|
|
def get_status(self) -> Dict[str, Any]:
|
|
"""Get current copy trading status"""
|
|
"""Get current copy trading status"""
|
|
session_info = self.state_manager.get_session_info()
|
|
session_info = self.state_manager.get_session_info()
|