|
@@ -112,10 +112,60 @@ class InfoCommands:
|
|
|
await context.bot.send_message(chat_id=chat_id, text="❌ Trading statistics not available.")
|
|
|
return
|
|
|
|
|
|
- # Get open positions from unified trades table
|
|
|
+ # 🆕 AUTO-SYNC: Check for positions on exchange that don't have trade lifecycle records
|
|
|
+ exchange_positions = self.trading_engine.get_positions() or []
|
|
|
+ synced_positions = []
|
|
|
+
|
|
|
+ for exchange_pos in exchange_positions:
|
|
|
+ symbol = exchange_pos.get('symbol')
|
|
|
+ contracts = float(exchange_pos.get('contracts', 0))
|
|
|
+
|
|
|
+ if symbol and abs(contracts) > 0:
|
|
|
+ # Check if we have a trade lifecycle record for this position
|
|
|
+ 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))
|
|
|
+ position_side = 'long' if contracts > 0 else 'short'
|
|
|
+ order_side = 'buy' if contracts > 0 else 'sell'
|
|
|
+
|
|
|
+ logger.info(f"🔄 Auto-syncing orphaned position: {symbol} {position_side} {abs(contracts)} @ ${entry_price}")
|
|
|
+
|
|
|
+ # Create trade lifecycle for external position
|
|
|
+ lifecycle_id = stats.create_trade_lifecycle(
|
|
|
+ symbol=symbol,
|
|
|
+ side=order_side,
|
|
|
+ entry_order_id=f"external_sync_{int(datetime.now().timestamp())}",
|
|
|
+ trade_type='external'
|
|
|
+ )
|
|
|
+
|
|
|
+ if lifecycle_id:
|
|
|
+ # Update to position_opened status
|
|
|
+ success = stats.update_trade_position_opened(
|
|
|
+ lifecycle_id=lifecycle_id,
|
|
|
+ entry_price=entry_price,
|
|
|
+ entry_amount=abs(contracts),
|
|
|
+ exchange_fill_id=f"external_fill_{int(datetime.now().timestamp())}"
|
|
|
+ )
|
|
|
+
|
|
|
+ if success:
|
|
|
+ synced_positions.append(symbol)
|
|
|
+ logger.info(f"✅ Successfully synced orphaned position for {symbol}")
|
|
|
+ else:
|
|
|
+ logger.error(f"❌ Failed to sync orphaned position for {symbol}")
|
|
|
+ else:
|
|
|
+ logger.error(f"❌ Failed to create lifecycle for orphaned position {symbol}")
|
|
|
+
|
|
|
+ if synced_positions:
|
|
|
+ sync_msg = f"🔄 <b>Auto-synced {len(synced_positions)} orphaned position(s):</b> {', '.join([s.split('/')[0] for s in synced_positions])}\n\n"
|
|
|
+ else:
|
|
|
+ sync_msg = ""
|
|
|
+
|
|
|
+ # Get open positions from unified trades table (now including any newly synced ones)
|
|
|
open_positions = stats.get_open_positions()
|
|
|
|
|
|
- positions_text = "📈 <b>Open Positions</b>\n\n"
|
|
|
+ positions_text = f"📈 <b>Open Positions</b>\n\n{sync_msg}"
|
|
|
|
|
|
if open_positions:
|
|
|
total_unrealized = 0
|
|
@@ -128,6 +178,7 @@ class InfoCommands:
|
|
|
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
|
|
@@ -165,7 +216,14 @@ class InfoCommands:
|
|
|
entry_price_str = formatter.format_price_with_symbol(entry_price, token)
|
|
|
mark_price_str = formatter.format_price_with_symbol(mark_price, token)
|
|
|
|
|
|
- positions_text += f"{pos_emoji} <b>{token} ({direction})</b>\n"
|
|
|
+ # Trade type indicator
|
|
|
+ type_indicator = ""
|
|
|
+ if trade_type == 'external':
|
|
|
+ type_indicator = " 🔄" # External/synced position
|
|
|
+ elif trade_type == 'bot':
|
|
|
+ type_indicator = " 🤖" # Bot-created position
|
|
|
+
|
|
|
+ positions_text += f"{pos_emoji} <b>{token} ({direction}){type_indicator}</b>\n"
|
|
|
positions_text += f" 📏 Size: {abs(current_amount):.6f} {token}\n"
|
|
|
positions_text += f" 💰 Entry: {entry_price_str}\n"
|
|
|
positions_text += f" 📊 Mark: {mark_price_str}\n"
|
|
@@ -191,7 +249,7 @@ class InfoCommands:
|
|
|
positions_text += f"💼 <b>Total Portfolio:</b>\n"
|
|
|
positions_text += f" 💵 Total Value: ${total_position_value:,.2f}\n"
|
|
|
positions_text += f" {portfolio_emoji} Total P&L: ${total_unrealized:,.2f}\n\n"
|
|
|
- positions_text += f"🆕 <b>Source:</b> Unified Trades Table (Phase 4)\n"
|
|
|
+ positions_text += f"🤖 <b>Legend:</b> 🤖 Bot-created • 🔄 External/synced\n"
|
|
|
positions_text += f"💡 Use /sl [token] [price] or /tp [token] [price] to set risk management"
|
|
|
|
|
|
else:
|