|
@@ -222,7 +222,7 @@ class PositionMonitor:
|
|
roe_text = ""
|
|
roe_text = ""
|
|
|
|
|
|
message = f"""
|
|
message = f"""
|
|
-🎯 <b>Position Closed (External)</b>
|
|
|
|
|
|
+🎯 <b>Position Closed</b>
|
|
|
|
|
|
📊 <b>Trade Details:</b>
|
|
📊 <b>Trade Details:</b>
|
|
• Token: {token}
|
|
• Token: {token}
|
|
@@ -621,9 +621,11 @@ class PositionMonitor:
|
|
logger.info(f"{pnl_emoji} Lifecycle CLOSED: {lc_id} ({closure_reason_action_type}). PNL for fill: {await formatter.format_price_with_symbol(realized_pnl)}")
|
|
logger.info(f"{pnl_emoji} Lifecycle CLOSED: {lc_id} ({closure_reason_action_type}). PNL for fill: {await formatter.format_price_with_symbol(realized_pnl)}")
|
|
symbols_with_fills.add(token)
|
|
symbols_with_fills.add(token)
|
|
|
|
|
|
- # For bot exits, don't send "External" notification here - let reconciliation handle it
|
|
|
|
- # to avoid duplicate notifications
|
|
|
|
- logger.info(f"ℹ️ Bot exit processed for {full_symbol}. Reconciliation will handle position closed notification.")
|
|
|
|
|
|
+ # Send notification immediately with correct PnL from actual fill
|
|
|
|
+ await self._send_position_change_notification(
|
|
|
|
+ full_symbol, side_from_fill, amount_from_fill, price_from_fill,
|
|
|
|
+ 'position_closed', timestamp_dt, active_lc, realized_pnl
|
|
|
|
+ )
|
|
|
|
|
|
stats.migrate_trade_to_aggregated_stats(lc_id)
|
|
stats.migrate_trade_to_aggregated_stats(lc_id)
|
|
if bot_order_db_id_to_update:
|
|
if bot_order_db_id_to_update:
|
|
@@ -1018,14 +1020,26 @@ class PositionMonitor:
|
|
logger.info(f"ℹ️ Position for {symbol} already marked as closed in lifecycle {lifecycle_id[:8]}. Skipping duplicate close processing.")
|
|
logger.info(f"ℹ️ Position for {symbol} already marked as closed in lifecycle {lifecycle_id[:8]}. Skipping duplicate close processing.")
|
|
return
|
|
return
|
|
|
|
|
|
|
|
+ # Check if this was already migrated to aggregated stats (bot exit already processed it)
|
|
|
|
+ try:
|
|
|
|
+ if not stats.get_trade_by_lifecycle_id(lifecycle_id):
|
|
|
|
+ logger.info(f"ℹ️ Lifecycle {lifecycle_id[:8]} for {symbol} already migrated to aggregated stats. Bot exit already handled this closure.")
|
|
|
|
+ return
|
|
|
|
+ except Exception:
|
|
|
|
+ # If we can't check, proceed with caution
|
|
|
|
+ pass
|
|
|
|
+
|
|
# Estimate exit price from market data
|
|
# Estimate exit price from market data
|
|
exit_price = 0
|
|
exit_price = 0
|
|
try:
|
|
try:
|
|
- market_data = await self.trading_engine.get_symbol_data(symbol)
|
|
|
|
- if market_data and 'markPrice' in market_data:
|
|
|
|
- exit_price = float(market_data['markPrice'])
|
|
|
|
|
|
+ market_data = self.trading_engine.get_market_data(symbol)
|
|
|
|
+ if market_data and market_data.get('ticker'):
|
|
|
|
+ exit_price = float(market_data['ticker'].get('last', 0))
|
|
|
|
+ if exit_price <= 0:
|
|
|
|
+ logger.warning(f"⚠️ Invalid market price for {symbol} - using entry price")
|
|
|
|
+ exit_price = entry_price
|
|
else:
|
|
else:
|
|
- logger.warning(f"⚠️ Could not get exit price for {symbol} - using entry price")
|
|
|
|
|
|
+ logger.warning(f"⚠️ Could not get market data for {symbol} - using entry price")
|
|
exit_price = entry_price
|
|
exit_price = entry_price
|
|
except Exception as e:
|
|
except Exception as e:
|
|
logger.warning(f"⚠️ Error fetching market data for {symbol}: {e} - using entry price")
|
|
logger.warning(f"⚠️ Error fetching market data for {symbol}: {e} - using entry price")
|