|
@@ -167,6 +167,9 @@ class InfoCommands:
|
|
total_position_value = 0
|
|
total_position_value = 0
|
|
total_margin_used = 0
|
|
total_margin_used = 0
|
|
|
|
|
|
|
|
+ # Get exchange orders to detect external stop losses
|
|
|
|
+ exchange_orders = self.trading_engine.get_orders() or []
|
|
|
|
+
|
|
for position_trade in open_positions:
|
|
for position_trade in open_positions:
|
|
symbol = position_trade['symbol']
|
|
symbol = position_trade['symbol']
|
|
# base_asset is the asset being traded, quote_asset is the settlement currency (usually USDC)
|
|
# base_asset is the asset being traded, quote_asset is the settlement currency (usually USDC)
|
|
@@ -304,17 +307,67 @@ class InfoCommands:
|
|
liq_price_str = formatter.format_price_with_symbol(position_trade.get('liquidation_price'), base_asset)
|
|
liq_price_str = formatter.format_price_with_symbol(position_trade.get('liquidation_price'), base_asset)
|
|
positions_text += f" ⚠️ Liquidation: {liq_price_str}\n"
|
|
positions_text += f" ⚠️ Liquidation: {liq_price_str}\n"
|
|
|
|
|
|
- # Show stop loss if linked
|
|
|
|
|
|
+ # Show stop loss if linked in database
|
|
if position_trade.get('stop_loss_price'):
|
|
if position_trade.get('stop_loss_price'):
|
|
sl_price = position_trade['stop_loss_price']
|
|
sl_price = position_trade['stop_loss_price']
|
|
positions_text += f" 🛑 Stop Loss: {formatter.format_price_with_symbol(sl_price, base_asset)}\n"
|
|
positions_text += f" 🛑 Stop Loss: {formatter.format_price_with_symbol(sl_price, base_asset)}\n"
|
|
|
|
|
|
- # Show take profit if linked
|
|
|
|
|
|
+ # Show take profit if linked in database
|
|
if position_trade.get('take_profit_price'):
|
|
if position_trade.get('take_profit_price'):
|
|
tp_price = position_trade['take_profit_price']
|
|
tp_price = position_trade['take_profit_price']
|
|
positions_text += f" 🎯 Take Profit: {formatter.format_price_with_symbol(tp_price, base_asset)}\n"
|
|
positions_text += f" 🎯 Take Profit: {formatter.format_price_with_symbol(tp_price, base_asset)}\n"
|
|
|
|
|
|
-
|
|
|
|
|
|
+ # Detect external/unlinked stop losses for this position
|
|
|
|
+ external_sls = []
|
|
|
|
+ for order in exchange_orders:
|
|
|
|
+ try:
|
|
|
|
+ order_symbol = order.get('symbol')
|
|
|
|
+ order_side = order.get('side', '').lower()
|
|
|
|
+ order_type = order.get('type', '').lower()
|
|
|
|
+ order_price = float(order.get('price', 0))
|
|
|
|
+ trigger_price = order.get('info', {}).get('triggerPrice')
|
|
|
|
+ is_reduce_only = order.get('reduceOnly', False) or order.get('info', {}).get('reduceOnly', False)
|
|
|
|
+ order_amount = float(order.get('amount', 0))
|
|
|
|
+
|
|
|
|
+ # Check if this order could be a stop loss for this position
|
|
|
|
+ if (order_symbol == symbol and is_reduce_only and
|
|
|
|
+ abs(order_amount - abs_current_amount) < 0.01 * abs_current_amount):
|
|
|
|
+
|
|
|
|
+ # Get trigger price (prefer triggerPrice over regular price)
|
|
|
|
+ sl_trigger_price = 0
|
|
|
|
+ if trigger_price:
|
|
|
|
+ try:
|
|
|
|
+ sl_trigger_price = float(trigger_price)
|
|
|
|
+ except (ValueError, TypeError):
|
|
|
|
+ pass
|
|
|
|
+ if not sl_trigger_price and order_price > 0:
|
|
|
|
+ sl_trigger_price = order_price
|
|
|
|
+
|
|
|
|
+ # Check if it's positioned correctly relative to entry price
|
|
|
|
+ is_valid_sl = False
|
|
|
|
+ if position_side == 'long' and order_side == 'sell':
|
|
|
|
+ # Long position: SL should be sell order below entry price
|
|
|
|
+ if sl_trigger_price > 0 and sl_trigger_price < entry_price:
|
|
|
|
+ is_valid_sl = True
|
|
|
|
+ elif position_side == 'short' and order_side == 'buy':
|
|
|
|
+ # Short position: SL should be buy order above entry price
|
|
|
|
+ if sl_trigger_price > 0 and sl_trigger_price > entry_price:
|
|
|
|
+ is_valid_sl = True
|
|
|
|
+
|
|
|
|
+ # Check if it's already linked in database
|
|
|
|
+ linked_sl_order_id = position_trade.get('stop_loss_order_id')
|
|
|
|
+ is_already_linked = (linked_sl_order_id and linked_sl_order_id == order.get('id'))
|
|
|
|
+
|
|
|
|
+ if is_valid_sl and not is_already_linked:
|
|
|
|
+ external_sls.append(sl_trigger_price)
|
|
|
|
+
|
|
|
|
+ except Exception as order_err:
|
|
|
|
+ # Skip problematic orders
|
|
|
|
+ continue
|
|
|
|
+
|
|
|
|
+ # Show external stop losses (unlinked)
|
|
|
|
+ for ext_sl_price in external_sls:
|
|
|
|
+ positions_text += f" 🛡️ External SL: {formatter.format_price_with_symbol(ext_sl_price, base_asset)}\n"
|
|
|
|
|
|
positions_text += f" 🆔 Lifecycle ID: {position_trade['trade_lifecycle_id'][:8]}\n\n"
|
|
positions_text += f" 🆔 Lifecycle ID: {position_trade['trade_lifecycle_id'][:8]}\n\n"
|
|
|
|
|
|
@@ -331,7 +384,7 @@ class InfoCommands:
|
|
margin_pnl_percentage = (total_unrealized / total_margin_used) * 100
|
|
margin_pnl_percentage = (total_unrealized / total_margin_used) * 100
|
|
positions_text += f" 📊 Portfolio Return: {margin_pnl_percentage:+.2f}% (on margin)\n"
|
|
positions_text += f" 📊 Portfolio Return: {margin_pnl_percentage:+.2f}% (on margin)\n"
|
|
positions_text += "\n"
|
|
positions_text += "\n"
|
|
- positions_text += f"🤖 <b>Legend:</b> 🤖 Bot-created • 🔄 External/synced\n"
|
|
|
|
|
|
+ positions_text += f"🤖 <b>Legend:</b> 🤖 Bot-created • 🔄 External/synced • 🛡️ External SL\n"
|
|
positions_text += f"💡 Use /sl [token] [price] or /tp [token] [price] to set risk management"
|
|
positions_text += f"💡 Use /sl [token] [price] or /tp [token] [price] to set risk management"
|
|
|
|
|
|
else:
|
|
else:
|