|
@@ -17,139 +17,61 @@ class RiskCommands(InfoCommandsBase):
|
|
|
self.formatter = get_formatter()
|
|
|
|
|
|
async def risk_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
|
- """Handle the /risk command to show risk management information."""
|
|
|
+ """Handle the /risk command to show key portfolio risk metrics."""
|
|
|
try:
|
|
|
- # Get token from command arguments or use default
|
|
|
- token = context.args[0].upper() if context.args else Config.DEFAULT_TRADING_TOKEN
|
|
|
-
|
|
|
- # Get current positions and orders
|
|
|
+ # Check for unused arguments and notify user
|
|
|
+ if context.args:
|
|
|
+ await self._reply(update, "Note: The `/risk` command shows portfolio-level data. Token-specific arguments are ignored.")
|
|
|
+
|
|
|
+ # Fetch necessary data
|
|
|
+ risk_metrics = self.trading_engine.stats.get_risk_metrics()
|
|
|
positions = self.trading_engine.get_positions()
|
|
|
- orders = self.trading_engine.get_orders()
|
|
|
-
|
|
|
- # Get trading statistics
|
|
|
- stats = self.trading_engine.get_stats()
|
|
|
-
|
|
|
- # Calculate unrealized P&L for open positions
|
|
|
- total_unrealized_pnl = 0.0
|
|
|
- position_risks = []
|
|
|
-
|
|
|
- for position in positions:
|
|
|
- try:
|
|
|
- # Get position details
|
|
|
- symbol = position.get('symbol', '')
|
|
|
- size = float(position.get('size', 0) or 0)
|
|
|
- entry_price = float(position.get('entryPrice', 0) or 0)
|
|
|
- mark_price = float(position.get('markPrice', 0) or 0)
|
|
|
- liquidation_price = float(position.get('liquidationPrice', 0) or 0)
|
|
|
-
|
|
|
- if size == 0 or entry_price == 0:
|
|
|
- continue
|
|
|
-
|
|
|
- # Calculate position value and P&L
|
|
|
- position_value = abs(size * entry_price)
|
|
|
- unrealized_pnl = size * (mark_price - entry_price)
|
|
|
- total_unrealized_pnl += unrealized_pnl
|
|
|
-
|
|
|
- # Calculate risk metrics
|
|
|
- price_risk = abs((mark_price - entry_price) / entry_price * 100)
|
|
|
- liquidation_risk = abs((liquidation_price - mark_price) / mark_price * 100) if liquidation_price > 0 else 0
|
|
|
-
|
|
|
- # Find stop loss orders for this position
|
|
|
- stop_loss_orders = [o for o in orders if o.get('symbol') == symbol and o.get('type') == 'stop']
|
|
|
- stop_loss_risk = 0
|
|
|
- if stop_loss_orders:
|
|
|
- stop_price = float(stop_loss_orders[0].get('price', 0) or 0)
|
|
|
- if stop_price > 0:
|
|
|
- stop_loss_risk = abs((stop_price - mark_price) / mark_price * 100)
|
|
|
-
|
|
|
- position_risks.append({
|
|
|
- 'symbol': symbol,
|
|
|
- 'size': size,
|
|
|
- 'entry_price': entry_price,
|
|
|
- 'mark_price': mark_price,
|
|
|
- 'position_value': position_value,
|
|
|
- 'unrealized_pnl': unrealized_pnl,
|
|
|
- 'price_risk': price_risk,
|
|
|
- 'liquidation_risk': liquidation_risk,
|
|
|
- 'stop_loss_risk': stop_loss_risk
|
|
|
- })
|
|
|
- except (ValueError, TypeError) as e:
|
|
|
- logger.error(f"Error processing position: {e}")
|
|
|
- continue
|
|
|
-
|
|
|
- # Get portfolio value
|
|
|
balance_data = self.trading_engine.get_balance()
|
|
|
- portfolio_value = float(balance_data.get('total', {}).get('USDC', 0) or 0) if balance_data else 0.0
|
|
|
-
|
|
|
- # Calculate portfolio risk metrics
|
|
|
- portfolio_risk = (total_unrealized_pnl / portfolio_value * 100) if portfolio_value > 0 else 0
|
|
|
-
|
|
|
- # Format the message
|
|
|
- message = f"📊 <b>Risk Analysis for {token}</b>\n\n"
|
|
|
-
|
|
|
- # Add position risks
|
|
|
- if position_risks:
|
|
|
- message += "🔍 <b>Position Risks:</b>\n"
|
|
|
- for risk in position_risks:
|
|
|
- pnl_emoji = "🟢" if risk['unrealized_pnl'] >= 0 else "🔴"
|
|
|
- message += (
|
|
|
- f"\n<b>{risk['symbol']}</b>\n"
|
|
|
- f"Size: {await self.formatter.format_amount(risk['size'], risk['symbol'].split('/')[0])}\n"
|
|
|
- f"Entry: {await self.formatter.format_price_with_symbol(risk['entry_price'], risk['symbol'].split('/')[0])}\n"
|
|
|
- f"Mark: {await self.formatter.format_price_with_symbol(risk['mark_price'], risk['symbol'].split('/')[0])}\n"
|
|
|
- f"P&L: {pnl_emoji} {await self.formatter.format_price_with_symbol(risk['unrealized_pnl'], risk['symbol'].split('/')[0])}\n"
|
|
|
- f"Price Risk: {risk['price_risk']:.2f}%\n"
|
|
|
- f"Liquidation Risk: {risk['liquidation_risk']:.2f}%\n"
|
|
|
- f"Stop Loss Risk: {risk['stop_loss_risk']:.2f}%\n"
|
|
|
- )
|
|
|
- else:
|
|
|
- message += "No open positions\n"
|
|
|
-
|
|
|
- # Add portfolio summary
|
|
|
- message += f"\n📈 <b>Portfolio Summary:</b>\n"
|
|
|
- message += f"Total Value: {await self.formatter.format_price_with_symbol(portfolio_value)}\n"
|
|
|
- message += f"Unrealized P&L: {await self.formatter.format_price_with_symbol(total_unrealized_pnl)}\n"
|
|
|
- message += f"Portfolio Risk: {portfolio_risk:.2f}%\n"
|
|
|
-
|
|
|
- # Add trading statistics
|
|
|
- if stats:
|
|
|
- performance_stats = stats.get_performance_stats()
|
|
|
- message += f"\n📊 <b>Trading Statistics:</b>\n"
|
|
|
- message += f"Win Rate: {performance_stats.get('win_rate', 0):.2f}%\n"
|
|
|
- message += f"Profit Factor: {performance_stats.get('profit_factor', 0):.2f}\n"
|
|
|
- message += f"Average Win: {await self.formatter.format_price_with_symbol(performance_stats.get('average_win', 0))}\n"
|
|
|
- message += f"Average Loss: {await self.formatter.format_price_with_symbol(performance_stats.get('average_loss', 0))}\n"
|
|
|
-
|
|
|
- # Add risk metrics
|
|
|
- risk_metrics = self.trading_engine.stats.get_risk_metrics()
|
|
|
+
|
|
|
if not risk_metrics:
|
|
|
await self._reply(update, "Risk metrics are not available yet.")
|
|
|
return
|
|
|
+
|
|
|
+ # Calculate total unrealized P&L
|
|
|
+ total_unrealized_pnl = sum(float(p.get('unrealizedPnl', 0.0)) for p in positions)
|
|
|
+ portfolio_value = float(balance_data.get('total', {}).get('USDC', 0.0))
|
|
|
|
|
|
- if risk_metrics:
|
|
|
- message += f"\n�� <b>Risk Metrics:</b>\n"
|
|
|
- # Drawdown
|
|
|
- max_drawdown_pct = risk_metrics.get('max_drawdown_percentage', 0.0)
|
|
|
- drawdown_start_date_iso = risk_metrics.get('drawdown_start_date')
|
|
|
-
|
|
|
- drawdown_date_str = ""
|
|
|
- if drawdown_start_date_iso:
|
|
|
- try:
|
|
|
- drawdown_date = datetime.fromisoformat(drawdown_start_date_iso).strftime('%Y-%m-%d')
|
|
|
- drawdown_date_str = f" (since {drawdown_date})"
|
|
|
- except (ValueError, TypeError):
|
|
|
- logger.warning(f"Could not parse drawdown_start_date: {drawdown_start_date_iso}")
|
|
|
+ # Calculate portfolio risk percentage
|
|
|
+ portfolio_risk_pct = (total_unrealized_pnl / portfolio_value * 100) if portfolio_value > 0 else 0.0
|
|
|
|
|
|
- message += f"• Max Drawdown: {max_drawdown_pct:.2f}%{drawdown_date_str}\n"
|
|
|
+ # Format the message
|
|
|
+ message = [
|
|
|
+ "<b>🛡️ Portfolio Risk Analysis</b>\n",
|
|
|
+ f"Here are the key risk metrics for your portfolio:\n"
|
|
|
+ ]
|
|
|
|
|
|
- # Sharpe Ratio
|
|
|
- sharpe_ratio = risk_metrics.get('sharpe_ratio')
|
|
|
- if sharpe_ratio is not None:
|
|
|
- message += f"• Sharpe Ratio: {sharpe_ratio:.2f}\n"
|
|
|
- else:
|
|
|
- message += f"• Sharpe Ratio: N/A (Insufficient data)\n"
|
|
|
+ # Max Drawdown
|
|
|
+ max_drawdown_pct = risk_metrics.get('max_drawdown_percentage', 0.0)
|
|
|
+ drawdown_start_date_iso = risk_metrics.get('drawdown_start_date')
|
|
|
+ drawdown_date_str = ""
|
|
|
+ if drawdown_start_date_iso:
|
|
|
+ try:
|
|
|
+ drawdown_date = datetime.fromisoformat(drawdown_start_date_iso).strftime('%Y-%m-%d')
|
|
|
+ drawdown_date_str = f" (since {drawdown_date})"
|
|
|
+ except (ValueError, TypeError):
|
|
|
+ logger.warning(f"Could not parse drawdown_start_date: {drawdown_start_date_iso}")
|
|
|
+
|
|
|
+ message.append(f"• <b>Max Drawdown:</b> {max_drawdown_pct:.2f}%{drawdown_date_str}")
|
|
|
|
|
|
- await self._reply(update, message)
|
|
|
+ # Sharpe Ratio
|
|
|
+ sharpe_ratio = risk_metrics.get('sharpe_ratio')
|
|
|
+ if sharpe_ratio is not None:
|
|
|
+ message.append(f"• <b>Sharpe Ratio:</b> {sharpe_ratio:.2f}")
|
|
|
+ else:
|
|
|
+ message.append("• <b>Sharpe Ratio:</b> N/A (Insufficient data)")
|
|
|
+
|
|
|
+ # Overall Portfolio Risk
|
|
|
+ pnl_emoji = "🟢" if total_unrealized_pnl >= 0 else "🔴"
|
|
|
+ message.append(f"• <b>Open P&L Risk:</b> {pnl_emoji} {portfolio_risk_pct:.2f}% ({await self.formatter.format_price_with_symbol(total_unrealized_pnl)})")
|
|
|
+
|
|
|
+ message.append("\n<i>This provides a high-level overview of portfolio risk. For position-specific details, use /positions.</i>")
|
|
|
+
|
|
|
+ await self._reply(update, "\n".join(message))
|
|
|
|
|
|
except Exception as e:
|
|
|
logger.error(f"Error in risk command: {e}", exc_info=True)
|