|
@@ -1166,6 +1166,7 @@ Tap any button below for instant access to bot functions:
|
|
|
self.application.add_handler(CommandHandler("daily", self.daily_command))
|
|
|
self.application.add_handler(CommandHandler("weekly", self.weekly_command))
|
|
|
self.application.add_handler(CommandHandler("monthly", self.monthly_command))
|
|
|
+ self.application.add_handler(CommandHandler("risk", self.risk_command))
|
|
|
|
|
|
# Callback query handler for inline keyboards
|
|
|
self.application.add_handler(CallbackQueryHandler(self.button_callback))
|
|
@@ -2869,6 +2870,118 @@ Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
|
|
|
error_message = f"❌ Error processing monthly command: {str(e)}"
|
|
|
await update.message.reply_text(error_message)
|
|
|
logger.error(f"Error in monthly command: {e}")
|
|
|
+
|
|
|
+ async def risk_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
|
+ """Handle the /risk command to show advanced risk metrics."""
|
|
|
+ if not self.is_authorized(update.effective_chat.id):
|
|
|
+ await update.message.reply_text("❌ Unauthorized access.")
|
|
|
+ return
|
|
|
+
|
|
|
+ try:
|
|
|
+ # Get current balance for context
|
|
|
+ balance = self.client.get_balance()
|
|
|
+ current_balance = 0
|
|
|
+ if balance and balance.get('total'):
|
|
|
+ current_balance = float(balance['total'].get('USDC', 0))
|
|
|
+
|
|
|
+ # Get risk metrics and basic stats
|
|
|
+ risk_metrics = self.stats.get_risk_metrics()
|
|
|
+ basic_stats = self.stats.get_basic_stats()
|
|
|
+
|
|
|
+ # Check if we have enough data for risk calculations
|
|
|
+ if basic_stats['completed_trades'] < 2:
|
|
|
+ await update.message.reply_text(
|
|
|
+ "📊 <b>Risk Analysis</b>\n\n"
|
|
|
+ "📭 <b>Insufficient Data</b>\n\n"
|
|
|
+ f"• Current completed trades: {basic_stats['completed_trades']}\n"
|
|
|
+ f"• Required for risk analysis: 2+ trades\n"
|
|
|
+ f"• Daily balance snapshots: {len(self.stats.data.get('daily_balances', []))}\n\n"
|
|
|
+ "💡 <b>To enable risk analysis:</b>\n"
|
|
|
+ "• Complete more trades to generate returns data\n"
|
|
|
+ "• Bot automatically records daily balance snapshots\n"
|
|
|
+ "• Risk metrics will be available after sufficient trading history\n\n"
|
|
|
+ "📈 Use /stats for current performance metrics",
|
|
|
+ parse_mode='HTML'
|
|
|
+ )
|
|
|
+ return
|
|
|
+
|
|
|
+ # Format the risk analysis message
|
|
|
+ risk_text = f"""
|
|
|
+📊 <b>Risk Analysis & Advanced Metrics</b>
|
|
|
+
|
|
|
+🎯 <b>Risk-Adjusted Performance:</b>
|
|
|
+• Sharpe Ratio: {risk_metrics['sharpe_ratio']:.3f}
|
|
|
+• Sortino Ratio: {risk_metrics['sortino_ratio']:.3f}
|
|
|
+• Annual Volatility: {risk_metrics['volatility']:.2f}%
|
|
|
+
|
|
|
+📉 <b>Drawdown Analysis:</b>
|
|
|
+• Maximum Drawdown: {risk_metrics['max_drawdown']:.2f}%
|
|
|
+• Value at Risk (95%): {risk_metrics['var_95']:.2f}%
|
|
|
+
|
|
|
+💰 <b>Portfolio Context:</b>
|
|
|
+• Current Balance: ${current_balance:,.2f}
|
|
|
+• Initial Balance: ${basic_stats['initial_balance']:,.2f}
|
|
|
+• Total P&L: ${basic_stats['total_pnl']:,.2f}
|
|
|
+• Days Active: {basic_stats['days_active']}
|
|
|
+
|
|
|
+📊 <b>Risk Interpretation:</b>
|
|
|
+"""
|
|
|
+
|
|
|
+ # Add interpretive guidance
|
|
|
+ sharpe = risk_metrics['sharpe_ratio']
|
|
|
+ if sharpe > 2.0:
|
|
|
+ risk_text += "• 🟢 <b>Excellent</b> risk-adjusted returns (Sharpe > 2.0)\n"
|
|
|
+ elif sharpe > 1.0:
|
|
|
+ risk_text += "• 🟡 <b>Good</b> risk-adjusted returns (Sharpe > 1.0)\n"
|
|
|
+ elif sharpe > 0.5:
|
|
|
+ risk_text += "• 🟠 <b>Moderate</b> risk-adjusted returns (Sharpe > 0.5)\n"
|
|
|
+ elif sharpe > 0:
|
|
|
+ risk_text += "• 🔴 <b>Poor</b> risk-adjusted returns (Sharpe > 0)\n"
|
|
|
+ else:
|
|
|
+ risk_text += "• ⚫ <b>Negative</b> risk-adjusted returns (Sharpe < 0)\n"
|
|
|
+
|
|
|
+ max_dd = risk_metrics['max_drawdown']
|
|
|
+ if max_dd < 5:
|
|
|
+ risk_text += "• 🟢 <b>Low</b> maximum drawdown (< 5%)\n"
|
|
|
+ elif max_dd < 15:
|
|
|
+ risk_text += "• 🟡 <b>Moderate</b> maximum drawdown (< 15%)\n"
|
|
|
+ elif max_dd < 30:
|
|
|
+ risk_text += "• 🟠 <b>High</b> maximum drawdown (< 30%)\n"
|
|
|
+ else:
|
|
|
+ risk_text += "• 🔴 <b>Very High</b> maximum drawdown (> 30%)\n"
|
|
|
+
|
|
|
+ volatility = risk_metrics['volatility']
|
|
|
+ if volatility < 10:
|
|
|
+ risk_text += "• 🟢 <b>Low</b> portfolio volatility (< 10%)\n"
|
|
|
+ elif volatility < 25:
|
|
|
+ risk_text += "• 🟡 <b>Moderate</b> portfolio volatility (< 25%)\n"
|
|
|
+ elif volatility < 50:
|
|
|
+ risk_text += "• 🟠 <b>High</b> portfolio volatility (< 50%)\n"
|
|
|
+ else:
|
|
|
+ risk_text += "• 🔴 <b>Very High</b> portfolio volatility (> 50%)\n"
|
|
|
+
|
|
|
+ risk_text += f"""
|
|
|
+💡 <b>Risk Definitions:</b>
|
|
|
+• <b>Sharpe Ratio:</b> Risk-adjusted return (excess return / volatility)
|
|
|
+• <b>Sortino Ratio:</b> Return / downside volatility (focuses on bad volatility)
|
|
|
+• <b>Max Drawdown:</b> Largest peak-to-trough decline
|
|
|
+• <b>VaR 95%:</b> Maximum expected loss 95% of the time
|
|
|
+• <b>Volatility:</b> Annualized standard deviation of returns
|
|
|
+
|
|
|
+📈 <b>Data Based On:</b>
|
|
|
+• Completed Trades: {basic_stats['completed_trades']}
|
|
|
+• Daily Balance Records: {len(self.stats.data.get('daily_balances', []))}
|
|
|
+• Trading Period: {basic_stats['days_active']} days
|
|
|
+
|
|
|
+🔄 Use /stats for trading performance metrics
|
|
|
+ """
|
|
|
+
|
|
|
+ await update.message.reply_text(risk_text.strip(), parse_mode='HTML')
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ error_message = f"❌ Error processing risk command: {str(e)}"
|
|
|
+ await update.message.reply_text(error_message)
|
|
|
+ logger.error(f"Error in risk command: {e}")
|
|
|
|
|
|
def _get_position_state(self, symbol: str) -> Dict[str, Any]:
|
|
|
"""Get current position state for a symbol."""
|