|
@@ -39,12 +39,31 @@ class TelegramTradingBot:
|
|
|
self.stats = None
|
|
|
self.version = "Unknown" # Will be set by launcher
|
|
|
|
|
|
+ # Order monitoring attributes
|
|
|
+ self.monitoring_active = False
|
|
|
+ self.last_known_orders = set() # Track order IDs we've seen
|
|
|
+ self.last_known_positions = {} # Track position sizes for P&L calculation
|
|
|
+
|
|
|
+ # External trade monitoring
|
|
|
+ self.last_processed_trade_time = None # Track last processed external trade
|
|
|
+
|
|
|
+ # Deposit/Withdrawal monitoring
|
|
|
+ self.last_deposit_withdrawal_check = None # Track last deposit/withdrawal check
|
|
|
+ self.deposit_withdrawal_check_interval = 3600 # Check every hour (3600 seconds)
|
|
|
+
|
|
|
+ # Alarm management
|
|
|
+ self.alarm_manager = AlarmManager()
|
|
|
+
|
|
|
# Initialize stats
|
|
|
self._initialize_stats()
|
|
|
|
|
|
def _initialize_stats(self):
|
|
|
"""Initialize stats with current balance."""
|
|
|
try:
|
|
|
+ # Initialize TradingStats object first
|
|
|
+ self.stats = TradingStats()
|
|
|
+
|
|
|
+ # Get current balance and set it as initial balance
|
|
|
balance = self.client.get_balance()
|
|
|
if balance and balance.get('total'):
|
|
|
# Get USDC balance as the main balance
|
|
@@ -207,6 +226,7 @@ For support, contact your bot administrator.
|
|
|
• /balance - Show account balance
|
|
|
• /positions - Show open positions
|
|
|
• /orders - Show open orders
|
|
|
+• /balance_adjustments - View deposit/withdrawal history
|
|
|
|
|
|
<b>📊 Market Data:</b>
|
|
|
• /market - Detailed market data (default token)
|
|
@@ -261,6 +281,7 @@ For support, contact your bot administrator.
|
|
|
• All trades logged automatically
|
|
|
• Comprehensive performance tracking
|
|
|
• Real-time balance monitoring
|
|
|
+• Deposit/withdrawal tracking (hourly)
|
|
|
• Risk metrics calculation
|
|
|
|
|
|
<b>📱 Mobile Optimized:</b>
|
|
@@ -1157,6 +1178,7 @@ Tap any button below for instant access to bot functions:
|
|
|
self.application.add_handler(CommandHandler("monthly", self.monthly_command))
|
|
|
self.application.add_handler(CommandHandler("risk", self.risk_command))
|
|
|
self.application.add_handler(CommandHandler("version", self.version_command))
|
|
|
+ self.application.add_handler(CommandHandler("balance_adjustments", self.balance_adjustments_command))
|
|
|
|
|
|
# Callback query handler for inline keyboards
|
|
|
self.application.add_handler(CallbackQueryHandler(self.button_callback))
|
|
@@ -1954,6 +1976,9 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
|
|
|
if Config.RISK_MANAGEMENT_ENABLED:
|
|
|
await self._check_stop_losses(current_positions)
|
|
|
|
|
|
+ # Check deposits/withdrawals (hourly)
|
|
|
+ await self._check_deposits_withdrawals()
|
|
|
+
|
|
|
except Exception as e:
|
|
|
logger.error(f"❌ Error checking order fills: {e}")
|
|
|
|
|
@@ -2053,6 +2078,132 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
|
|
|
except Exception as e:
|
|
|
logger.error(f"❌ Error checking external trades: {e}")
|
|
|
|
|
|
+ async def _check_deposits_withdrawals(self):
|
|
|
+ """Check for deposits and withdrawals to maintain accurate P&L tracking."""
|
|
|
+ try:
|
|
|
+ # Check if it's time to run (hourly check)
|
|
|
+ current_time = datetime.now()
|
|
|
+
|
|
|
+ if self.last_deposit_withdrawal_check is not None:
|
|
|
+ time_since_last_check = (current_time - self.last_deposit_withdrawal_check).total_seconds()
|
|
|
+ if time_since_last_check < self.deposit_withdrawal_check_interval:
|
|
|
+ return # Not time to check yet
|
|
|
+
|
|
|
+ logger.info("🔍 Checking for deposits and withdrawals...")
|
|
|
+
|
|
|
+ # Initialize last check time if first run
|
|
|
+ if self.last_deposit_withdrawal_check is None:
|
|
|
+ # Set to 24 hours ago to catch recent activity
|
|
|
+ self.last_deposit_withdrawal_check = current_time - timedelta(hours=24)
|
|
|
+
|
|
|
+ # Calculate timestamp for API calls (last check time)
|
|
|
+ since_timestamp = int(self.last_deposit_withdrawal_check.timestamp() * 1000) # Hyperliquid expects milliseconds
|
|
|
+
|
|
|
+ # Track new deposits/withdrawals
|
|
|
+ new_deposits = 0
|
|
|
+ new_withdrawals = 0
|
|
|
+
|
|
|
+ # Check for deposits
|
|
|
+ try:
|
|
|
+ deposits = self.client.client.fetch_deposits(code='USDC', since=since_timestamp)
|
|
|
+ if deposits:
|
|
|
+ for deposit in deposits:
|
|
|
+ amount = float(deposit.get('amount', 0))
|
|
|
+ timestamp = deposit.get('datetime', datetime.now().isoformat())
|
|
|
+ deposit_id = deposit.get('id', 'unknown')
|
|
|
+
|
|
|
+ # Record in stats to adjust P&L calculations
|
|
|
+ self.stats.record_deposit(amount, timestamp, deposit_id)
|
|
|
+ new_deposits += 1
|
|
|
+
|
|
|
+ # Send notification
|
|
|
+ await self._send_deposit_notification(amount, timestamp)
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.warning(f"⚠️ Error fetching deposits: {e}")
|
|
|
+
|
|
|
+ # Check for withdrawals
|
|
|
+ try:
|
|
|
+ withdrawals = self.client.client.fetch_withdrawals(code='USDC', since=since_timestamp)
|
|
|
+ if withdrawals:
|
|
|
+ for withdrawal in withdrawals:
|
|
|
+ amount = float(withdrawal.get('amount', 0))
|
|
|
+ timestamp = withdrawal.get('datetime', datetime.now().isoformat())
|
|
|
+ withdrawal_id = withdrawal.get('id', 'unknown')
|
|
|
+
|
|
|
+ # Record in stats to adjust P&L calculations
|
|
|
+ self.stats.record_withdrawal(amount, timestamp, withdrawal_id)
|
|
|
+ new_withdrawals += 1
|
|
|
+
|
|
|
+ # Send notification
|
|
|
+ await self._send_withdrawal_notification(amount, timestamp)
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.warning(f"⚠️ Error fetching withdrawals: {e}")
|
|
|
+
|
|
|
+ # Update last check time
|
|
|
+ self.last_deposit_withdrawal_check = current_time
|
|
|
+
|
|
|
+ if new_deposits > 0 or new_withdrawals > 0:
|
|
|
+ logger.info(f"💰 Processed {new_deposits} deposits and {new_withdrawals} withdrawals")
|
|
|
+
|
|
|
+ # Get updated balance adjustments summary
|
|
|
+ adjustments = self.stats.get_balance_adjustments_summary()
|
|
|
+ logger.info(f"📊 Total adjustments: ${adjustments['net_adjustment']:,.2f} net ({adjustments['adjustment_count']} total)")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"❌ Error checking deposits/withdrawals: {e}")
|
|
|
+
|
|
|
+ async def _send_deposit_notification(self, amount: float, timestamp: str):
|
|
|
+ """Send notification for detected deposit."""
|
|
|
+ try:
|
|
|
+ time_str = datetime.fromisoformat(timestamp.replace('Z', '+00:00')).strftime('%H:%M:%S')
|
|
|
+
|
|
|
+ message = f"""
|
|
|
+💰 <b>Deposit Detected</b>
|
|
|
+
|
|
|
+💵 <b>Amount:</b> ${amount:,.2f} USDC
|
|
|
+⏰ <b>Time:</b> {time_str}
|
|
|
+
|
|
|
+📊 <b>P&L Impact:</b>
|
|
|
+• Initial balance adjusted to maintain accurate P&L
|
|
|
+• Trading statistics unaffected by balance change
|
|
|
+• This deposit will not show as trading profit
|
|
|
+
|
|
|
+✅ <b>Balance tracking updated automatically</b>
|
|
|
+ """
|
|
|
+
|
|
|
+ await self.send_message(message.strip())
|
|
|
+ logger.info(f"📱 Sent deposit notification: ${amount:,.2f}")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"❌ Error sending deposit notification: {e}")
|
|
|
+
|
|
|
+ async def _send_withdrawal_notification(self, amount: float, timestamp: str):
|
|
|
+ """Send notification for detected withdrawal."""
|
|
|
+ try:
|
|
|
+ time_str = datetime.fromisoformat(timestamp.replace('Z', '+00:00')).strftime('%H:%M:%S')
|
|
|
+
|
|
|
+ message = f"""
|
|
|
+💸 <b>Withdrawal Detected</b>
|
|
|
+
|
|
|
+💵 <b>Amount:</b> ${amount:,.2f} USDC
|
|
|
+⏰ <b>Time:</b> {time_str}
|
|
|
+
|
|
|
+📊 <b>P&L Impact:</b>
|
|
|
+• Initial balance adjusted to maintain accurate P&L
|
|
|
+• Trading statistics unaffected by balance change
|
|
|
+• This withdrawal will not show as trading loss
|
|
|
+
|
|
|
+✅ <b>Balance tracking updated automatically</b>
|
|
|
+ """
|
|
|
+
|
|
|
+ await self.send_message(message.strip())
|
|
|
+ logger.info(f"📱 Sent withdrawal notification: ${amount:,.2f}")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"❌ Error sending withdrawal notification: {e}")
|
|
|
+
|
|
|
async def _process_external_trade(self, trade: Dict[str, Any]):
|
|
|
"""Process an individual external trade and determine if it's opening or closing a position."""
|
|
|
try:
|
|
@@ -2307,6 +2458,16 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
|
|
|
# Get alarm statistics
|
|
|
alarm_stats = self.alarm_manager.get_statistics()
|
|
|
|
|
|
+ # Get balance adjustments info
|
|
|
+ adjustments_summary = self.stats.get_balance_adjustments_summary()
|
|
|
+ last_deposit_check = "Never"
|
|
|
+ next_deposit_check = "Unknown"
|
|
|
+
|
|
|
+ if self.last_deposit_withdrawal_check:
|
|
|
+ last_deposit_check = self.last_deposit_withdrawal_check.strftime('%H:%M:%S')
|
|
|
+ next_check_time = self.last_deposit_withdrawal_check + timedelta(seconds=self.deposit_withdrawal_check_interval)
|
|
|
+ next_deposit_check = next_check_time.strftime('%H:%M:%S')
|
|
|
+
|
|
|
status_text = f"""
|
|
|
🔄 <b>System Monitoring Status</b>
|
|
|
|
|
@@ -2316,6 +2477,13 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
|
|
|
• Orders Tracked: {len(self.last_known_orders)}
|
|
|
• Positions Tracked: {len(self.last_known_positions)}
|
|
|
|
|
|
+💰 <b>Deposit/Withdrawal Monitoring:</b>
|
|
|
+• Check Interval: {self.deposit_withdrawal_check_interval // 3600} hour(s)
|
|
|
+• Last Check: {last_deposit_check}
|
|
|
+• Next Check: {next_deposit_check}
|
|
|
+• Total Adjustments: {adjustments_summary['adjustment_count']}
|
|
|
+• Net Adjustment: ${adjustments_summary['net_adjustment']:,.2f}
|
|
|
+
|
|
|
🔔 <b>Price Alarms:</b>
|
|
|
• Active Alarms: {alarm_stats['total_active']}
|
|
|
• Triggered Today: {alarm_stats['total_triggered']}
|
|
@@ -2338,6 +2506,7 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
|
|
|
• 🎯 P&L Calculations
|
|
|
• 🔔 Price Alarm Triggers
|
|
|
• 🔄 External Trade Detection
|
|
|
+• 💰 Deposit/Withdrawal Detection
|
|
|
• 🛑 Automatic Stop Loss Triggers
|
|
|
|
|
|
⏰ <b>Last Check:</b> {datetime.now().strftime('%H:%M:%S')}
|
|
@@ -2348,6 +2517,7 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
|
|
|
• Position change tracking
|
|
|
• Price alarm monitoring
|
|
|
• External trade monitoring
|
|
|
+• Deposit/withdrawal tracking
|
|
|
• Auto stats synchronization
|
|
|
• Instant Telegram notifications
|
|
|
"""
|
|
@@ -3038,6 +3208,82 @@ Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
|
|
|
error_message = f"❌ Error processing version command: {str(e)}"
|
|
|
await update.message.reply_text(error_message)
|
|
|
logger.error(f"Error in version command: {e}")
|
|
|
+
|
|
|
+ async def balance_adjustments_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
|
+ """Handle the /balance_adjustments command to show deposit/withdrawal history."""
|
|
|
+ if not self.is_authorized(update.effective_chat.id):
|
|
|
+ await update.message.reply_text("❌ Unauthorized access.")
|
|
|
+ return
|
|
|
+
|
|
|
+ try:
|
|
|
+ # Get balance adjustments summary
|
|
|
+ adjustments_summary = self.stats.get_balance_adjustments_summary()
|
|
|
+
|
|
|
+ # Get detailed adjustments
|
|
|
+ all_adjustments = self.stats.data.get('balance_adjustments', [])
|
|
|
+
|
|
|
+ if not all_adjustments:
|
|
|
+ await update.message.reply_text(
|
|
|
+ "💰 <b>Balance Adjustments</b>\n\n"
|
|
|
+ "📭 No deposits or withdrawals detected yet.\n\n"
|
|
|
+ "💡 The bot automatically monitors for deposits and withdrawals\n"
|
|
|
+ "every hour to maintain accurate P&L calculations.",
|
|
|
+ parse_mode='HTML'
|
|
|
+ )
|
|
|
+ return
|
|
|
+
|
|
|
+ # Format the message
|
|
|
+ adjustments_text = f"""
|
|
|
+💰 <b>Balance Adjustments History</b>
|
|
|
+
|
|
|
+📊 <b>Summary:</b>
|
|
|
+• Total Deposits: ${adjustments_summary['total_deposits']:,.2f}
|
|
|
+• Total Withdrawals: ${adjustments_summary['total_withdrawals']:,.2f}
|
|
|
+• Net Adjustment: ${adjustments_summary['net_adjustment']:,.2f}
|
|
|
+• Total Transactions: {adjustments_summary['adjustment_count']}
|
|
|
+
|
|
|
+📅 <b>Recent Adjustments:</b>
|
|
|
+"""
|
|
|
+
|
|
|
+ # Show last 10 adjustments
|
|
|
+ recent_adjustments = sorted(all_adjustments, key=lambda x: x['timestamp'], reverse=True)[:10]
|
|
|
+
|
|
|
+ for adj in recent_adjustments:
|
|
|
+ try:
|
|
|
+ # Format timestamp
|
|
|
+ adj_time = datetime.fromisoformat(adj['timestamp']).strftime('%m/%d %H:%M')
|
|
|
+
|
|
|
+ # Format type and amount
|
|
|
+ if adj['type'] == 'deposit':
|
|
|
+ emoji = "💰"
|
|
|
+ amount_str = f"+${adj['amount']:,.2f}"
|
|
|
+ else: # withdrawal
|
|
|
+ emoji = "💸"
|
|
|
+ amount_str = f"-${abs(adj['amount']):,.2f}"
|
|
|
+
|
|
|
+ adjustments_text += f"• {emoji} {adj_time}: {amount_str}\n"
|
|
|
+
|
|
|
+ except Exception as adj_error:
|
|
|
+ logger.warning(f"Error formatting adjustment: {adj_error}")
|
|
|
+ continue
|
|
|
+
|
|
|
+ adjustments_text += f"""
|
|
|
+
|
|
|
+💡 <b>How it Works:</b>
|
|
|
+• Bot checks for deposits/withdrawals every hour
|
|
|
+• Adjustments maintain accurate P&L calculations
|
|
|
+• Non-trading balance changes don't affect performance metrics
|
|
|
+• Trading statistics remain pure and accurate
|
|
|
+
|
|
|
+⏰ <b>Last Check:</b> {adjustments_summary['last_adjustment'][:16] if adjustments_summary['last_adjustment'] else 'Never'}
|
|
|
+ """
|
|
|
+
|
|
|
+ await update.message.reply_text(adjustments_text.strip(), parse_mode='HTML')
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ error_message = f"❌ Error processing balance adjustments command: {str(e)}"
|
|
|
+ await update.message.reply_text(error_message)
|
|
|
+ logger.error(f"Error in balance_adjustments command: {e}")
|
|
|
|
|
|
def _get_position_state(self, symbol: str) -> Dict[str, Any]:
|
|
|
"""Get current position state for a symbol."""
|