trades.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import logging
  2. from typing import Dict, Any, List, Optional
  3. from datetime import datetime, timedelta
  4. from telegram import Update
  5. from telegram.ext import ContextTypes
  6. from .base import InfoCommandsBase
  7. logger = logging.getLogger(__name__)
  8. class TradesCommands(InfoCommandsBase):
  9. """Handles all trade history-related commands."""
  10. async def trades_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  11. """Handle the /trades command."""
  12. try:
  13. if not self._is_authorized(update):
  14. await self._reply(update, "❌ Unauthorized access.")
  15. return
  16. stats = self.trading_engine.get_stats()
  17. if not stats:
  18. await self._reply(update, "❌ Trading stats not available.")
  19. return
  20. # Get recent trades
  21. recent_trades = stats.get_recent_trades()
  22. if not recent_trades:
  23. await self._reply(update, "📭 No recent trades")
  24. return
  25. # Format trades text
  26. trades_text = "📜 <b>Recent Trades</b>\n\n"
  27. for trade in recent_trades:
  28. try:
  29. # Defensive check to ensure 'trade' is a dictionary
  30. if not isinstance(trade, dict):
  31. logger.warning(f"Skipping non-dict item in recent_trades: {trade}")
  32. continue
  33. symbol = trade.get('symbol', 'unknown')
  34. base_asset = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
  35. side = trade.get('side', 'unknown').upper()
  36. price = float(trade.get('price', 0))
  37. amount = float(trade.get('amount', 0))
  38. pnl = float(trade.get('pnl', 0))
  39. pnl_percentage = float(trade.get('pnl_percentage', 0))
  40. trade_type = trade.get('trade_type', 'unknown')
  41. trade_time = trade.get('trade_time')
  42. # Format trade details
  43. formatter = self._get_formatter()
  44. price_str = await formatter.format_price_with_symbol(price, base_asset)
  45. amount_str = await formatter.format_amount(amount, base_asset)
  46. # Trade header
  47. side_emoji = "🟢" if side == "BUY" else "🔴"
  48. trades_text += f"{side_emoji} <b>{base_asset} {side}</b>\n"
  49. trades_text += f" 📏 Amount: {amount_str} {base_asset}\n"
  50. trades_text += f" 💰 Price: {price_str}\n"
  51. # Add P&L info
  52. pnl_emoji = "🟢" if pnl >= 0 else "🔴"
  53. trades_text += f" {pnl_emoji} P&L: ${pnl:,.2f} ({pnl_percentage:+.2f}%)\n"
  54. # Add trade type
  55. type_indicator = ""
  56. if trade.get('trade_lifecycle_id'):
  57. type_indicator = " 🤖"
  58. elif trade_type == 'external':
  59. type_indicator = " 🔄"
  60. trades_text += f" 📝 Type: {trade_type.upper()}{type_indicator}\n"
  61. # Add trade time
  62. if trade_time:
  63. try:
  64. trade_datetime = datetime.fromisoformat(trade_time.replace('Z', '+00:00'))
  65. time_diff = datetime.now(trade_datetime.tzinfo) - trade_datetime
  66. if time_diff.days > 0:
  67. time_str = f"{time_diff.days}d ago"
  68. elif time_diff.seconds >= 3600:
  69. time_str = f"{time_diff.seconds // 3600}h ago"
  70. else:
  71. time_str = f"{time_diff.seconds // 60}m ago"
  72. trades_text += f" ⏰ Time: {time_str}\n"
  73. except ValueError:
  74. logger.warning(f"Could not parse trade_time: {trade_time}")
  75. # Add trade ID
  76. trade_id = trade.get('id', 'unknown')
  77. trades_text += f" 🆔 Trade ID: {trade_id[:8]}\n\n"
  78. except Exception as e:
  79. logger.error(f"Error processing trade {trade.get('symbol', 'unknown')}: {e}")
  80. continue
  81. await self._reply(update, trades_text.strip())
  82. except Exception as e:
  83. logger.error(f"Error in trades command: {e}")
  84. await self._reply(update, "❌ Error retrieving trade history.")