|
@@ -167,6 +167,10 @@ class InfoCommands:
|
|
|
total_unrealized = 0
|
|
|
total_position_value = 0
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
for position_trade in open_positions:
|
|
|
symbol = position_trade['symbol']
|
|
|
|
|
@@ -179,6 +183,33 @@ class InfoCommands:
|
|
|
abs_current_amount = abs(current_amount)
|
|
|
trade_type = position_trade.get('trade_type', 'manual')
|
|
|
|
|
|
+
|
|
|
+ position_opened_at_str = position_trade.get('position_opened_at')
|
|
|
+ duration_str = "N/A"
|
|
|
+ if position_opened_at_str:
|
|
|
+ try:
|
|
|
+ opened_at_dt = datetime.fromisoformat(position_opened_at_str)
|
|
|
+ if opened_at_dt.tzinfo is None:
|
|
|
+ opened_at_dt = opened_at_dt.replace(tzinfo=timezone.utc)
|
|
|
+ now_utc = datetime.now(timezone.utc)
|
|
|
+ duration = now_utc - opened_at_dt
|
|
|
+
|
|
|
+ days = duration.days
|
|
|
+ hours, remainder = divmod(duration.seconds, 3600)
|
|
|
+ minutes, _ = divmod(remainder, 60)
|
|
|
+
|
|
|
+ parts = []
|
|
|
+ if days > 0:
|
|
|
+ parts.append(f"{days}d")
|
|
|
+ if hours > 0:
|
|
|
+ parts.append(f"{hours}h")
|
|
|
+ if minutes > 0 or (days == 0 and hours == 0):
|
|
|
+ parts.append(f"{minutes}m")
|
|
|
+ duration_str = " ".join(parts) if parts else "0m"
|
|
|
+ except ValueError:
|
|
|
+ logger.warning(f"Could not parse position_opened_at: {position_opened_at_str} for {symbol}")
|
|
|
+ duration_str = "Error"
|
|
|
+
|
|
|
mark_price = position_trade.get('mark_price', entry_price)
|
|
|
|
|
|
|
|
@@ -256,6 +287,7 @@ class InfoCommands:
|
|
|
positions_text += f"{pos_emoji} <b>{base_asset} ({direction_text}){type_indicator}</b>\n"
|
|
|
positions_text += f" 📏 Size: {size_str} {base_asset}\n"
|
|
|
positions_text += f" 💰 Entry: {entry_price_str}\n"
|
|
|
+ positions_text += f" ⏳ Duration: {duration_str}\n"
|
|
|
|
|
|
|
|
|
positions_text += f" 🏦 Value: ${individual_position_value:,.2f}\n"
|
|
@@ -285,6 +317,61 @@ class InfoCommands:
|
|
|
tp_status = "Pending" if not position_trade.get('take_profit_order_id') else "Active"
|
|
|
positions_text += f" 🎯 Take Profit: {formatter.format_price_with_symbol(tp_price, base_asset)} ({tp_status})\n"
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ temp_all_exchange_orders = self.trading_engine.get_orders() or []
|
|
|
+
|
|
|
+
|
|
|
+ if temp_all_exchange_orders:
|
|
|
+ unlinked_sl_display = []
|
|
|
+ unlinked_tp_display = []
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ for ex_order in temp_all_exchange_orders:
|
|
|
+ if ex_order.get('symbol') == symbol:
|
|
|
+ ex_order_id = ex_order.get('id')
|
|
|
+
|
|
|
+ if ex_order_id == position_trade.get('stop_loss_order_id') or \
|
|
|
+ ex_order_id == position_trade.get('take_profit_order_id'):
|
|
|
+ continue
|
|
|
+
|
|
|
+ order_price = float(ex_order.get('price', 0))
|
|
|
+ order_side = ex_order.get('side', '').lower()
|
|
|
+
|
|
|
+ if position_side == 'long' and order_side == 'sell':
|
|
|
+ if order_price < entry_price:
|
|
|
+ unlinked_sl_display.append(f"SELL @ {formatter.format_price_with_symbol(order_price, base_asset)}")
|
|
|
+ elif order_price > entry_price:
|
|
|
+ unlinked_tp_display.append(f"SELL @ {formatter.format_price_with_symbol(order_price, base_asset)}")
|
|
|
+ elif position_side == 'short' and order_side == 'buy':
|
|
|
+ if order_price > entry_price:
|
|
|
+ unlinked_sl_display.append(f"BUY @ {formatter.format_price_with_symbol(order_price, base_asset)}")
|
|
|
+ elif order_price < entry_price:
|
|
|
+ unlinked_tp_display.append(f"BUY @ {formatter.format_price_with_symbol(order_price, base_asset)}")
|
|
|
+
|
|
|
+ if unlinked_sl_display:
|
|
|
+ positions_text += f" ⚠️ Unlinked SL(s)?: {', '.join(unlinked_sl_display)}\n"
|
|
|
+ if unlinked_tp_display:
|
|
|
+ positions_text += f" 🎯 Unlinked TP(s)?: {', '.join(unlinked_tp_display)}\n"
|
|
|
+
|
|
|
+
|
|
|
positions_text += f" 🆔 Lifecycle ID: {position_trade['trade_lifecycle_id'][:8]}\n\n"
|
|
|
|
|
|
|
|
@@ -310,6 +397,10 @@ class InfoCommands:
|
|
|
if not self._is_authorized(update):
|
|
|
return
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
reply_method = None
|
|
|
if update.callback_query:
|
|
|
reply_method = update.callback_query.message.reply_text
|