|
@@ -143,11 +143,46 @@ class RiskCleanupManager:
|
|
|
if contracts == 0 or entry_price <= 0 or mark_price <= 0:
|
|
|
continue
|
|
|
|
|
|
- entry_value = abs(contracts) * entry_price
|
|
|
- if entry_value <= 0:
|
|
|
- continue
|
|
|
+ # Calculate ROE (Return on Equity) percentage instead of standard PnL percentage
|
|
|
+ # ROE is what the user sees in the Hyperliquid UI and represents actual return on cash invested
|
|
|
+ roe_percentage = 0.0
|
|
|
+
|
|
|
+ # Try to get ROE directly from exchange
|
|
|
+ info_data = position.get('info', {})
|
|
|
+ position_info = info_data.get('position', {})
|
|
|
+ roe_raw = position_info.get('returnOnEquity')
|
|
|
+
|
|
|
+ if roe_raw is not None:
|
|
|
+ try:
|
|
|
+ roe_percentage = float(roe_raw) * 100 # Convert to percentage
|
|
|
+ except (ValueError, TypeError):
|
|
|
+ logger.warning(f"Could not parse ROE value: {roe_raw} for {symbol}")
|
|
|
+ roe_percentage = 0.0
|
|
|
+
|
|
|
+ # Fallback to calculating ROE if not available from exchange
|
|
|
+ if roe_percentage == 0.0 and unrealized_pnl != 0:
|
|
|
+ # Calculate equity used: For leveraged positions, equity is less than margin
|
|
|
+ # We can estimate it from the margin and leverage
|
|
|
+ margin_used = position.get('marginUsed') or position.get('initialMargin')
|
|
|
+ leverage = position.get('leverage', 1.0)
|
|
|
|
|
|
- pnl_percentage = (unrealized_pnl / entry_value) * 100
|
|
|
+ if margin_used and leverage and leverage > 0:
|
|
|
+ try:
|
|
|
+ equity_used = float(margin_used) / float(leverage)
|
|
|
+ if equity_used > 0:
|
|
|
+ roe_percentage = (unrealized_pnl / equity_used) * 100
|
|
|
+ logger.debug(f"Calculated ROE for {symbol}: {roe_percentage:.2f}% (margin: ${margin_used}, leverage: {leverage}x, equity: ${equity_used})")
|
|
|
+ except (ValueError, TypeError, ZeroDivisionError):
|
|
|
+ pass
|
|
|
+
|
|
|
+ # Final fallback to standard percentage if ROE calculation fails
|
|
|
+ if roe_percentage == 0.0 and unrealized_pnl != 0:
|
|
|
+ entry_value = abs(contracts) * entry_price
|
|
|
+ if entry_value > 0:
|
|
|
+ roe_percentage = (unrealized_pnl / entry_value) * 100
|
|
|
+ logger.debug(f"Using standard PnL calculation for {symbol} as ROE fallback: {roe_percentage:.2f}%")
|
|
|
+
|
|
|
+ pnl_percentage = roe_percentage # Use ROE as the percentage for stop loss comparison
|
|
|
|
|
|
if pnl_percentage <= -Config.STOP_LOSS_PERCENTAGE:
|
|
|
token = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
|
|
@@ -159,7 +194,7 @@ class RiskCleanupManager:
|
|
|
if active_trade_lc:
|
|
|
lifecycle_id_str = active_trade_lc.get('trade_lifecycle_id', "N/A")[:8] + "..."
|
|
|
|
|
|
- logger.warning(f"🚨 AUTOMATIC STOP LOSS TRIGGERED: {token} {position_side} position (Lifecycle: {lifecycle_id_str}) has {pnl_percentage:.2f}% loss (threshold: -{Config.STOP_LOSS_PERCENTAGE}%)")
|
|
|
+ logger.warning(f"🚨 AUTOMATIC STOP LOSS TRIGGERED: {token} {position_side} position (Lifecycle: {lifecycle_id_str}) has {pnl_percentage:.2f}% ROE loss (threshold: -{Config.STOP_LOSS_PERCENTAGE}%)")
|
|
|
|
|
|
if self.notification_manager:
|
|
|
await self.notification_manager.send_generic_notification(
|
|
@@ -169,8 +204,8 @@ Lifecycle ID: {lifecycle_id_str}\\n
|
|
|
Position: {position_side} {abs(contracts):.6f}\\n
|
|
|
Entry Price: ${entry_price:.4f}\\n
|
|
|
Current Price: ${mark_price:.4f}\\n
|
|
|
-Unrealized P&L: ${unrealized_pnl:.2f} ({pnl_percentage:.2f}%)\\n
|
|
|
-Safety Threshold: -{Config.STOP_LOSS_PERCENTAGE}%\\n
|
|
|
+Unrealized P&L: ${unrealized_pnl:.2f} ({pnl_percentage:.2f}% ROE)\\n
|
|
|
+Safety Threshold: -{Config.STOP_LOSS_PERCENTAGE}% ROE\\n
|
|
|
Action: Executing emergency exit order..."""
|
|
|
)
|
|
|
|
|
@@ -192,8 +227,8 @@ Action: Executing emergency exit order..."""
|
|
|
f"""✅ <b>Emergency Exit Initiated</b>\\n\\n
|
|
|
📊 <b>Position:</b> {token} {position_side}\\n
|
|
|
🆔 <b>Lifecycle ID:</b> {lifecycle_id_str}\\n
|
|
|
-📉 <b>Loss at Trigger:</b> {pnl_percentage:.2f}% (${unrealized_pnl:.2f})\\n
|
|
|
-⚠️ <b>Threshold:</b> -{Config.STOP_LOSS_PERCENTAGE}%\\n
|
|
|
+📉 <b>Loss at Trigger:</b> {pnl_percentage:.2f}% ROE (${unrealized_pnl:.2f})\\n
|
|
|
+⚠️ <b>Threshold:</b> -{Config.STOP_LOSS_PERCENTAGE}% ROE\\n
|
|
|
✅ <b>Action:</b> Market exit order placed successfully\\n
|
|
|
🆔 <b>Exit Order ID:</b> {placed_order_details.get('exchange_order_id', 'N/A')}\\n
|
|
|
{f'🛑 <b>Cleanup:</b> Cancelled {cancelled_sl_count} other pending stop losses' if cancelled_sl_count > 0 else ''}
|
|
@@ -208,7 +243,7 @@ Action: Executing emergency exit order..."""
|
|
|
f"""❌ <b>CRITICAL: Emergency Exit Failed!</b>\\n\\n
|
|
|
📊 <b>Position:</b> {token} {position_side}\\n
|
|
|
🆔 <b>Lifecycle ID:</b> {lifecycle_id_str}\\n
|
|
|
-📉 <b>Loss:</b> {pnl_percentage:.2f}%\\n
|
|
|
+📉 <b>Loss:</b> {pnl_percentage:.2f}% ROE\\n
|
|
|
❌ <b>Error Placing Order:</b> {error_msg}\\n\\n
|
|
|
⚠️ <b>MANUAL INTERVENTION REQUIRED</b>\\n
|
|
|
Please close this position manually via /exit {token}"""
|