Pārlūkot izejas kodu

Add balance adjustments tracking and notifications in Telegram bot - Implement a new feature to monitor deposits and withdrawals, enhancing P&L accuracy. Introduce the /balance_adjustments command for users to view their deposit/withdrawal history, and update trading statistics to include balance adjustments summary. Refactor TradingStats to manage balance adjustments effectively.

Carles Sentis 2 nedēļas atpakaļ
vecāks
revīzija
c95eb361c3
3 mainītis faili ar 386 papildinājumiem un 53 dzēšanām
  1. 6 0
      price_alarms.json
  2. 246 0
      src/telegram_bot.py
  3. 134 53
      src/trading_stats.py

+ 6 - 0
price_alarms.json

@@ -0,0 +1,6 @@
+{
+  "next_id": 1,
+  "alarms": [],
+  "triggered_alarms": [],
+  "last_update": "2025-06-01T20:01:10.017487"
+}

+ 246 - 0
src/telegram_bot.py

@@ -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."""

+ 134 - 53
src/trading_stats.py

@@ -63,7 +63,8 @@ class TradingStats:
             'daily_stats': {},    # Daily aggregate stats {date: {trades: N, pnl: X, pnl_pct: Y}}
             'weekly_stats': {},   # Weekly aggregate stats {week: {trades: N, pnl: X, pnl_pct: Y}}
             'monthly_stats': {},  # Monthly aggregate stats {month: {trades: N, pnl: X, pnl_pct: Y}}
-            'enhanced_positions': {}  # Enhanced position tracking {symbol: {contracts, avg_entry_price, total_cost_basis, entry_count, entry_history}}
+            'enhanced_positions': {},  # Enhanced position tracking {symbol: {contracts, avg_entry_price, total_cost_basis, entry_count, entry_history}}
+            'balance_adjustments': []  # List of balance adjustments
         }
         self._save_stats()
     
@@ -534,68 +535,67 @@ class TradingStats:
             completed_trades = basic.get('completed_trades', 0)
             total_orders = basic.get('total_trades', 0)
             
-            message = f"""
+            # Calculate key metrics
+            total_pnl = current_balance - basic['initial_balance'] if basic['initial_balance'] > 0 else 0
+            total_return = (total_pnl / basic['initial_balance'] * 100) if basic['initial_balance'] > 0 else 0
+            win_rate = (perf['win_rate'] / 100) if completed_trades > 0 else 0
+            
+            # Calculate average trade metrics
+            avg_win = perf['avg_win']
+            avg_loss = perf['avg_loss']
+            profit_factor = perf['profit_factor']
+            
+            # Get balance adjustments summary
+            adjustments_summary = self.get_balance_adjustments_summary()
+            
+            # P&L emoji
+            pnl_emoji = "🟢" if total_pnl >= 0 else "🔴"
+            
+            stats_text = f"""
 📊 <b>Trading Statistics</b>
 
-💰 <b>Balance Overview</b>
-• Initial: ${basic.get('initial_balance', 0):,.2f}
-• Current: ${stats.get('current_balance', 0):,.2f}
-• Total P&L: ${basic.get('total_pnl', 0):,.2f}
-• Total Return: {stats.get('total_return', 0):.2f}%
+💰 <b>Account Overview:</b>
+• Current Balance: ${current_balance:,.2f}
+• Initial Balance: ${basic['initial_balance']:,.2f}
+• {pnl_emoji} Total P&L: ${total_pnl:,.2f} ({total_return:+.2f}%)
 
-📈 <b>Trading Activity</b>
+📈 <b>Trading Activity:</b>
 • Total Orders: {total_orders}
 • Completed Trades: {completed_trades}
-• Buy Orders: {basic.get('buy_trades', 0)}
-• Sell Orders: {basic.get('sell_trades', 0)}
-• Days Active: {basic.get('days_active', 0)}
-            """
-            
-            if completed_trades > 0:
-                # Show full performance metrics when we have completed trades
-                message += f"""
-🏆 <b>Performance Metrics</b>
-• Win Rate: {perf.get('win_rate', 0):.1f}%
-• Profit Factor: {perf.get('profit_factor', 0):.2f}
-• Avg Win: ${perf.get('avg_win', 0):.2f}
-• Avg Loss: ${perf.get('avg_loss', 0):.2f}
-• Expectancy: ${perf.get('expectancy', 0):.2f}
+• Open Positions: {len([p for p in self.data.get('enhanced_positions', {}).values() if p.get('contracts', 0) != 0])}
+• Days Active: {(datetime.now() - datetime.fromisoformat(basic['start_date'])).days + 1}
 
-📊 <b>Risk Metrics</b>
-• Sharpe Ratio: {risk.get('sharpe_ratio', 0):.2f}
-• Sortino Ratio: {risk.get('sortino_ratio', 0):.2f}
-• Max Drawdown: {risk.get('max_drawdown', 0):.2f}%
-• Volatility: {risk.get('volatility', 0):.2f}%
-• VaR (95%): {risk.get('var_95', 0):.2f}%
-
-🎯 <b>Best/Worst</b>
-• Largest Win: ${perf.get('largest_win', 0):.2f}
-• Largest Loss: ${perf.get('largest_loss', 0):.2f}
-• Max Consecutive Wins: {perf.get('consecutive_wins', 0)}
-• Max Consecutive Losses: {perf.get('consecutive_losses', 0)}
-                """
-            else:
-                # Show informative message when no completed trades
-                open_positions = total_orders - (basic.get('buy_trades', 0) if basic.get('sell_trades', 0) > 0 else 0)
-                message += f"""
-💡 <b>Performance Status</b>
-• Open Positions: {open_positions} position{'s' if open_positions != 1 else ''}
-• Performance metrics will appear after closing positions
-• Current trades: Entry orders placed, waiting for exits
+🏆 <b>Performance Metrics:</b>
+• Win Rate: {win_rate:.1f}% ({perf['total_wins']}W/{perf['total_losses']}L)
+• Profit Factor: {profit_factor:.2f}
+• Avg Win: ${avg_win:.2f} | Avg Loss: ${avg_loss:.2f}
+• Largest Win: ${perf['largest_win']:.2f} | Largest Loss: ${perf['largest_loss']:.2f}
+"""
+            
+            # Add balance adjustments section if there are any
+            if adjustments_summary['adjustment_count'] > 0:
+                adj_emoji = "💰" if adjustments_summary['net_adjustment'] >= 0 else "💸"
+                stats_text += f"""
+💰 <b>Balance Adjustments:</b>
+• Deposits: ${adjustments_summary['total_deposits']:,.2f}
+• Withdrawals: ${adjustments_summary['total_withdrawals']:,.2f}
+• {adj_emoji} Net: ${adjustments_summary['net_adjustment']:,.2f} ({adjustments_summary['adjustment_count']} transactions)
 
-📈 <b>Ready for Analysis</b>
-• Close positions to see P&L calculations
-• Win rate, profit factor, and risk metrics
-• Use /exit [token] to close positions
-                """
+"""
             
-            message += f"""
-📅 <b>Since:</b> {basic.get('start_date', 'N/A')}
+            # Calculate trade volume safely
+            stats_text += f"""
+🎯 <b>Trade Distribution:</b>
+• Buy Orders: {basic['buy_trades']} | Sell Orders: {basic['sell_trades']}
+• Volume Traded: ${sum(t['value'] for t in self.data['trades'] if t['side'] == 'sell'):,.2f}
+• Avg Trade Size: ${(sum(t['value'] for t in self.data['trades'] if t['side'] == 'sell') / len([t for t in self.data['trades'] if t['side'] == 'sell'])):.2f}
 
-💡 <b>Note:</b> Performance metrics based on {completed_trades} completed trade cycle{'s' if completed_trades != 1 else ''}
+⏰ <b>Session Info:</b>
+• Started: {basic['start_date']}
+• Last Update: {datetime.now().strftime('%H:%M:%S')}
             """
             
-            return message.strip()
+            return stats_text.strip()
                 
         except Exception as e:
             # Fallback message if stats calculation fails
@@ -996,4 +996,85 @@ Please try again in a moment. If the issue persists, contact support.
                     'has_trades': False
                 })
         
-        return monthly_stats 
+        return monthly_stats
+
+    def record_deposit(self, amount: float, timestamp: str = None, deposit_id: str = None):
+        """Record a deposit to adjust P&L calculations."""
+        if timestamp is None:
+            timestamp = datetime.now().isoformat()
+            
+        deposit_record = {
+            'timestamp': timestamp,
+            'amount': amount,
+            'type': 'deposit',
+            'id': deposit_id,
+            'description': f'Deposit of ${amount:.2f}'
+        }
+        
+        # Initialize balance_adjustments if not present
+        if 'balance_adjustments' not in self.data:
+            self.data['balance_adjustments'] = []
+        
+        self.data['balance_adjustments'].append(deposit_record)
+        
+        # Adjust initial balance to maintain P&L accuracy
+        self.data['initial_balance'] += amount
+        
+        logger.info(f"💰 Recorded deposit: ${amount:.2f} (adjusted initial balance to ${self.data['initial_balance']:.2f})")
+        self._save_stats()
+    
+    def record_withdrawal(self, amount: float, timestamp: str = None, withdrawal_id: str = None):
+        """Record a withdrawal to adjust P&L calculations."""
+        if timestamp is None:
+            timestamp = datetime.now().isoformat()
+            
+        withdrawal_record = {
+            'timestamp': timestamp,
+            'amount': -amount,  # Negative for withdrawal
+            'type': 'withdrawal',
+            'id': withdrawal_id,
+            'description': f'Withdrawal of ${amount:.2f}'
+        }
+        
+        # Initialize balance_adjustments if not present
+        if 'balance_adjustments' not in self.data:
+            self.data['balance_adjustments'] = []
+        
+        self.data['balance_adjustments'].append(withdrawal_record)
+        
+        # Adjust initial balance to maintain P&L accuracy
+        self.data['initial_balance'] -= amount
+        
+        logger.info(f"💸 Recorded withdrawal: ${amount:.2f} (adjusted initial balance to ${self.data['initial_balance']:.2f})")
+        self._save_stats()
+    
+    def get_balance_adjustments_summary(self) -> Dict[str, Any]:
+        """Get summary of all balance adjustments (deposits/withdrawals)."""
+        adjustments = self.data.get('balance_adjustments', [])
+        
+        if not adjustments:
+            return {
+                'total_deposits': 0.0,
+                'total_withdrawals': 0.0,
+                'net_adjustment': 0.0,
+                'adjustment_count': 0,
+                'last_adjustment': None
+            }
+        
+        deposits = [adj for adj in adjustments if adj['type'] == 'deposit']
+        withdrawals = [adj for adj in adjustments if adj['type'] == 'withdrawal']
+        
+        total_deposits = sum(adj['amount'] for adj in deposits)
+        total_withdrawals = sum(abs(adj['amount']) for adj in withdrawals)
+        net_adjustment = total_deposits - total_withdrawals
+        
+        # Find most recent adjustment
+        latest_adjustment = max(adjustments, key=lambda x: x['timestamp']) if adjustments else None
+        
+        return {
+            'total_deposits': total_deposits,
+            'total_withdrawals': total_withdrawals,
+            'net_adjustment': net_adjustment,
+            'adjustment_count': len(adjustments),
+            'last_adjustment': latest_adjustment['timestamp'] if latest_adjustment else None
+        }