|
@@ -125,17 +125,18 @@ class InfoCommands:
|
|
|
existing_trade = stats.get_trade_by_symbol_and_status(symbol, 'position_opened')
|
|
|
|
|
|
if not existing_trade:
|
|
|
- # 🚨 ORPHANED POSITION: Auto-create trade lifecycle record
|
|
|
- entry_price = float(exchange_pos.get('entryPx', 0))
|
|
|
+ # 🚨 ORPHANED POSITION: Auto-create trade lifecycle record using exchange data
|
|
|
+ entry_price = float(exchange_pos.get('entryPrice', 0))
|
|
|
position_side = 'long' if contracts > 0 else 'short'
|
|
|
order_side = 'buy' if contracts > 0 else 'sell'
|
|
|
|
|
|
- # 🔧 FIX: If entry price is 0, try to estimate it
|
|
|
- if entry_price <= 0:
|
|
|
+ # ✅ Use exchange data - no need to estimate!
|
|
|
+ if entry_price > 0:
|
|
|
+ logger.info(f"🔄 Auto-syncing orphaned position: {symbol} {position_side} {abs(contracts)} @ ${entry_price} (exchange data)")
|
|
|
+ else:
|
|
|
+ # Fallback only if exchange truly doesn't provide entry price
|
|
|
entry_price = await self._estimate_entry_price_for_orphaned_position(symbol, contracts)
|
|
|
- logger.info(f"🔄 Estimated entry price for orphaned {symbol}: ${entry_price:.4f}")
|
|
|
-
|
|
|
- logger.info(f"🔄 Auto-syncing orphaned position: {symbol} {position_side} {abs(contracts)} @ ${entry_price}")
|
|
|
+ logger.warning(f"🔄 Auto-syncing orphaned position: {symbol} {position_side} {abs(contracts)} @ ${entry_price} (estimated)")
|
|
|
|
|
|
# Create trade lifecycle for external position
|
|
|
lifecycle_id = stats.create_trade_lifecycle(
|
|
@@ -176,32 +177,61 @@ class InfoCommands:
|
|
|
total_unrealized = 0
|
|
|
total_position_value = 0
|
|
|
|
|
|
+ # Also get fresh exchange data for display
|
|
|
+ fresh_exchange_positions = self.trading_engine.get_positions() or []
|
|
|
+ exchange_data_map = {pos.get('symbol'): pos for pos in fresh_exchange_positions}
|
|
|
+
|
|
|
for position_trade in open_positions:
|
|
|
symbol = position_trade['symbol']
|
|
|
token = symbol.split('/')[0] if '/' in symbol else symbol
|
|
|
position_side = position_trade['position_side'] # 'long' or 'short'
|
|
|
entry_price = position_trade['entry_price']
|
|
|
current_amount = position_trade['current_position_size']
|
|
|
- unrealized_pnl = position_trade.get('unrealized_pnl', 0)
|
|
|
trade_type = position_trade.get('trade_type', 'manual')
|
|
|
|
|
|
- # Get current market price
|
|
|
- mark_price = entry_price # Fallback
|
|
|
- try:
|
|
|
- market_data = self.trading_engine.get_market_data(symbol)
|
|
|
- if market_data and market_data.get('ticker'):
|
|
|
- mark_price = float(market_data['ticker'].get('last', entry_price))
|
|
|
-
|
|
|
- # Update unrealized PnL with current price
|
|
|
- if position_side == 'long':
|
|
|
- unrealized_pnl = current_amount * (mark_price - entry_price)
|
|
|
- else: # Short position
|
|
|
- unrealized_pnl = current_amount * (entry_price - mark_price)
|
|
|
- except:
|
|
|
- pass # Use entry price as fallback
|
|
|
+ # 🆕 Use fresh exchange data if available (most accurate)
|
|
|
+ exchange_pos = exchange_data_map.get(symbol)
|
|
|
+ if exchange_pos:
|
|
|
+ # Use exchange's official data
|
|
|
+ unrealized_pnl = float(exchange_pos.get('unrealizedPnl', 0))
|
|
|
+ mark_price = float(exchange_pos.get('markPrice') or 0)
|
|
|
+ position_value = float(exchange_pos.get('notional', 0))
|
|
|
+ liquidation_price = float(exchange_pos.get('liquidationPrice', 0))
|
|
|
+ margin_used = float(exchange_pos.get('initialMargin', 0))
|
|
|
+ leverage = float(exchange_pos.get('leverage', 1))
|
|
|
+ pnl_percentage = float(exchange_pos.get('percentage', 0))
|
|
|
+
|
|
|
+ # Get mark price from market data if not in position data
|
|
|
+ if mark_price <= 0:
|
|
|
+ try:
|
|
|
+ market_data = self.trading_engine.get_market_data(symbol)
|
|
|
+ if market_data and market_data.get('ticker'):
|
|
|
+ mark_price = float(market_data['ticker'].get('last', entry_price))
|
|
|
+ except:
|
|
|
+ mark_price = entry_price # Fallback
|
|
|
+ else:
|
|
|
+ # Fallback to our calculation if exchange data unavailable
|
|
|
+ unrealized_pnl = position_trade.get('unrealized_pnl', 0)
|
|
|
+ mark_price = entry_price # Fallback
|
|
|
+ try:
|
|
|
+ market_data = self.trading_engine.get_market_data(symbol)
|
|
|
+ if market_data and market_data.get('ticker'):
|
|
|
+ mark_price = float(market_data['ticker'].get('last', entry_price))
|
|
|
+
|
|
|
+ # Calculate unrealized PnL with current price
|
|
|
+ if position_side == 'long':
|
|
|
+ unrealized_pnl = current_amount * (mark_price - entry_price)
|
|
|
+ else: # Short position
|
|
|
+ unrealized_pnl = current_amount * (entry_price - mark_price)
|
|
|
+ except:
|
|
|
+ pass # Use entry price as fallback
|
|
|
+
|
|
|
+ position_value = abs(current_amount) * mark_price
|
|
|
+ liquidation_price = None
|
|
|
+ margin_used = None
|
|
|
+ leverage = None
|
|
|
+ pnl_percentage = (unrealized_pnl / position_value * 100) if position_value > 0 else 0
|
|
|
|
|
|
- # Calculate position value
|
|
|
- position_value = abs(current_amount) * mark_price
|
|
|
total_position_value += position_value
|
|
|
total_unrealized += unrealized_pnl
|
|
|
|
|
@@ -214,7 +244,6 @@ class InfoCommands:
|
|
|
direction = "SHORT"
|
|
|
|
|
|
pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
|
|
|
- pnl_percent = (unrealized_pnl / position_value * 100) if position_value > 0 else 0
|
|
|
|
|
|
# Format prices with proper precision for this token
|
|
|
formatter = get_formatter()
|
|
@@ -233,7 +262,16 @@ class InfoCommands:
|
|
|
positions_text += f" 💰 Entry: {entry_price_str}\n"
|
|
|
positions_text += f" 📊 Mark: {mark_price_str}\n"
|
|
|
positions_text += f" 💵 Value: ${position_value:,.2f}\n"
|
|
|
- positions_text += f" {pnl_emoji} P&L: ${unrealized_pnl:,.2f} ({pnl_percent:+.2f}%)\n"
|
|
|
+ positions_text += f" {pnl_emoji} P&L: ${unrealized_pnl:,.2f} ({pnl_percentage:+.2f}%)\n"
|
|
|
+
|
|
|
+ # Show exchange-provided risk data if available
|
|
|
+ if leverage:
|
|
|
+ positions_text += f" ⚡ Leverage: {leverage:.1f}x\n"
|
|
|
+ if margin_used:
|
|
|
+ positions_text += f" 💳 Margin: ${margin_used:,.2f}\n"
|
|
|
+ if liquidation_price:
|
|
|
+ liq_price_str = formatter.format_price_with_symbol(liquidation_price, token)
|
|
|
+ positions_text += f" ⚠️ Liquidation: {liq_price_str}\n"
|
|
|
|
|
|
# Show stop loss if linked
|
|
|
if position_trade.get('stop_loss_price'):
|