|
@@ -33,19 +33,25 @@ class PositionsCommands(InfoCommandsBase):
|
|
|
exchange_orders = self.trading_engine.get_orders() or []
|
|
|
|
|
|
# Debug: Log the exchange positions data structure
|
|
|
- logger.info(f"🔍 Exchange positions data: {[{k: v for k, v in pos.items() if k in ['coin', 'symbol', 'returnOnEquity', 'szi']} for pos in exchange_positions[:2]]}")
|
|
|
+ logger.info(f"🔍 Exchange positions data: {[{k: v for k, v in pos.items() if k in ['symbol']} for pos in exchange_positions[:2]]}")
|
|
|
|
|
|
# Create lookup for exchange data by symbol
|
|
|
exchange_data_by_symbol = {}
|
|
|
for ex_pos in exchange_positions:
|
|
|
- # Try both 'coin' and 'symbol' fields for mapping
|
|
|
- symbol_key = ex_pos.get('coin', '') or ex_pos.get('symbol', '')
|
|
|
- if symbol_key:
|
|
|
- # Also try extracting base asset from symbol if it's in format "TOKEN/USDC:USDC"
|
|
|
- if '/' in symbol_key:
|
|
|
- base_token = symbol_key.split('/')[0]
|
|
|
- exchange_data_by_symbol[base_token] = ex_pos
|
|
|
- exchange_data_by_symbol[symbol_key] = ex_pos
|
|
|
+ # Access the nested position data in info.position
|
|
|
+ position_data = ex_pos.get('info', {}).get('position', {})
|
|
|
+ if position_data:
|
|
|
+ # Get the coin symbol from the nested data
|
|
|
+ coin = position_data.get('coin', '')
|
|
|
+ if coin:
|
|
|
+ exchange_data_by_symbol[coin] = position_data
|
|
|
+
|
|
|
+ # Also map by the CCXT symbol for backup
|
|
|
+ symbol = ex_pos.get('symbol', '')
|
|
|
+ if symbol and '/' in symbol:
|
|
|
+ base_token = symbol.split('/')[0]
|
|
|
+ if base_token not in exchange_data_by_symbol and position_data:
|
|
|
+ exchange_data_by_symbol[base_token] = position_data
|
|
|
|
|
|
logger.info(f"🔍 Exchange data keys: {list(exchange_data_by_symbol.keys())}")
|
|
|
|
|
@@ -113,31 +119,40 @@ class PositionsCommands(InfoCommandsBase):
|
|
|
exchange_data = exchange_data_by_symbol.get(base_asset)
|
|
|
logger.info(f"🔍 Looking for '{base_asset}' in exchange data. Found: {exchange_data is not None}")
|
|
|
if exchange_data:
|
|
|
- logger.info(f"🔍 Exchange data for {base_asset}: ROE={exchange_data.get('returnOnEquity')}, markPrice={exchange_data.get('markPrice')}")
|
|
|
+ logger.info(f"🔍 Exchange data for {base_asset}: ROE={exchange_data.get('returnOnEquity')}, entryPx={exchange_data.get('entryPx')}")
|
|
|
|
|
|
# Get price data with defaults - prioritize live exchange data
|
|
|
mark_price = entry_price # Default to entry price
|
|
|
|
|
|
- # Try to get live mark price from exchange first
|
|
|
- if exchange_data and exchange_data.get('markPrice') is not None:
|
|
|
+ # For Hyperliquid, we need to calculate current mark price from position value and size
|
|
|
+ if exchange_data:
|
|
|
try:
|
|
|
- mark_price = float(exchange_data['markPrice'])
|
|
|
+ position_value = float(exchange_data.get('positionValue', 0))
|
|
|
+ position_size = float(exchange_data.get('szi', 0))
|
|
|
+ if position_size != 0:
|
|
|
+ mark_price = position_value / abs(position_size)
|
|
|
except (ValueError, TypeError):
|
|
|
- logger.warning(f"Could not convert exchange mark_price for {symbol}")
|
|
|
- # Fallback to database mark price
|
|
|
- elif position_trade.get('mark_price') is not None:
|
|
|
+ logger.warning(f"Could not calculate mark price from exchange data for {symbol}")
|
|
|
+
|
|
|
+ # Fallback to database mark price if calculation fails
|
|
|
+ if mark_price == entry_price and position_trade.get('mark_price') is not None:
|
|
|
try:
|
|
|
mark_price = float(position_trade['mark_price'])
|
|
|
except (ValueError, TypeError):
|
|
|
logger.warning(f"Could not convert database mark_price for {symbol}")
|
|
|
|
|
|
- # Calculate unrealized PnL
|
|
|
+ # Get unrealized PnL from exchange data first, then database
|
|
|
unrealized_pnl = 0.0
|
|
|
- if position_trade.get('unrealized_pnl') is not None:
|
|
|
+ if exchange_data and exchange_data.get('unrealizedPnl') is not None:
|
|
|
+ try:
|
|
|
+ unrealized_pnl = float(exchange_data['unrealizedPnl'])
|
|
|
+ except (ValueError, TypeError):
|
|
|
+ logger.warning(f"Could not convert exchange unrealizedPnl for {symbol}")
|
|
|
+ elif position_trade.get('unrealized_pnl') is not None:
|
|
|
try:
|
|
|
unrealized_pnl = float(position_trade['unrealized_pnl'])
|
|
|
except (ValueError, TypeError):
|
|
|
- logger.warning(f"Could not convert unrealized_pnl for {symbol}")
|
|
|
+ logger.warning(f"Could not convert database unrealized_pnl for {symbol}")
|
|
|
|
|
|
# Get ROE from live exchange data (much more accurate)
|
|
|
roe_percentage = 0.0
|
|
@@ -224,7 +239,7 @@ class PositionsCommands(InfoCommandsBase):
|
|
|
positions_text += f" 📈 Mark: {mark_price_str}\n"
|
|
|
|
|
|
pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
|
|
|
- positions_text += f" {pnl_emoji} P&L: ${unrealized_pnl:,.2f}\n"
|
|
|
+ positions_text += f" {pnl_emoji} uP&L: ${unrealized_pnl:,.2f}\n"
|
|
|
|
|
|
# Show ROE
|
|
|
roe_emoji = "🟢" if roe_percentage >= 0 else "🔴"
|