import logging import html from telegram import Update from telegram.ext import ContextTypes from .base import InfoCommandsBase from src.config.config import Config from src.utils.token_display_formatter import get_formatter from datetime import datetime logger = logging.getLogger(__name__) class RiskCommands(InfoCommandsBase): """Handles all risk management-related commands.""" def __init__(self, trading_engine, notification_manager): super().__init__(trading_engine, notification_manager) self.formatter = get_formatter() async def risk_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """Handle the /risk command to show a comprehensive portfolio risk report.""" try: if context.args: await self._reply(update, "Note: The `/risk` command shows portfolio-level data and does not accept arguments.") message = ["🛡️ Portfolio Risk Analysis\n"] # 1. Open Positions positions = self.trading_engine.get_positions() message.append(self._format_open_positions(positions)) # 2. Portfolio Summary balance_data = self.trading_engine.get_balance() message.append(await self._format_portfolio_summary(balance_data, positions)) # 3. Trading Stats stats = self.trading_engine.stats.get_trading_stats() message.append(self._format_trading_stats(stats)) # 4. Risk Metrics risk_metrics = self.trading_engine.stats.get_risk_metrics() message.append(self._format_risk_metrics(risk_metrics)) # 5. Footer message.append("This report provides a snapshot of your portfolio's risk. Manage positions carefully.") await self._reply(update, "\n".join(message)) except Exception as e: logger.error(f"Error in risk command: {e}", exc_info=True) await self._reply(update, "❌ Error generating risk report. Please try again later.") def _format_open_positions(self, positions): """Formats the open positions section.""" if not positions: return "Open Positions: None\n" lines = ["Open Positions:"] for p in positions: try: position_info = p.get('position', p) pnl = float(p.get('unrealizedPnl', 0.0)) pnl_emoji = "🟢" if pnl >= 0 else "🔴" # Use .get() with defaults to avoid KeyErrors asset = position_info.get('asset', 'N/A') size = position_info.get('szi', 'N/A') side = position_info.get('side', 'N/A') entry_price = position_info.get('entryPx', 'N/A') lines.append(f"• {asset}: {size} {side} @ ${entry_price} {pnl_emoji}") except Exception as e: logger.error(f"Error formatting a position: {p}, error: {e}") lines.append("• Error displaying one position.") return "\n".join(lines) + "\n" async def _format_portfolio_summary(self, balance_data, positions): """Formats the portfolio summary section.""" 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)) pnl_emoji = "🟢" if total_unrealized_pnl >= 0 else "🔴" return ( "Portfolio Summary:\n" f"• Total Value: {await self.formatter.format_price_with_symbol(portfolio_value)}\n" f"• Unrealized P&L: {pnl_emoji} {await self.formatter.format_price_with_symbol(total_unrealized_pnl)}\n" ) def _format_trading_stats(self, stats): """Formats the trading statistics section.""" if not stats: return "Trading Stats: Not available\n" return ( "Trading Stats:\n" f"• Win Rate: {stats.get('win_rate', 0.0):.2f}%\n" f"• Profit Factor: {stats.get('profit_factor', 0.0):.2f}\n" ) def _format_risk_metrics(self, risk_metrics): """Formats the risk metrics section.""" if not risk_metrics: return "Risk Metrics: Not available\n" 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}") sharpe_ratio = risk_metrics.get('sharpe_ratio') sharpe_str = f"{sharpe_ratio:.2f}" if sharpe_ratio is not None else "N/A" return ( "Risk Metrics:\n" f"• Max Drawdown: {max_drawdown_pct:.2f}%{drawdown_date_str}\n" f"• Sharpe Ratio: {sharpe_str}\n" )