|
@@ -616,7 +616,7 @@ class InfoCommands:
|
|
|
logger.error(f"Error in cycles command: {e}")
|
|
|
|
|
|
async def active_trades_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
|
- """Handle the /active command to show active trades (Phase 1 testing)."""
|
|
|
+ """Handle the /active command to show active trades (using open positions)."""
|
|
|
chat_id = update.effective_chat.id
|
|
|
if not self._is_authorized(chat_id):
|
|
|
await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
|
|
@@ -628,87 +628,80 @@ class InfoCommands:
|
|
|
await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
|
|
|
return
|
|
|
|
|
|
- # Get all active trades
|
|
|
- all_active_trades = stats.get_all_active_trades()
|
|
|
+ # Get open positions from unified trades table (current active trades)
|
|
|
+ open_positions = stats.get_open_positions()
|
|
|
|
|
|
- if not all_active_trades:
|
|
|
+ if not open_positions:
|
|
|
await context.bot.send_message(
|
|
|
chat_id=chat_id,
|
|
|
- text="📊 <b>Active Trades (Phase 1)</b>\n\n📭 No active trades found.",
|
|
|
+ text="📊 <b>Active Positions</b>\n\n📭 No active positions found.\n\n💡 Use /long or /short to open positions.",
|
|
|
parse_mode='HTML'
|
|
|
)
|
|
|
return
|
|
|
|
|
|
- # Group by status
|
|
|
- active_trades_by_status = {}
|
|
|
- for trade in all_active_trades:
|
|
|
- status = trade['status']
|
|
|
- if status not in active_trades_by_status:
|
|
|
- active_trades_by_status[status] = []
|
|
|
- active_trades_by_status[status].append(trade)
|
|
|
-
|
|
|
- message_text = "📊 <b>Active Trades (Phase 1)</b>\n\n"
|
|
|
-
|
|
|
- # Show each status group
|
|
|
- for status, trades in active_trades_by_status.items():
|
|
|
- status_emoji = {
|
|
|
- 'pending': '⏳',
|
|
|
- 'active': '🟢',
|
|
|
- 'closed': '✅',
|
|
|
- 'cancelled': '❌'
|
|
|
- }.get(status, '📊')
|
|
|
+ message_text = "📊 <b>Active Positions</b>\n\n"
|
|
|
+
|
|
|
+ # Show each position
|
|
|
+ for position in open_positions:
|
|
|
+ symbol = position['symbol']
|
|
|
+ token = symbol.split('/')[0] if '/' in symbol else symbol
|
|
|
+ position_side = position['position_side'] # 'long' or 'short'
|
|
|
+ entry_price = position['entry_price']
|
|
|
+ current_amount = position['current_position_size']
|
|
|
+ trade_type = position.get('trade_type', 'manual')
|
|
|
|
|
|
- message_text += f"{status_emoji} <b>{status.upper()}</b> ({len(trades)} trades):\n"
|
|
|
+ # Position emoji and formatting
|
|
|
+ if position_side == 'long':
|
|
|
+ pos_emoji = "🟢"
|
|
|
+ direction = "LONG"
|
|
|
+ else: # Short position
|
|
|
+ pos_emoji = "🔴"
|
|
|
+ direction = "SHORT"
|
|
|
|
|
|
- for trade in trades[:5]: # Limit to 5 per status to avoid long messages
|
|
|
- symbol = trade['symbol']
|
|
|
- token = symbol.split('/')[0] if '/' in symbol else symbol
|
|
|
- side = trade['side'].upper()
|
|
|
- entry_price = trade.get('entry_price')
|
|
|
- entry_amount = trade.get('entry_amount')
|
|
|
- realized_pnl = trade.get('realized_pnl', 0)
|
|
|
-
|
|
|
- message_text += f" • {side} {token}"
|
|
|
-
|
|
|
- if entry_price and entry_amount:
|
|
|
- message_text += f" | {entry_amount:.6f} @ ${entry_price:.2f}"
|
|
|
-
|
|
|
- if status == 'closed' and realized_pnl != 0:
|
|
|
- pnl_emoji = "🟢" if realized_pnl >= 0 else "🔴"
|
|
|
- message_text += f" | {pnl_emoji} ${realized_pnl:.2f}"
|
|
|
-
|
|
|
- if trade.get('stop_loss_price'):
|
|
|
- message_text += f" | SL: ${trade['stop_loss_price']:.2f}"
|
|
|
-
|
|
|
- message_text += "\n"
|
|
|
+ # Trade type indicator
|
|
|
+ type_indicator = ""
|
|
|
+ if trade_type == 'external':
|
|
|
+ type_indicator = " 🔄" # External/synced position
|
|
|
+ elif trade_type == 'bot':
|
|
|
+ type_indicator = " 🤖" # Bot-created position
|
|
|
|
|
|
- if len(trades) > 5:
|
|
|
- message_text += f" ... and {len(trades) - 5} more\n"
|
|
|
-
|
|
|
- message_text += "\n"
|
|
|
+ message_text += f"{pos_emoji} <b>{token} ({direction}){type_indicator}</b>\n"
|
|
|
+ message_text += f" 📏 Size: {abs(current_amount):.6f} {token}\n"
|
|
|
+ message_text += f" 💰 Entry: ${entry_price:.4f}\n"
|
|
|
+
|
|
|
+ # Show stop loss if linked
|
|
|
+ if position.get('stop_loss_price'):
|
|
|
+ sl_price = position['stop_loss_price']
|
|
|
+ sl_status = "Pending" if not position.get('stop_loss_order_id') else "Active"
|
|
|
+ message_text += f" 🛑 Stop Loss: ${sl_price:.4f} ({sl_status})\n"
|
|
|
+
|
|
|
+ # Show take profit if linked
|
|
|
+ if position.get('take_profit_price'):
|
|
|
+ tp_price = position['take_profit_price']
|
|
|
+ tp_status = "Pending" if not position.get('take_profit_order_id') else "Active"
|
|
|
+ message_text += f" 🎯 Take Profit: ${tp_price:.4f} ({tp_status})\n"
|
|
|
+
|
|
|
+ message_text += f" 🆔 Lifecycle ID: {position['trade_lifecycle_id'][:8]}\n\n"
|
|
|
|
|
|
# Add summary
|
|
|
- total_trades = len(all_active_trades)
|
|
|
- pending_count = len(active_trades_by_status.get('pending', []))
|
|
|
- active_count = len(active_trades_by_status.get('active', []))
|
|
|
- closed_count = len(active_trades_by_status.get('closed', []))
|
|
|
- cancelled_count = len(active_trades_by_status.get('cancelled', []))
|
|
|
+ total_positions = len(open_positions)
|
|
|
+ bot_positions = len([p for p in open_positions if p.get('trade_type') == 'bot'])
|
|
|
+ external_positions = len([p for p in open_positions if p.get('trade_type') == 'external'])
|
|
|
|
|
|
message_text += f"📈 <b>Summary:</b>\n"
|
|
|
- message_text += f" Total: {total_trades} | "
|
|
|
- message_text += f"Pending: {pending_count} | "
|
|
|
- message_text += f"Active: {active_count} | "
|
|
|
- message_text += f"Closed: {closed_count} | "
|
|
|
- message_text += f"Cancelled: {cancelled_count}\n\n"
|
|
|
+ message_text += f" Total: {total_positions} | "
|
|
|
+ message_text += f"Bot: {bot_positions} | "
|
|
|
+ message_text += f"External: {external_positions}\n\n"
|
|
|
|
|
|
- message_text += f"💡 This is Phase 1 testing - active trades run parallel to trade cycles"
|
|
|
+ message_text += f"🤖 <b>Legend:</b> 🤖 Bot-created • 🔄 External/synced\n"
|
|
|
+ message_text += f"💡 Use /positions for detailed P&L information"
|
|
|
|
|
|
await context.bot.send_message(chat_id=chat_id, text=message_text.strip(), parse_mode='HTML')
|
|
|
|
|
|
except Exception as e:
|
|
|
- error_message = f"❌ Error processing active trades command: {str(e)}"
|
|
|
+ error_message = f"❌ Error processing active positions command: {str(e)}"
|
|
|
await context.bot.send_message(chat_id=chat_id, text=error_message)
|
|
|
- logger.error(f"Error in active trades command: {e}")
|
|
|
+ logger.error(f"Error in active positions command: {e}")
|
|
|
|
|
|
async def market_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
|
"""Handle the /market command."""
|