Browse Source

Refactor data management and update file structure for trading bot - Modified the .gitignore to include new data directories for persistent trading data and logs. Updated reset_data.sh to reflect changes in file paths and improved user prompts. Enhanced trading_stats.py and alarm_manager.py to ensure data is stored in the new structure, improving organization and maintainability. Added demo_stats.json for testing purposes.

Carles Sentis 3 days ago
parent
commit
0cfca41a25
7 changed files with 2025 additions and 214 deletions
  1. 12 5
      .gitignore
  2. 46 23
      reset_data.sh
  3. 4 3
      src/commands/management_commands.py
  4. 89 106
      src/monitoring/alarm_manager.py
  5. 79 73
      src/trading/trading_stats.py
  6. 1789 0
      tests/demo_stats.json
  7. 6 4
      trading_bot.py

+ 12 - 5
.gitignore

@@ -165,11 +165,18 @@ cython_debug/
 .env.production
 .env.development
 
-# Trading data and logs
-logs/
-*.log
-trading_stats.json
-demo_stats.json
+# Trading data, logs, and state files
+logs/  # For log files
+data/  # For persistent data like stats, alarms, state
+
+# Specific file ignores (mostly covered by data/ but good to be explicit if needed elsewhere)
+# trading_stats.json # Now covered by data/
+# price_alarms.json # Now covered by data/
+# trading_engine_state.json # Now covered by data/
+demo_stats.json # If this is still used at root, keep it
+
+# General log files if not in logs/ directory (already covered by *.log above)
+# *.log
 
 # Backup files
 *.bak

+ 46 - 23
reset_data.sh

@@ -4,7 +4,7 @@ echo "🗑️ Resetting Hyperliquid Trading Bot Data"
 echo "========================================="
 
 # Confirm with user
-read -p "⚠️  This will delete ALL trading data, stats, and logs. Are you sure? (yes/no): " confirm
+read -p "⚠️  This will delete ALL trading data, state, alarms, and logs. Are you sure? (yes/no): " confirm
 
 if [ "$confirm" != "yes" ]; then
     echo "❌ Reset cancelled."
@@ -13,49 +13,70 @@ fi
 
 echo "🧹 Clearing data files..."
 
+DATA_DIR="data"
+
+# Create data directory if it doesn't exist (for idempotency, though removal is primary goal)
+# mkdir -p $DATA_DIR
+
 # Remove trading statistics
-if [ -f "trading_stats.json" ]; then
-    rm trading_stats.json
-    echo "✅ Removed trading_stats.json"
+if [ -f "$DATA_DIR/trading_stats.json" ]; then
+    rm "$DATA_DIR/trading_stats.json"
+    echo "✅ Removed $DATA_DIR/trading_stats.json"
 fi
 
-# Remove backup stats
-rm -f trading_stats.json.backup*
-echo "✅ Removed stats backups"
+# Remove backup stats (if any were made at root or in data)
+rm -f trading_stats.json.backup* # Old root backups
+rm -f "$DATA_DIR/trading_stats.json.backup*"
+echo "✅ Removed stats backups (if any)"
 
-# Remove bot state persistence file
+# Remove trading engine state persistence file
+if [ -f "$DATA_DIR/trading_engine_state.json" ]; then
+    rm "$DATA_DIR/trading_engine_state.json"
+    echo "✅ Removed $DATA_DIR/trading_engine_state.json"
+fi
+# Remove old bot_state.json if it exists at root from previous versions
 if [ -f "bot_state.json" ]; then
     rm bot_state.json
-    echo "✅ Removed bot_state.json"
+    echo "✅ Removed legacy bot_state.json from root"
 fi
 
 # Remove alarm data
-if [ -f "price_alarms.json" ]; then
-    rm price_alarms.json
-    echo "✅ Removed price_alarms.json"
+if [ -f "$DATA_DIR/price_alarms.json" ]; then
+    rm "$DATA_DIR/price_alarms.json"
+    echo "✅ Removed $DATA_DIR/price_alarms.json"
+fi
+rm -f alarms.json alarms.db # Legacy alarm files at root
+echo "✅ Removed legacy alarm data from root (if any)"
+
+# Optionally, remove the entire data directory if it's now empty
+# Check if directory exists and is empty
+if [ -d "$DATA_DIR" ] && [ -z "$(ls -A $DATA_DIR)" ]; then
+    rmdir "$DATA_DIR"
+    echo "✅ Removed empty $DATA_DIR directory"
+elif [ -d "$DATA_DIR" ]; then
+    echo "ℹ️  $DATA_DIR directory still contains other files."
 fi
-rm -f alarms.json alarms.db
-echo "✅ Removed legacy alarm data"
 
 # Remove log files
-rm -f *.log trading_bot.log*
+rm -f *.log # Root logs (if any)
 rm -rf logs/
-echo "✅ Removed log files"
+echo "✅ Removed log files and logs/ directory"
 
 # Remove any temporary files
 rm -f *.tmp *.temp
 echo "✅ Removed temporary files"
 
 # Remove any Python cache
-rm -rf __pycache__/
-rm -rf src/__pycache__/
-rm -rf tests/__pycache__/
-echo "✅ Removed Python cache"
+find . -type d -name "__pycache__" -exec rm -rf {} +
+echo "✅ Removed Python cache directories"
 
 echo ""
 echo "🎉 Data reset complete!"
 echo ""
 echo "📝 What was cleared:"
+echo "   • Persistent data from the '$DATA_DIR/' directory (stats, state, alarms)"
+echo "   • Log files from 'logs/' directory and root"
+echo "   • Legacy data files from the root directory (if found)"
 echo "   • Trading statistics and P&L history"
 echo "   • All completed trade records"
 echo "   • Daily/weekly/monthly performance data"
@@ -63,15 +84,17 @@ echo "   • Bot state persistence (pending stop losses, order tracking)"
 echo "   • Price alarms and notifications"
 echo "   • Log files and debugging data"
 echo "   • Enhanced position tracking data"
+
 echo ""
 echo "🚀 Your bot is now ready for a fresh start!"
-echo "   • Initial balance will be reset"
+echo "   • Initial balance will be reset by the bot logic"
 echo "   • All stats will start from zero"
 echo "   • Position tracking will reinitialize"
 echo "   • Bot state persistence will restart fresh"
 echo "   • Pending stop losses will be cleared"
+
 echo ""
 echo "💡 Next steps:"
-echo "   1. Run your bot: python src/telegram_bot.py"
-echo "   2. Check /balance to set initial balance"
+echo "   1. Run your bot: python trading_bot.py (or your main script)"
+echo "   2. Check /balance for initial balance status"
 echo "   3. Start trading to build new statistics" 

+ 4 - 3
src/commands/management_commands.py

@@ -10,6 +10,7 @@ import sys
 from datetime import datetime, timedelta
 from telegram import Update
 from telegram.ext import ContextTypes
+import json
 
 from src.config.config import Config
 from src.monitoring.alarm_manager import AlarmManager
@@ -357,9 +358,9 @@ Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
 • Running: {'✅ Yes' if self.market_monitor.is_running else '❌ No'}
 
 📁 <b>State Files:</b>
-• Trading Engine State: {'✅ Exists' if os.path.exists('trading_engine_state.json') else '❌ Missing'}
-• Price Alarms: {'✅ Exists' if os.path.exists('price_alarms.json') else '❌ Missing'}
-• Trading Stats: {'✅ Exists' if os.path.exists('trading_stats.json') else '❌ Missing'}
+• Trading Engine State: {'✅ Exists' if os.path.exists('data/trading_engine_state.json') else '❌ Missing'}
+• Price Alarms: {'✅ Exists' if os.path.exists('data/price_alarms.json') else '❌ Missing'}
+• Trading Stats: {'✅ Exists' if os.path.exists('data/trading_stats.json') else '❌ Missing'}
 
 🔔 <b>Alarm Manager:</b>
 • Active Alarms: {self.alarm_manager.get_statistics()['total_active']}

+ 89 - 106
src/monitoring/alarm_manager.py

@@ -16,9 +16,16 @@ logger = logging.getLogger(__name__)
 class AlarmManager:
     """Manages price alarms with persistence and monitoring."""
     
-    def __init__(self, alarms_file: str = "price_alarms.json"):
+    def __init__(self, alarms_file: str = "data/price_alarms.json"):
         """Initialize the alarm manager."""
         self.alarms_file = alarms_file
+        
+        # Ensure the data directory exists
+        data_dir = os.path.dirname(self.alarms_file)
+        if data_dir and not os.path.exists(data_dir):
+            os.makedirs(data_dir)
+            logger.info(f"Created data directory: {data_dir}")
+            
         self.data = self._load_alarms()
         
         # Initialize if first run
@@ -30,9 +37,18 @@ class AlarmManager:
         try:
             if os.path.exists(self.alarms_file):
                 with open(self.alarms_file, 'r') as f:
-                    return json.load(f)
+                    loaded_data = json.load(f)
+                    # Basic validation
+                    if isinstance(loaded_data, dict) and 'next_id' in loaded_data and 'alarms' in loaded_data:
+                        return loaded_data
+                    else:
+                        logger.warning(f"Alarms file {self.alarms_file} has invalid format. Re-initializing.")
+                        return {}
+        except json.JSONDecodeError as e:
+            logger.error(f"Error decoding JSON from {self.alarms_file}: {e}. Re-initializing.")
+            return {}
         except Exception as e:
-            logger.error(f"Error loading alarms: {e}")
+            logger.error(f"Error loading alarms from {self.alarms_file}: {e}")
         return {}
     
     def _save_alarms(self):
@@ -41,7 +57,7 @@ class AlarmManager:
             with open(self.alarms_file, 'w') as f:
                 json.dump(self.data, f, indent=2, default=str)
         except Exception as e:
-            logger.error(f"Error saving alarms: {e}")
+            logger.error(f"Error saving alarms to {self.alarms_file}: {e}")
     
     def _initialize_alarms(self):
         """Initialize alarms structure."""
@@ -55,18 +71,18 @@ class AlarmManager:
     
     def create_alarm(self, token: str, target_price: float, current_price: float) -> Dict[str, Any]:
         """Create a new price alarm."""
-        # Determine direction based on current vs target price
-        direction = "above" if target_price > current_price else "below"
+        direction = 'above' if target_price > current_price else 'below'
         
         alarm = {
             'id': self.data['next_id'],
             'token': token.upper(),
             'target_price': target_price,
-            'current_price_when_set': current_price,
+            'current_price_at_creation': current_price,
             'direction': direction,
             'status': 'active',
             'created_at': datetime.now().isoformat(),
-            'triggered_at': None
+            'triggered_at': None,
+            'triggered_price': None
         }
         
         self.data['alarms'].append(alarm)
@@ -74,12 +90,12 @@ class AlarmManager:
         self.data['last_update'] = datetime.now().isoformat()
         self._save_alarms()
         
-        logger.info(f"Created alarm {alarm['id']}: {token} @ ${target_price} ({direction})")
+        logger.info(f"Created alarm {alarm['id']}: {token} {direction} ${target_price}")
         return alarm
     
     def get_alarm_by_id(self, alarm_id: int) -> Optional[Dict[str, Any]]:
         """Get alarm by ID."""
-        for alarm in self.data['alarms']:
+        for alarm in self.data.get('alarms', []):
             if alarm['id'] == alarm_id:
                 return alarm
         return None
@@ -87,21 +103,23 @@ class AlarmManager:
     def get_alarms_by_token(self, token: str) -> List[Dict[str, Any]]:
         """Get all active alarms for a specific token."""
         token = token.upper()
-        return [alarm for alarm in self.data['alarms'] if alarm['token'] == token and alarm['status'] == 'active']
+        return [alarm for alarm in self.data.get('alarms', []) if alarm['token'] == token and alarm['status'] == 'active']
     
     def get_all_active_alarms(self) -> List[Dict[str, Any]]:
         """Get all active alarms."""
-        return [alarm for alarm in self.data['alarms'] if alarm['status'] == 'active']
+        return [alarm for alarm in self.data.get('alarms', []) if alarm['status'] == 'active']
     
     def get_recent_triggered_alarms(self, limit: int = 10) -> List[Dict[str, Any]]:
         """Get recently triggered alarms."""
-        return self.data['triggered_alarms'][-limit:] if self.data['triggered_alarms'] else []
+        triggered = self.data.get('triggered_alarms', [])
+        return triggered[-limit:]
     
     def remove_alarm(self, alarm_id: int) -> bool:
         """Remove an alarm by ID."""
-        for i, alarm in enumerate(self.data['alarms']):
+        alarms_list = self.data.get('alarms', [])
+        for i, alarm in enumerate(alarms_list):
             if alarm['id'] == alarm_id:
-                removed_alarm = self.data['alarms'].pop(i)
+                removed_alarm = alarms_list.pop(i)
                 self.data['last_update'] = datetime.now().isoformat()
                 self._save_alarms()
                 logger.info(f"Removed alarm {alarm_id}: {removed_alarm['token']} @ ${removed_alarm['target_price']}")
@@ -110,18 +128,16 @@ class AlarmManager:
     
     def trigger_alarm(self, alarm_id: int, triggered_price: float) -> Optional[Dict[str, Any]]:
         """Mark an alarm as triggered."""
-        for alarm in self.data['alarms']:
+        alarms_list = self.data.get('alarms', [])
+        for alarm in alarms_list:
             if alarm['id'] == alarm_id and alarm['status'] == 'active':
                 alarm['status'] = 'triggered'
                 alarm['triggered_at'] = datetime.now().isoformat()
                 alarm['triggered_price'] = triggered_price
                 
-                # Move to triggered alarms list
-                self.data['triggered_alarms'].append(alarm.copy())
-                
-                # Keep only last 50 triggered alarms
-                if len(self.data['triggered_alarms']) > 50:
-                    self.data['triggered_alarms'] = self.data['triggered_alarms'][-50:]
+                triggered_alarms_list = self.data.get('triggered_alarms', [])
+                triggered_alarms_list.append(alarm.copy())
+                self.data['triggered_alarms'] = triggered_alarms_list[-50:] # Keep only last 50
                 
                 self.data['last_update'] = datetime.now().isoformat()
                 self._save_alarms()
@@ -131,109 +147,76 @@ class AlarmManager:
         return None
     
     def check_alarms(self, price_data: Dict[str, float]) -> List[Dict[str, Any]]:
-        """Check all active alarms against current prices."""
+        """Check all active alarms against current prices and trigger if needed."""
         triggered_alarms = []
-        
         for alarm in self.get_all_active_alarms():
             token = alarm['token']
-            target_price = alarm['target_price']
-            direction = alarm['direction']
-            
-            # Get current price for this token
-            current_price = price_data.get(token)
-            if current_price is None:
-                continue
-            
-            # Check if alarm should trigger
-            should_trigger = False
-            
-            if direction == "above" and current_price >= target_price:
-                should_trigger = True
-            elif direction == "below" and current_price <= target_price:
-                should_trigger = True
-            
-            if should_trigger:
-                triggered_alarm = self.trigger_alarm(alarm['id'], current_price)
-                if triggered_alarm:
-                    triggered_alarms.append(triggered_alarm)
-        
+            if token in price_data:
+                current_price = price_data[token]
+                target_price = alarm['target_price']
+                direction = alarm['direction']
+                
+                if (direction == 'above' and current_price >= target_price) or \
+                   (direction == 'below' and current_price <= target_price):
+                    triggered_alarm_obj = self.trigger_alarm(alarm['id'], current_price)
+                    if triggered_alarm_obj:
+                        triggered_alarms.append(triggered_alarm_obj)
         return triggered_alarms
     
-    def format_alarm_list(self, alarms: List[Dict[str, Any]], title: str = "Price Alarms") -> str:
+    def get_statistics(self) -> Dict[str, Any]:
+        """Get alarm statistics."""
+        active_alarms = self.get_all_active_alarms()
+        triggered_alarms = self.data.get('triggered_alarms', [])
+        
+        token_counts = {}
+        for alarm in active_alarms:
+            token_counts[alarm['token']] = token_counts.get(alarm['token'], 0) + 1
+        
+        return {
+            'total_active': len(active_alarms),
+            'total_triggered': len(triggered_alarms),
+            'tokens_tracked': len(token_counts),
+            'token_breakdown': token_counts,
+            'next_id': self.data.get('next_id', 1)
+        }
+    
+    def format_alarm_list(self, alarms: List[Dict[str, Any]], title: str = "Active Price Alarms") -> str:
         """Format a list of alarms for display."""
         if not alarms:
-            return f"📢 <b>{title}</b>\n\n📭 No alarms found."
-        
-        message = f"📢 <b>{title}</b>\n\n"
+            return f"🔔 <b>{title}</b>\n\n📭 No active alarms.\nUse <code>/alarm TOKEN PRICE</code> to set one."
         
+        message = f"🔔 <b>{title}</b>\n\n"
         for alarm in alarms:
-            status_emoji = "✅" if alarm['status'] == 'active' else "🔔"
             direction_emoji = "📈" if alarm['direction'] == 'above' else "📉"
+            created_price = alarm.get('current_price_at_creation', alarm['target_price'])
+            price_diff = abs(alarm['target_price'] - created_price)
+            price_diff_percent = (price_diff / created_price * 100) if created_price > 0 else 0
             
-            message += f"{status_emoji} <b>ID {alarm['id']}</b> - {alarm['token']}\n"
-            message += f"   {direction_emoji} Alert {alarm['direction']} ${alarm['target_price']:,.2f}\n"
-            
-            if alarm['status'] == 'active':
-                created_date = datetime.fromisoformat(alarm['created_at']).strftime('%m/%d %H:%M')
-                message += f"   📅 Created: {created_date}\n"
-            else:
-                triggered_date = datetime.fromisoformat(alarm['triggered_at']).strftime('%m/%d %H:%M')
-                message += f"   🔔 Triggered: {triggered_date} @ ${alarm['triggered_price']:,.2f}\n"
-            
-            message += "\n"
-        
-        if title == "Price Alarms":
-            message += f"💡 <b>Commands:</b>\n"
-            message += f"• <code>/alarm BTC 50000</code> - Set new alarm\n"
-            message += f"• <code>/alarm BTC</code> - Show BTC alarms\n"
-            message += f"• <code>/alarm 3</code> - Remove alarm ID 3"
+            message += f"• ID {alarm['id']}: {alarm['token']} {direction_emoji} ${alarm['target_price']:,.2f}\n"
+            message += f"  (Created at: ${created_price:,.2f}, Diff: {price_diff_percent:.1f}%)\n"
         
+        message += "\n💡 Use <code>/alarm ID</code> to remove an alarm."
         return message.strip()
     
     def format_triggered_alarm(self, alarm: Dict[str, Any]) -> str:
         """Format a triggered alarm notification."""
         direction_emoji = "📈" if alarm['direction'] == 'above' else "📉"
-        price_change = alarm['triggered_price'] - alarm['current_price_when_set']
-        change_percent = (price_change / alarm['current_price_when_set']) * 100
-        
+        triggered_at_str = "Unknown time"
+        if alarm.get('triggered_at'):
+            try:
+                triggered_at_str = datetime.fromisoformat(alarm['triggered_at']).strftime('%H:%M:%S')
+            except ValueError:
+                pass # Keep as Unknown time
+
         message = f"""
-🔔 <b>Price Alert Triggered!</b>
+🚨 <b>Price Alarm Triggered!</b>
 
-📊 <b>Alarm Details:</b>
+🔔 <b>Alarm ID: {alarm['id']}</b>
 • Token: {alarm['token']}
-• Alert ID: {alarm['id']}
-• Target Price: ${alarm['target_price']:,.2f}
-• Direction: {alarm['direction'].upper()}
-
-💰 <b>Price Info:</b>
-• Current Price: ${alarm['triggered_price']:,.2f}
-• Price When Set: ${alarm['current_price_when_set']:,.2f}
-• Change: ${price_change:,.2f} ({change_percent:+.2f}%)
-
-{direction_emoji} <b>Alert Condition:</b> Price moved {alarm['direction']} ${alarm['target_price']:,.2f}
+• Condition: Price {alarm['direction']} ${alarm['target_price']:,.2f}
+• Triggered Price: ${alarm.get('triggered_price', 0.0):,.2f} {direction_emoji}
 
-⏰ <b>Triggered:</b> {datetime.now().strftime('%H:%M:%S')}
-
-✅ <b>Status:</b> Alarm completed and removed from active list
+⏰ <b>Time:</b> {triggered_at_str}
+💡 This alarm is now inactive.
         """
-        
-        return message.strip()
-    
-    def get_statistics(self) -> Dict[str, Any]:
-        """Get alarm statistics."""
-        active_alarms = self.get_all_active_alarms()
-        triggered_alarms = self.data.get('triggered_alarms', [])
-        
-        # Count by token
-        token_counts = {}
-        for alarm in active_alarms:
-            token = alarm['token']
-            token_counts[token] = token_counts.get(token, 0) + 1
-        
-        return {
-            'total_active': len(active_alarms),
-            'total_triggered': len(triggered_alarms),
-            'tokens_tracked': len(token_counts),
-            'token_breakdown': token_counts,
-            'next_id': self.data['next_id']
-        } 
+        return message.strip() 

+ 79 - 73
src/trading/trading_stats.py

@@ -14,64 +14,87 @@ Tracks and calculates comprehensive trading statistics including:
 import json
 import os
 import logging
-from datetime import datetime, timedelta
-from typing import Dict, List, Any
+from datetime import datetime, timedelta, timezone
+from typing import Dict, List, Any, Optional, Tuple
 import numpy as np
+import math
+from collections import defaultdict
 
 logger = logging.getLogger(__name__)
 
 class TradingStats:
     """Comprehensive trading statistics tracker."""
     
-    def __init__(self, stats_file: str = "trading_stats.json"):
+    def __init__(self, stats_file: str = "data/trading_stats.json"):
         """Initialize the stats tracker."""
         self.stats_file = stats_file
-        self.data = self._load_stats()
         
-        # Initialize if first run
-        if not self.data:
-            self._initialize_stats()
+        # Ensure the data directory exists
+        data_dir = os.path.dirname(self.stats_file)
+        if data_dir and not os.path.exists(data_dir):
+            os.makedirs(data_dir)
+            logger.info(f"Created data directory: {data_dir}")
+            
+        self.data = self._load_stats()
+        self._initialize_if_empty()
     
     def _load_stats(self) -> Dict[str, Any]:
         """Load stats from file."""
         try:
             if os.path.exists(self.stats_file):
                 with open(self.stats_file, 'r') as f:
-                    return json.load(f)
+                    loaded_data = json.load(f)
+                    # Basic validation
+                    if isinstance(loaded_data, dict) and 'trades' in loaded_data:
+                        # Ensure necessary nested structures exist
+                        loaded_data.setdefault('positions', {})
+                        loaded_data.setdefault('daily_balances', [])
+                        loaded_data.setdefault('balance_adjustments', [])
+                        return loaded_data
+                    else:
+                        logger.warning(f"Stats file {self.stats_file} has invalid format. Re-initializing.")
+                        return {}
+        except json.JSONDecodeError as e:
+            logger.error(f"Error decoding JSON from {self.stats_file}: {e}. Re-initializing.")
+            return {}
         except Exception as e:
-            logger.error(f"Error loading stats: {e}")
+            logger.error(f"Error loading stats from {self.stats_file}: {e}")
         return {}
     
     def _save_stats(self):
         """Save stats to file."""
         try:
+            self.data['last_updated'] = datetime.now(timezone.utc).isoformat()
             with open(self.stats_file, 'w') as f:
-                json.dump(self.data, f, indent=2, default=str)
+                json.dump(self.data, f, indent=2)
         except Exception as e:
-            logger.error(f"Error saving stats: {e}")
+            logger.error(f"Error saving stats to {self.stats_file}: {e}")
     
-    def _initialize_stats(self):
-        """Initialize stats structure."""
-        self.data = {
-            'start_time': datetime.now().isoformat(),
-            'initial_balance': 0.0,
-            'trades': [],
-            'daily_balances': [],
-            'last_update': datetime.now().isoformat(),
-            'manual_trades_only': True,  # Flag to indicate manual trading
-            '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}}
-            'balance_adjustments': []  # List of balance adjustments
-        }
-        self._save_stats()
+    def _initialize_if_empty(self):
+        """Initialize the data structure if it's empty or missing keys."""
+        if not self.data or not all(k in self.data for k in ['trades', 'initial_balance', 'start_date']):
+            self.data = {
+                'trades': [],
+                'initial_balance': 0.0,
+                'start_date': datetime.now(timezone.utc).isoformat(),
+                'positions': {},  # For enhanced position tracking
+                'daily_balances': [], # Store {date, balance} for P&L over time
+                'balance_adjustments': [] # Store {type, amount, timestamp, id}
+            }
+            self._save_stats()
+            logger.info("Initialized new trading stats file.")
+        else:
+            # Ensure all necessary keys are present for older stats files
+            self.data.setdefault('positions', {})
+            self.data.setdefault('daily_balances', [])
+            self.data.setdefault('balance_adjustments', [])
+            logger.info(f"Loaded trading stats. Last update: {self.data.get('last_updated', 'N/A')}")
     
     def set_initial_balance(self, balance: float):
         """Set the initial balance when bot starts."""
         if self.data.get('initial_balance', 0) == 0:
             self.data['initial_balance'] = balance
-            self.data['start_time'] = datetime.now().isoformat()
+            self.data['start_date'] = datetime.now(timezone.utc).isoformat()
             logger.info(f"Initial balance set to: ${balance:.2f}")
             self._save_stats()
     
@@ -124,19 +147,21 @@ class TradingStats:
     def get_enhanced_position_state(self, symbol: str) -> Dict[str, Any]:
         """Get current enhanced position state for a symbol."""
         # Initialize enhanced_positions if not present (for backward compatibility)
-        if 'enhanced_positions' not in self.data:
-            self.data['enhanced_positions'] = {}
+        if 'positions' not in self.data:
+            self.data['positions'] = {}
         
-        if symbol not in self.data['enhanced_positions']:
-            self.data['enhanced_positions'][symbol] = {
+        if symbol not in self.data['positions']:
+            self.data['positions'][symbol] = {
                 'contracts': 0.0,
                 'avg_entry_price': 0.0,
                 'total_cost_basis': 0.0,
+                'total_sell_value': 0.0,
                 'entry_count': 0,
-                'entry_history': [],  # List of {price, amount, timestamp, side}
-                'last_update': datetime.now().isoformat()
+                'last_entry_timestamp': None,
+                'realized_pnl': 0.0,
+                'side': None
             }
-        return self.data['enhanced_positions'][symbol]
+        return self.data['positions'][symbol]
     
     def update_enhanced_position_state(self, symbol: str, side: str, amount: float, price: float, timestamp: str = None):
         """Update enhanced position state with a new trade and return action type."""
@@ -155,12 +180,7 @@ class TradingStats:
                 position['total_cost_basis'] += amount * price
                 position['avg_entry_price'] = position['total_cost_basis'] / position['contracts'] if position['contracts'] > 0 else 0
                 position['entry_count'] += 1
-                position['entry_history'].append({
-                    'price': price,
-                    'amount': amount,
-                    'timestamp': timestamp,
-                    'side': 'buy'
-                })
+                position['last_entry_timestamp'] = timestamp
                 
                 logger.info(f"📈 Enhanced position updated: {symbol} LONG {position['contracts']:.6f} @ avg ${position['avg_entry_price']:.2f}")
                 action_type = 'long_opened' if old_contracts == 0 else 'long_increased'
@@ -181,12 +201,7 @@ class TradingStats:
                         position['total_cost_basis'] = remaining_amount * price
                         position['avg_entry_price'] = price
                         position['entry_count'] = 1
-                        position['entry_history'] = [{
-                            'price': price,
-                            'amount': remaining_amount,
-                            'timestamp': timestamp,
-                            'side': 'buy'
-                        }]
+                        position['last_entry_timestamp'] = timestamp
                         action_type = 'short_closed_and_long_opened'
                 else:
                     action_type = 'short_reduced'
@@ -198,12 +213,7 @@ class TradingStats:
                 old_contracts = position['contracts']
                 position['contracts'] -= amount
                 position['entry_count'] += 1
-                position['entry_history'].append({
-                    'price': price,
-                    'amount': amount,
-                    'timestamp': timestamp,
-                    'side': 'sell'
-                })
+                position['last_entry_timestamp'] = timestamp
                 
                 logger.info(f"📉 Enhanced position updated: {symbol} SHORT {abs(position['contracts']):.6f} @ ${price:.2f}")
                 action_type = 'short_opened' if old_contracts == 0 else 'short_increased'
@@ -227,29 +237,24 @@ class TradingStats:
                         remaining_amount = amount - reduction
                         position['contracts'] = -remaining_amount
                         position['entry_count'] = 1
-                        position['entry_history'] = [{
-                            'price': price,
-                            'amount': remaining_amount,
-                            'timestamp': timestamp,
-                            'side': 'sell'
-                        }]
+                        position['last_entry_timestamp'] = timestamp
                         action_type = 'long_closed_and_short_opened'
         
-        position['last_update'] = timestamp
+        position['last_entry_timestamp'] = timestamp
         self._save_stats()
         return action_type
     
     def _reset_enhanced_position_state(self, symbol: str):
         """Reset enhanced position state when position is fully closed."""
-        if 'enhanced_positions' in self.data and symbol in self.data['enhanced_positions']:
-            del self.data['enhanced_positions'][symbol]
+        if 'positions' in self.data and symbol in self.data['positions']:
+            del self.data['positions'][symbol]
     
     def calculate_enhanced_position_pnl(self, symbol: str, exit_amount: float, exit_price: float) -> Dict[str, float]:
         """Calculate P&L for a position exit using enhanced tracking."""
         position = self.get_enhanced_position_state(symbol)
         
         if position['contracts'] == 0:
-            return {'pnl': 0.0, 'pnl_percent': 0.0, 'avg_entry_price': 0.0}
+            return {'pnl': 0.0, 'pnl_percent': 0.0, 'avg_entry_price': 0.0, 'closed_contracts': 0.0}
         
         avg_entry = position['avg_entry_price']
         
@@ -264,7 +269,8 @@ class TradingStats:
         return {
             'pnl': pnl,
             'pnl_percent': pnl_percent,
-            'avg_entry_price': avg_entry
+            'avg_entry_price': avg_entry,
+            'closed_contracts': exit_amount
         }
     
     def record_trade_with_enhanced_tracking(self, symbol: str, side: str, amount: float, price: float, 
@@ -328,7 +334,7 @@ class TradingStats:
         
         return trades_with_pnl
     
-    def get_basic_stats(self) -> Dict[str, Any]:
+    def get_basic_stats(self, current_balance: Optional[float] = None) -> Dict[str, Any]:
         """Get basic trading statistics."""
         if not self.data['trades']:
             return {
@@ -340,7 +346,7 @@ class TradingStats:
                 'current_balance': 0,
                 'total_pnl': 0,
                 'days_active': 0,
-                'start_date': datetime.now().strftime('%Y-%m-%d') if 'start_time' not in self.data else datetime.fromisoformat(self.data['start_time']).strftime('%Y-%m-%d'),
+                'start_date': datetime.now(timezone.utc).strftime('%Y-%m-%d') if 'start_date' not in self.data else datetime.fromisoformat(self.data['start_date']).strftime('%Y-%m-%d'),
                 'last_trade': None
             }
         
@@ -348,8 +354,8 @@ class TradingStats:
         total_pnl = sum(cycle['total_pnl'] for cycle in completed_cycles)
         
         # Calculate days active
-        start_date = datetime.fromisoformat(self.data['start_time'])
-        days_active = (datetime.now() - start_date).days + 1
+        start_date = datetime.fromisoformat(self.data['start_date'])
+        days_active = (datetime.now(timezone.utc) - start_date).days + 1
         
         return {
             'total_trades': len(self.data['trades']),  # Total individual orders placed
@@ -500,7 +506,7 @@ class TradingStats:
         if current_balance:
             self.record_balance(current_balance)
         
-        basic = self.get_basic_stats()
+        basic = self.get_basic_stats(current_balance)
         performance = self.get_performance_stats()
         risk = self.get_risk_metrics()
         
@@ -516,7 +522,7 @@ class TradingStats:
             'risk': risk,
             'current_balance': current_balance or 0,
             'total_return': total_return,
-            'last_updated': datetime.now().isoformat()
+            'last_updated': datetime.now(timezone.utc).isoformat()
         }
     
     def format_stats_message(self, current_balance: float = None) -> str:
@@ -559,8 +565,8 @@ class TradingStats:
 📈 <b>Trading Activity:</b>
 • Total Orders: {total_orders}
 • Completed Trades: {completed_trades}
-• 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}
+• Open Positions: {len([p for p in self.data.get('positions', {}).values() if p.get('contracts', 0) != 0])}
+• Days Active: {(datetime.now(timezone.utc) - datetime.fromisoformat(basic['start_date'])).days + 1}
 
 🏆 <b>Performance Metrics:</b>
 • Win Rate: {win_rate:.1f}% ({perf['total_wins']}W/{perf['total_losses']}L)
@@ -589,7 +595,7 @@ class TradingStats:
 
 ⏰ <b>Session Info:</b>
 • Started: {basic['start_date']}
-• Last Update: {datetime.now().strftime('%H:%M:%S')}
+• Last Update: {datetime.now(timezone.utc).strftime('%H:%M:%S')}
             """
             
             return stats_text.strip()

+ 1789 - 0
tests/demo_stats.json

@@ -0,0 +1,1789 @@
+{
+  "start_time": "2025-06-01T13:00:11.649332",
+  "initial_balance": 1000.0,
+  "trades": [
+    {
+      "timestamp": "2025-05-02T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.07968040071062454,
+      "price": 3195.8741815084513,
+      "value": 254.64853540333263,
+      "order_id": "demo_0",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-03T01:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.07968040071062454,
+      "price": 3253.7432559191616,
+      "value": 259.25956644113097,
+      "order_id": "demo_1",
+      "type": "manual",
+      "pnl": 4.611031037798342
+    },
+    {
+      "timestamp": "2025-05-02T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.05397581638789908,
+      "price": 3189.687616129785,
+      "value": 172.16599310297678,
+      "order_id": "demo_2",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-03T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.07857714211303082,
+      "price": 2822.808467425993,
+      "value": 221.808222102799,
+      "order_id": "demo_3",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-03T23:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.07857714211303082,
+      "price": 2791.8199657088935,
+      "value": 219.37323419950457,
+      "order_id": "demo_4",
+      "type": "manual",
+      "pnl": -2.4349879032944313
+    },
+    {
+      "timestamp": "2025-05-03T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.004479345962320734,
+      "price": 48967.2667560882,
+      "value": 219.341328629766,
+      "order_id": "demo_5",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-03T18:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.004479345962320734,
+      "price": 48059.68575930301,
+      "value": 215.2759593563372,
+      "order_id": "demo_6",
+      "type": "manual",
+      "pnl": -4.0653692734287645
+    },
+    {
+      "timestamp": "2025-05-03T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0919082456257589,
+      "price": 2886.2939279988927,
+      "value": 265.2742112826587,
+      "order_id": "demo_7",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-03T16:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.0919082456257589,
+      "price": 2989.084457874144,
+      "value": 274.7215085504352,
+      "order_id": "demo_8",
+      "type": "manual",
+      "pnl": 9.447297267776495
+    },
+    {
+      "timestamp": "2025-05-04T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.013373360625332661,
+      "price": 3089.3348367439535,
+      "value": 41.314788864180095,
+      "order_id": "demo_9",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-04T23:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.013373360625332661,
+      "price": 3237.0253620077638,
+      "value": 43.28990751947783,
+      "order_id": "demo_10",
+      "type": "manual",
+      "pnl": 1.9751186552977391
+    },
+    {
+      "timestamp": "2025-05-05T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.004771240470929578,
+      "price": 47153.601501897065,
+      "value": 224.981171835937,
+      "order_id": "demo_11",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-06T00:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.004771240470929578,
+      "price": 49154.16585107024,
+      "value": 234.52634542341096,
+      "order_id": "demo_12",
+      "type": "manual",
+      "pnl": 9.545173587473945
+    },
+    {
+      "timestamp": "2025-05-06T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.002324576566672756,
+      "price": 52853.496826768154,
+      "value": 122.86200019021813,
+      "order_id": "demo_13",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-07T01:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.002324576566672756,
+      "price": 55379.3692693349,
+      "value": 128.73358408061327,
+      "order_id": "demo_14",
+      "type": "manual",
+      "pnl": 5.871583890395135
+    },
+    {
+      "timestamp": "2025-05-06T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.006795526155242234,
+      "price": 49473.83250786384,
+      "value": 336.2007228072622,
+      "order_id": "demo_15",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-06T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.002945734301337783,
+      "price": 46818.36157388072,
+      "value": 137.91445362061523,
+      "order_id": "demo_16",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-06T17:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.002945734301337783,
+      "price": 47880.8173342238,
+      "value": 141.04416599751175,
+      "order_id": "demo_17",
+      "type": "manual",
+      "pnl": 3.129712376896527
+    },
+    {
+      "timestamp": "2025-05-07T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.006534748830570108,
+      "price": 47297.513320773345,
+      "value": 309.07736986179776,
+      "order_id": "demo_18",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-08T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.009476213545958418,
+      "price": 54660.55067280194,
+      "value": 517.9750507151523,
+      "order_id": "demo_19",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-09T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0037092252820775653,
+      "price": 49905.10705219338,
+      "value": 185.10928478278308,
+      "order_id": "demo_20",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-09T14:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.0037092252820775653,
+      "price": 50876.609299014934,
+      "value": 188.71280547828874,
+      "order_id": "demo_21",
+      "type": "manual",
+      "pnl": 3.6035206955056664
+    },
+    {
+      "timestamp": "2025-05-09T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.005823361738728476,
+      "price": 47747.58306778652,
+      "value": 278.05144835370766,
+      "order_id": "demo_22",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-09T21:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.005823361738728476,
+      "price": 47751.71971203377,
+      "value": 278.07553752954385,
+      "order_id": "demo_23",
+      "type": "manual",
+      "pnl": 0.02408917583616652
+    },
+    {
+      "timestamp": "2025-05-10T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.025888336322082435,
+      "price": 2804.2692543480525,
+      "value": 72.59786559423772,
+      "order_id": "demo_24",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-10T18:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.025888336322082435,
+      "price": 2871.8302683363604,
+      "value": 74.34690784662794,
+      "order_id": "demo_25",
+      "type": "manual",
+      "pnl": 1.7490422523902303
+    },
+    {
+      "timestamp": "2025-05-10T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0016563771129113785,
+      "price": 46911.7652367747,
+      "value": 77.70357426446525,
+      "order_id": "demo_26",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-10T18:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.0016563771129113785,
+      "price": 46235.08324527299,
+      "value": 76.58273370102252,
+      "order_id": "demo_27",
+      "type": "manual",
+      "pnl": -1.1208405634427265
+    },
+    {
+      "timestamp": "2025-05-11T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0871593529086579,
+      "price": 2856.984854492601,
+      "value": 249.0129511874112,
+      "order_id": "demo_28",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-12T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.05631886081887048,
+      "price": 2855.593387947315,
+      "value": 160.82376657109165,
+      "order_id": "demo_29",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-12T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.015528673567993576,
+      "price": 2901.028046476049,
+      "value": 45.04911754532066,
+      "order_id": "demo_30",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-12T19:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.015528673567993576,
+      "price": 2934.370800547883,
+      "value": 45.56688628916006,
+      "order_id": "demo_31",
+      "type": "manual",
+      "pnl": 0.5177687438393981
+    },
+    {
+      "timestamp": "2025-05-14T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.006477052880580735,
+      "price": 52003.98892980755,
+      "value": 336.83258629949864,
+      "order_id": "demo_32",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-14T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0014534599353703906,
+      "price": 47216.89837165532,
+      "value": 68.62787005565644,
+      "order_id": "demo_33",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-14T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.021765506144908506,
+      "price": 2927.598123947769,
+      "value": 63.72065495660778,
+      "order_id": "demo_34",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-16T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.06599132378680087,
+      "price": 2811.037524681868,
+      "value": 185.50408746812838,
+      "order_id": "demo_35",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-16T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.08953427138615268,
+      "price": 2866.2196408221234,
+      "value": 256.62488717368905,
+      "order_id": "demo_36",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-16T14:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.08953427138615268,
+      "price": 2821.6627044424617,
+      "value": 252.6355143397369,
+      "order_id": "demo_37",
+      "type": "manual",
+      "pnl": -3.989372833952169
+    },
+    {
+      "timestamp": "2025-05-16T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0607395201413988,
+      "price": 3143.9694306560023,
+      "value": 190.96319455727237,
+      "order_id": "demo_38",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-18T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.09536031909219933,
+      "price": 2916.411729786666,
+      "value": 278.10995315668947,
+      "order_id": "demo_39",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-18T23:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.09536031909219933,
+      "price": 2972.5920478148264,
+      "value": 283.4673262105561,
+      "order_id": "demo_40",
+      "type": "manual",
+      "pnl": 5.357373053866614
+    },
+    {
+      "timestamp": "2025-05-18T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.06618513260851003,
+      "price": 2854.834577159299,
+      "value": 188.94760506464786,
+      "order_id": "demo_41",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-19T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.07581891266736868,
+      "price": 3033.5610491924517,
+      "value": 230.0013002598538,
+      "order_id": "demo_42",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-19T20:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.07581891266736868,
+      "price": 3125.351344262589,
+      "value": 236.96074062548854,
+      "order_id": "demo_43",
+      "type": "manual",
+      "pnl": 6.959440365634741
+    },
+    {
+      "timestamp": "2025-05-19T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0036435964560123804,
+      "price": 53797.49834231611,
+      "value": 196.0163743023949,
+      "order_id": "demo_44",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-19T15:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.0036435964560123804,
+      "price": 55552.18137829273,
+      "value": 202.40973119370435,
+      "order_id": "demo_45",
+      "type": "manual",
+      "pnl": 6.3933568913094465
+    },
+    {
+      "timestamp": "2025-05-19T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.01009641486802869,
+      "price": 3197.5024818977936,
+      "value": 32.28331159879152,
+      "order_id": "demo_46",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-19T17:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.01009641486802869,
+      "price": 3317.0304380868142,
+      "value": 33.49011543280343,
+      "order_id": "demo_47",
+      "type": "manual",
+      "pnl": 1.2068038340119098
+    },
+    {
+      "timestamp": "2025-05-20T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.07897006064649197,
+      "price": 3054.7780763063042,
+      "value": 241.2360099474829,
+      "order_id": "demo_48",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-21T00:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.07897006064649197,
+      "price": 3032.692701423034,
+      "value": 239.49192655355057,
+      "order_id": "demo_49",
+      "type": "manual",
+      "pnl": -1.7440833939323506
+    },
+    {
+      "timestamp": "2025-05-20T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0051607703343509535,
+      "price": 50591.67280194242,
+      "value": 261.09200416145444,
+      "order_id": "demo_50",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-20T20:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.0051607703343509535,
+      "price": 52151.748798012806,
+      "value": 269.1431980813075,
+      "order_id": "demo_51",
+      "type": "manual",
+      "pnl": 8.051193919853068
+    },
+    {
+      "timestamp": "2025-05-21T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.008409240860812031,
+      "price": 46466.06328390287,
+      "value": 390.74431800807366,
+      "order_id": "demo_52",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-22T01:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.008409240860812031,
+      "price": 46296.86099301739,
+      "value": 389.3214551898165,
+      "order_id": "demo_53",
+      "type": "manual",
+      "pnl": -1.4228628182571634
+    },
+    {
+      "timestamp": "2025-05-22T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.031424724747812334,
+      "price": 2831.682652473089,
+      "value": 88.98484792712196,
+      "order_id": "demo_54",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-22T21:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.031424724747812334,
+      "price": 2915.700563911932,
+      "value": 91.62508766797367,
+      "order_id": "demo_55",
+      "type": "manual",
+      "pnl": 2.640239740851712
+    },
+    {
+      "timestamp": "2025-05-22T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.007106206035300662,
+      "price": 47624.67457208893,
+      "value": 338.43074987340833,
+      "order_id": "demo_56",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-22T20:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.007106206035300662,
+      "price": 47159.89735116395,
+      "value": 335.12794718100093,
+      "order_id": "demo_57",
+      "type": "manual",
+      "pnl": -3.302802692407373
+    },
+    {
+      "timestamp": "2025-05-22T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.005510892142533395,
+      "price": 48008.514037750545,
+      "value": 264.56974278534364,
+      "order_id": "demo_58",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-22T15:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.005510892142533395,
+      "price": 48887.516276189985,
+      "value": 269.4138293144288,
+      "order_id": "demo_59",
+      "type": "manual",
+      "pnl": 4.844086529085174
+    },
+    {
+      "timestamp": "2025-05-25T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.09447270966449066,
+      "price": 2847.459850596547,
+      "value": 269.0072477467015,
+      "order_id": "demo_60",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-25T20:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.09447270966449066,
+      "price": 2791.803268007009,
+      "value": 263.74921957880235,
+      "order_id": "demo_61",
+      "type": "manual",
+      "pnl": -5.258028167899143
+    },
+    {
+      "timestamp": "2025-05-27T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.08137565640135344,
+      "price": 3134.4533263825847,
+      "value": 255.06819689378855,
+      "order_id": "demo_62",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-28T00:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.08137565640135344,
+      "price": 3246.2232213857214,
+      "value": 264.16354546557915,
+      "order_id": "demo_63",
+      "type": "manual",
+      "pnl": 9.095348571790607
+    },
+    {
+      "timestamp": "2025-05-27T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.09355065954618957,
+      "price": 2963.893716915757,
+      "value": 277.27421204227636,
+      "order_id": "demo_64",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-28T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.00445577336554784,
+      "price": 46678.72262353221,
+      "value": 207.98980900373022,
+      "order_id": "demo_65",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-28T20:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.00445577336554784,
+      "price": 47451.951880753906,
+      "value": 211.43514333352098,
+      "order_id": "demo_66",
+      "type": "manual",
+      "pnl": 3.4453343297907795
+    },
+    {
+      "timestamp": "2025-05-29T13:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.009695725703817843,
+      "price": 45917.12600778308,
+      "value": 445.1998588791052,
+      "order_id": "demo_67",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-29T16:00:11.650949",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.009695725703817843,
+      "price": 47378.167117373734,
+      "value": 459.36571271969785,
+      "order_id": "demo_68",
+      "type": "manual",
+      "pnl": 14.165853840592636
+    },
+    {
+      "timestamp": "2025-05-29T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.03225139681073991,
+      "price": 3162.4257486105025,
+      "value": 101.99264770293853,
+      "order_id": "demo_69",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-29T15:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.03225139681073991,
+      "price": 3175.483396591684,
+      "value": 102.41377508939458,
+      "order_id": "demo_70",
+      "type": "manual",
+      "pnl": 0.4211273864560452
+    },
+    {
+      "timestamp": "2025-05-30T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.058867687447039074,
+      "price": 2871.491946000009,
+      "value": 169.03809038381854,
+      "order_id": "demo_71",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-31T13:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.036486591931006986,
+      "price": 2814.9789646654954,
+      "value": 102.70898877811847,
+      "order_id": "demo_72",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-31T15:00:11.650949",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.036486591931006986,
+      "price": 2864.349306985832,
+      "value": 104.51034431185471,
+      "order_id": "demo_73",
+      "type": "manual",
+      "pnl": 1.8013555337362417
+    },
+    {
+      "timestamp": "2025-05-03T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0033521588394246544,
+      "price": 54837.37245594949,
+      "value": 183.82358280903316,
+      "order_id": "demo_74",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-03T20:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.0033521588394246544,
+      "price": 54208.77895209817,
+      "value": 181.71643753869301,
+      "order_id": "demo_75",
+      "type": "manual",
+      "pnl": -2.1071452703401174
+    },
+    {
+      "timestamp": "2025-05-04T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.032396316418644884,
+      "price": 2800.7316843648573,
+      "value": 90.73338985040817,
+      "order_id": "demo_76",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-04T14:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.032396316418644884,
+      "price": 2749.1910614065223,
+      "value": 89.06366352063587,
+      "order_id": "demo_77",
+      "type": "manual",
+      "pnl": -1.6697263297722937
+    },
+    {
+      "timestamp": "2025-05-04T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.06967409034382549,
+      "price": 3179.6294195773603,
+      "value": 221.5377874395184,
+      "order_id": "demo_78",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-06T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.08790568306748972,
+      "price": 2852.73742875299,
+      "value": 250.7718322867259,
+      "order_id": "demo_79",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-06T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.08499477441915652,
+      "price": 2908.4274668004928,
+      "value": 247.20113645518674,
+      "order_id": "demo_80",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-06T21:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.08499477441915652,
+      "price": 2947.6673612870036,
+      "value": 250.5363224352992,
+      "order_id": "demo_81",
+      "type": "manual",
+      "pnl": 3.3351859801124912
+    },
+    {
+      "timestamp": "2025-05-06T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.028884783782118444,
+      "price": 3019.0891288771245,
+      "value": 87.20573670656006,
+      "order_id": "demo_82",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-07T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.002125054259770345,
+      "price": 51709.3769315301,
+      "value": 109.88523171841845,
+      "order_id": "demo_83",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-07T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.08212289513544505,
+      "price": 3110.5450409222954,
+      "value": 255.4469642097403,
+      "order_id": "demo_84",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-07T16:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.08212289513544505,
+      "price": 3101.225785950571,
+      "value": 254.6816400109569,
+      "order_id": "demo_85",
+      "type": "manual",
+      "pnl": -0.7653241987833772
+    },
+    {
+      "timestamp": "2025-05-08T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.07071435249130663,
+      "price": 3044.8152882942054,
+      "value": 215.31214156735587,
+      "order_id": "demo_86",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-08T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.008989255967184813,
+      "price": 45168.86127092907,
+      "value": 406.03445571064213,
+      "order_id": "demo_87",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-09T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.007020311619016966,
+      "price": 49487.453165594256,
+      "value": 347.4173424539793,
+      "order_id": "demo_88",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-09T23:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.007020311619016966,
+      "price": 49446.191375268725,
+      "value": 347.1276718279355,
+      "order_id": "demo_89",
+      "type": "manual",
+      "pnl": -0.28967062604376326
+    },
+    {
+      "timestamp": "2025-05-09T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.09976382865078823,
+      "price": 3025.575837952515,
+      "value": 301.8430294674597,
+      "order_id": "demo_90",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-09T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0023377965041430165,
+      "price": 46133.111549101,
+      "value": 107.84982690472813,
+      "order_id": "demo_91",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-10T01:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.0023377965041430165,
+      "price": 47994.410629237966,
+      "value": 112.20116538743694,
+      "order_id": "demo_92",
+      "type": "manual",
+      "pnl": 4.3513384827088135
+    },
+    {
+      "timestamp": "2025-05-12T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.06653449723711768,
+      "price": 3034.863355383511,
+      "value": 201.92310753379394,
+      "order_id": "demo_93",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-13T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.007160192454335488,
+      "price": 51551.207735312906,
+      "value": 369.1165686382687,
+      "order_id": "demo_94",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-13T17:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.007160192454335488,
+      "price": 50548.80901316162,
+      "value": 361.9392008716855,
+      "order_id": "demo_95",
+      "type": "manual",
+      "pnl": -7.177367766583181
+    },
+    {
+      "timestamp": "2025-05-13T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.08575215914530462,
+      "price": 3117.503193579421,
+      "value": 267.3326299918179,
+      "order_id": "demo_96",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-13T19:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.08575215914530462,
+      "price": 3117.7683031022684,
+      "value": 267.35536370581207,
+      "order_id": "demo_97",
+      "type": "manual",
+      "pnl": 0.02273371399415667
+    },
+    {
+      "timestamp": "2025-05-13T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.07047882591413089,
+      "price": 2801.3999180847522,
+      "value": 197.4393771425558,
+      "order_id": "demo_98",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-13T15:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.07047882591413089,
+      "price": 2869.7504230109535,
+      "value": 202.25664048039246,
+      "order_id": "demo_99",
+      "type": "manual",
+      "pnl": 4.817263337836681
+    },
+    {
+      "timestamp": "2025-05-14T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.07794240351681371,
+      "price": 2804.293825316272,
+      "value": 218.57340091250998,
+      "order_id": "demo_100",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-14T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.05395007187208047,
+      "price": 3155.79357153999,
+      "value": 170.25528999803197,
+      "order_id": "demo_101",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-14T22:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.05395007187208047,
+      "price": 3211.8916181799873,
+      "value": 173.28178364614317,
+      "order_id": "demo_102",
+      "type": "manual",
+      "pnl": 3.026493648111182
+    },
+    {
+      "timestamp": "2025-05-14T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.01528610471078623,
+      "price": 2839.2146873148363,
+      "value": 43.400533006696776,
+      "order_id": "demo_103",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-14T19:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.01528610471078623,
+      "price": 2869.4592205069503,
+      "value": 43.86285410800028,
+      "order_id": "demo_104",
+      "type": "manual",
+      "pnl": 0.46232110130350396
+    },
+    {
+      "timestamp": "2025-05-16T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.03930501157086138,
+      "price": 3121.777860630053,
+      "value": 122.70151493372312,
+      "order_id": "demo_105",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-16T20:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.03930501157086138,
+      "price": 3117.3865101213823,
+      "value": 122.52891285116812,
+      "order_id": "demo_106",
+      "type": "manual",
+      "pnl": -0.17260208255501033
+    },
+    {
+      "timestamp": "2025-05-16T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.061433160492361916,
+      "price": 3076.1977380951575,
+      "value": 188.98054935064053,
+      "order_id": "demo_107",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-16T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.009397803174073533,
+      "price": 53925.20047214757,
+      "value": 506.7784201597,
+      "order_id": "demo_108",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-17T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.009034893061948292,
+      "price": 47709.705042732436,
+      "value": 431.05208307818276,
+      "order_id": "demo_109",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-17T20:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.009034893061948292,
+      "price": 50069.95638129586,
+      "value": 452.37670152142357,
+      "order_id": "demo_110",
+      "type": "manual",
+      "pnl": 21.32461844324084
+    },
+    {
+      "timestamp": "2025-05-20T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0037200348235427814,
+      "price": 46877.44342504467,
+      "value": 174.38572197982276,
+      "order_id": "demo_111",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-20T22:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.0037200348235427814,
+      "price": 47522.6097173067,
+      "value": 176.7857630540135,
+      "order_id": "demo_112",
+      "type": "manual",
+      "pnl": 2.4000410741907343
+    },
+    {
+      "timestamp": "2025-05-21T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.001985689749127183,
+      "price": 54117.71894227799,
+      "value": 107.4609997498274,
+      "order_id": "demo_113",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-21T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.01018572153915191,
+      "price": 3153.9586894493455,
+      "value": 32.12534495671953,
+      "order_id": "demo_114",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-22T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.04577773987137663,
+      "price": 2814.5055878315356,
+      "value": 128.841704666288,
+      "order_id": "demo_115",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-25T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.08087865776229979,
+      "price": 2933.8187008227137,
+      "value": 237.28331864047524,
+      "order_id": "demo_116",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-26T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0015594068304559236,
+      "price": 49022.480685421855,
+      "value": 76.44599122674043,
+      "order_id": "demo_117",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-27T00:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.0015594068304559236,
+      "price": 48358.21804638505,
+      "value": 75.41013553020976,
+      "order_id": "demo_118",
+      "type": "manual",
+      "pnl": -1.0358556965306671
+    },
+    {
+      "timestamp": "2025-05-27T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0017331448029555732,
+      "price": 48393.78646266766,
+      "value": 83.87343950311423,
+      "order_id": "demo_119",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-27T18:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.0017331448029555732,
+      "price": 47855.38957445567,
+      "value": 82.94031973438217,
+      "order_id": "demo_120",
+      "type": "manual",
+      "pnl": -0.9331197687320596
+    },
+    {
+      "timestamp": "2025-05-27T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.016702731974013233,
+      "price": 3146.8991169589603,
+      "value": 52.561812499824434,
+      "order_id": "demo_121",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-27T16:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.016702731974013233,
+      "price": 3117.7938012065392,
+      "value": 52.07567421179272,
+      "order_id": "demo_122",
+      "type": "manual",
+      "pnl": -0.48613828803171527
+    },
+    {
+      "timestamp": "2025-05-27T13:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "buy",
+      "amount": 0.013738470343242423,
+      "price": 3092.4976585881095,
+      "value": 42.48618736905937,
+      "order_id": "demo_123",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-27T15:07:49.891867",
+      "symbol": "ETH/USDC:USDC",
+      "side": "sell",
+      "amount": 0.013738470343242423,
+      "price": 3093.3687748427715,
+      "value": 42.498155173889565,
+      "order_id": "demo_124",
+      "type": "manual",
+      "pnl": 0.0119678048301897
+    },
+    {
+      "timestamp": "2025-05-28T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.009841470672700286,
+      "price": 45561.07339252519,
+      "value": 448.387967609282,
+      "order_id": "demo_125",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-28T21:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.009841470672700286,
+      "price": 46107.84278624627,
+      "value": 453.7689825623181,
+      "order_id": "demo_126",
+      "type": "manual",
+      "pnl": 5.381014953036113
+    },
+    {
+      "timestamp": "2025-05-28T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0036234981652747316,
+      "price": 49982.130097937465,
+      "value": 181.11015670639935,
+      "order_id": "demo_127",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-29T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.004273788409769072,
+      "price": 52729.446889668245,
+      "value": 225.35449897059797,
+      "order_id": "demo_128",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-29T22:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.004273788409769072,
+      "price": 54880.72864310026,
+      "value": 234.5486219945634,
+      "order_id": "demo_129",
+      "type": "manual",
+      "pnl": 9.194123023965423
+    },
+    {
+      "timestamp": "2025-05-29T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.009153831398851914,
+      "price": 52285.39755145657,
+      "value": 478.61171380797816,
+      "order_id": "demo_130",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-29T20:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "sell",
+      "amount": 0.009153831398851914,
+      "price": 51580.60150515855,
+      "value": 472.16012962958865,
+      "order_id": "demo_131",
+      "type": "manual",
+      "pnl": -6.451584178389495
+    },
+    {
+      "timestamp": "2025-05-31T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.004655245384732312,
+      "price": 48846.2970447554,
+      "value": 227.39149887886114,
+      "order_id": "demo_132",
+      "type": "manual",
+      "pnl": 0.0
+    },
+    {
+      "timestamp": "2025-05-31T13:07:49.891867",
+      "symbol": "BTC/USDC:USDC",
+      "side": "buy",
+      "amount": 0.0054566374558719394,
+      "price": 49455.226810725966,
+      "value": 269.85924300404946,
+      "order_id": "demo_133",
+      "type": "manual",
+      "pnl": 0.0
+    }
+  ],
+  "daily_balances": [
+    {
+      "date": "2025-05-02",
+      "balance": 1009.4136235522218,
+      "timestamp": "2025-05-02T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-03",
+      "balance": 997.2616343671272,
+      "timestamp": "2025-05-03T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-04",
+      "balance": 1006.607687384254,
+      "timestamp": "2025-05-04T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-05",
+      "balance": 1018.1746605922912,
+      "timestamp": "2025-05-05T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-06",
+      "balance": 1045.5251551471488,
+      "timestamp": "2025-05-06T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-07",
+      "balance": 1033.5718742791619,
+      "timestamp": "2025-05-07T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-08",
+      "balance": 1061.8858331332183,
+      "timestamp": "2025-05-08T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-09",
+      "balance": 1051.3839536063972,
+      "timestamp": "2025-05-09T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-10",
+      "balance": 1049.1915997785413,
+      "timestamp": "2025-05-10T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-11",
+      "balance": 1032.0098252452372,
+      "timestamp": "2025-05-11T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-12",
+      "balance": 1062.1786914476386,
+      "timestamp": "2025-05-12T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-13",
+      "balance": 1060.4065147770173,
+      "timestamp": "2025-05-13T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-14",
+      "balance": 1085.3792713310338,
+      "timestamp": "2025-05-14T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-15",
+      "balance": 1107.0913825789532,
+      "timestamp": "2025-05-15T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-16",
+      "balance": 1117.3660863840544,
+      "timestamp": "2025-05-16T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-17",
+      "balance": 1134.8658150063777,
+      "timestamp": "2025-05-17T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-18",
+      "balance": 1135.463265971198,
+      "timestamp": "2025-05-18T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-19",
+      "balance": 1137.6640263708298,
+      "timestamp": "2025-05-19T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-20",
+      "balance": 1130.8384424036697,
+      "timestamp": "2025-05-20T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-21",
+      "balance": 1148.9865696311822,
+      "timestamp": "2025-05-21T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-22",
+      "balance": 1182.139767832545,
+      "timestamp": "2025-05-22T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-23",
+      "balance": 1194.5740403469038,
+      "timestamp": "2025-05-23T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-24",
+      "balance": 1211.292487882684,
+      "timestamp": "2025-05-24T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-25",
+      "balance": 1197.2302275279906,
+      "timestamp": "2025-05-25T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-26",
+      "balance": 1185.866408700501,
+      "timestamp": "2025-05-26T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-27",
+      "balance": 1184.868515771586,
+      "timestamp": "2025-05-27T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-28",
+      "balance": 1200.142691866494,
+      "timestamp": "2025-05-28T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-29",
+      "balance": 1231.7311942814806,
+      "timestamp": "2025-05-29T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-30",
+      "balance": 1261.3464667196076,
+      "timestamp": "2025-05-30T13:00:11.650949"
+    },
+    {
+      "date": "2025-05-31",
+      "balance": 1285.1563807582047,
+      "timestamp": "2025-05-31T13:00:11.650949"
+    },
+    {
+      "date": "2025-06-01",
+      "balance": 1326.199606403431,
+      "timestamp": "2025-06-01T13:07:49.893261"
+    },
+    {
+      "date": "2025-05-02",
+      "balance": 983.7276340650023,
+      "timestamp": "2025-05-02T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-03",
+      "balance": 969.4267081501913,
+      "timestamp": "2025-05-03T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-04",
+      "balance": 977.3533174792553,
+      "timestamp": "2025-05-04T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-05",
+      "balance": 989.6176935613455,
+      "timestamp": "2025-05-05T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-06",
+      "balance": 979.9811947559465,
+      "timestamp": "2025-05-06T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-07",
+      "balance": 1003.9405940788333,
+      "timestamp": "2025-05-07T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-08",
+      "balance": 1031.7843911332445,
+      "timestamp": "2025-05-08T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-09",
+      "balance": 1043.2822474100687,
+      "timestamp": "2025-05-09T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-10",
+      "balance": 1060.126826409171,
+      "timestamp": "2025-05-10T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-11",
+      "balance": 1085.982200336003,
+      "timestamp": "2025-05-11T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-12",
+      "balance": 1071.7984776044532,
+      "timestamp": "2025-05-12T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-13",
+      "balance": 1060.7417641549316,
+      "timestamp": "2025-05-13T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-14",
+      "balance": 1078.9585869120694,
+      "timestamp": "2025-05-14T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-15",
+      "balance": 1090.5731163039657,
+      "timestamp": "2025-05-15T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-16",
+      "balance": 1105.0612861284387,
+      "timestamp": "2025-05-16T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-17",
+      "balance": 1155.3969135673965,
+      "timestamp": "2025-05-17T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-18",
+      "balance": 1182.9435516178885,
+      "timestamp": "2025-05-18T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-19",
+      "balance": 1163.7509735259455,
+      "timestamp": "2025-05-19T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-20",
+      "balance": 1170.8456015349104,
+      "timestamp": "2025-05-20T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-21",
+      "balance": 1188.0998565611892,
+      "timestamp": "2025-05-21T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-22",
+      "balance": 1198.0694023687886,
+      "timestamp": "2025-05-22T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-23",
+      "balance": 1219.0491722203544,
+      "timestamp": "2025-05-23T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-24",
+      "balance": 1209.3031333220576,
+      "timestamp": "2025-05-24T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-25",
+      "balance": 1234.05085714286,
+      "timestamp": "2025-05-25T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-26",
+      "balance": 1231.6910031431223,
+      "timestamp": "2025-05-26T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-27",
+      "balance": 1256.101814107773,
+      "timestamp": "2025-05-27T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-28",
+      "balance": 1266.148708920409,
+      "timestamp": "2025-05-28T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-29",
+      "balance": 1284.698713082611,
+      "timestamp": "2025-05-29T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-30",
+      "balance": 1309.5961555510478,
+      "timestamp": "2025-05-30T13:07:49.891867"
+    },
+    {
+      "date": "2025-05-31",
+      "balance": 1326.199606403431,
+      "timestamp": "2025-05-31T13:07:49.891867"
+    }
+  ],
+  "last_update": "2025-06-01T13:00:11.649126",
+  "manual_trades_only": true
+}

+ 6 - 4
trading_bot.py

@@ -125,18 +125,20 @@ class BotManager:
     
     def check_stats_persistence(self):
         """Check and report on statistics persistence."""
-        stats_file = "trading_stats.json"
+        stats_file = "data/trading_stats.json"
         if os.path.exists(stats_file):
             try:
                 stats = TradingStats()
                 basic_stats = stats.get_basic_stats()
-                self.logger.info(f"📊 Existing stats found - {basic_stats['total_trades']} trades since {basic_stats.get('start_date', 'unknown')}")
+                total_trades = basic_stats.get('total_trades_count', basic_stats.get('total_trades', 0))
+                start_date = basic_stats.get('start_date_formatted', basic_stats.get('start_date', 'unknown'))
+                self.logger.info(f"📊 Existing stats found - {total_trades} trades since {start_date}")
                 return True
             except Exception as e:
-                self.logger.warning(f"⚠️ Stats file exists but couldn't load: {e}")
+                self.logger.warning(f"⚠️ Stats file {stats_file} exists but couldn't load: {e}")
                 return False
         else:
-            self.logger.info("📊 No existing stats - will create new tracking from launch")
+            self.logger.info(f"📊 No existing stats file at {stats_file} - will create new tracking from launch")
             return False
     
     async def run_bot(self):