Jelajahi Sumber

Update bot version to 2.1.2 and refactor type imports across modules - Removed unused imports in alarm_manager, hyperliquid_client, logging_config, and telegram_bot. Enhanced trading_stats to calculate completed trade cycles, improving performance metrics and reporting accuracy.

Carles Sentis 5 hari lalu
induk
melakukan
65b089e7f9
6 mengubah file dengan 203 tambahan dan 97 penghapusan
  1. 1 2
      src/alarm_manager.py
  2. 1 2
      src/hyperliquid_client.py
  3. 0 1
      src/logging_config.py
  4. 13 6
      src/telegram_bot.py
  5. 187 85
      src/trading_stats.py
  6. 1 1
      trading_bot.py

+ 1 - 2
src/alarm_manager.py

@@ -9,8 +9,7 @@ import json
 import os
 import os
 import logging
 import logging
 from datetime import datetime
 from datetime import datetime
-from typing import Dict, List, Any, Optional, Tuple
-from config import Config
+from typing import Dict, List, Any, Optional
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 

+ 1 - 2
src/hyperliquid_client.py

@@ -1,7 +1,6 @@
-import asyncio
 import logging
 import logging
 from typing import Optional, Dict, Any, List
 from typing import Optional, Dict, Any, List
-from hyperliquid import HyperliquidSync, HyperliquidAsync
+from hyperliquid import HyperliquidSync
 from config import Config
 from config import Config
 
 
 # Use existing logger setup (will be configured by main application)
 # Use existing logger setup (will be configured by main application)

+ 0 - 1
src/logging_config.py

@@ -5,7 +5,6 @@ Logging configuration module with rotation and cleanup support.
 
 
 import logging
 import logging
 import logging.handlers
 import logging.handlers
-import os
 from pathlib import Path
 from pathlib import Path
 from datetime import datetime, timedelta
 from datetime import datetime, timedelta
 from typing import Optional
 from typing import Optional

+ 13 - 6
src/telegram_bot.py

@@ -6,13 +6,11 @@ This module provides a Telegram interface for manual Hyperliquid trading
 with comprehensive statistics tracking and phone-friendly controls.
 with comprehensive statistics tracking and phone-friendly controls.
 """
 """
 
 
-import logging
 import asyncio
 import asyncio
-import re
 import json
 import json
 import os
 import os
 from datetime import datetime, timedelta
 from datetime import datetime, timedelta
-from typing import Optional, Dict, Any, List, Tuple
+from typing import Optional, Dict, Any
 from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, KeyboardButton
 from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, KeyboardButton
 from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes, MessageHandler, filters
 from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes, MessageHandler, filters
 from hyperliquid_client import HyperliquidClient
 from hyperliquid_client import HyperliquidClient
@@ -617,10 +615,19 @@ Tap any button below for instant access to bot functions:
                     symbol = position.get('symbol', 'Unknown')
                     symbol = position.get('symbol', 'Unknown')
                     contracts = float(position.get('contracts', 0))
                     contracts = float(position.get('contracts', 0))
                     unrealized_pnl = float(position.get('unrealizedPnl', 0))
                     unrealized_pnl = float(position.get('unrealizedPnl', 0))
-                    entry_price = float(position.get('entryPx', 0))
                     
                     
-                    # Calculate position value and P&L percentage
-                    position_value = abs(contracts) * entry_price
+                    # Use the correct field name for entry price
+                    entry_price = float(position.get('entryPrice', 0))  # Changed from 'entryPx' to 'entryPrice'
+                    
+                    # Try to get position value from notional first, then calculate if not available
+                    notional = position.get('notional')
+                    if notional is not None:
+                        position_value = float(notional)
+                    else:
+                        # Fallback calculation
+                        position_value = abs(contracts) * entry_price
+                    
+                    # Calculate P&L percentage based on position value
                     pnl_percentage = (unrealized_pnl / position_value * 100) if position_value > 0 else 0
                     pnl_percentage = (unrealized_pnl / position_value * 100) if position_value > 0 else 0
                     
                     
                     pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
                     pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"

+ 187 - 85
src/trading_stats.py

@@ -15,9 +15,8 @@ import json
 import os
 import os
 import logging
 import logging
 from datetime import datetime, timedelta
 from datetime import datetime, timedelta
-from typing import Dict, List, Any, Optional, Tuple
+from typing import Dict, List, Any
 import numpy as np
 import numpy as np
-from config import Config
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -345,17 +344,16 @@ class TradingStats:
                 'last_trade': None
                 'last_trade': None
             }
             }
         
         
-        trades_with_pnl = self.calculate_trade_pnl()
-        completed_trades = [t for t in trades_with_pnl if t.get('pnl', 0) != 0]
-        total_pnl = sum(trade.get('pnl', 0) for trade in completed_trades)
+        completed_cycles = self.calculate_completed_trade_cycles()
+        total_pnl = sum(cycle['total_pnl'] for cycle in completed_cycles)
         
         
         # Calculate days active
         # Calculate days active
         start_date = datetime.fromisoformat(self.data['start_time'])
         start_date = datetime.fromisoformat(self.data['start_time'])
         days_active = (datetime.now() - start_date).days + 1
         days_active = (datetime.now() - start_date).days + 1
         
         
         return {
         return {
-            'total_trades': len(self.data['trades']),  # Total orders placed
-            'completed_trades': len(completed_trades),  # Actual completed trade cycles with P&L
+            'total_trades': len(self.data['trades']),  # Total individual orders placed
+            'completed_trades': len(completed_cycles),  # Actual completed trade cycles
             'buy_trades': len([t for t in self.data['trades'] if t['side'] == 'buy']),
             'buy_trades': len([t for t in self.data['trades'] if t['side'] == 'buy']),
             'sell_trades': len([t for t in self.data['trades'] if t['side'] == 'sell']),
             'sell_trades': len([t for t in self.data['trades'] if t['side'] == 'sell']),
             'initial_balance': self.data.get('initial_balance', 0),
             'initial_balance': self.data.get('initial_balance', 0),
@@ -367,10 +365,9 @@ class TradingStats:
     
     
     def get_performance_stats(self) -> Dict[str, Any]:
     def get_performance_stats(self) -> Dict[str, Any]:
         """Calculate advanced performance statistics."""
         """Calculate advanced performance statistics."""
-        trades_with_pnl = self.calculate_trade_pnl()
-        completed_trades = [t for t in trades_with_pnl if t.get('pnl', 0) != 0]
+        completed_cycles = self.calculate_completed_trade_cycles()
         
         
-        if not completed_trades:
+        if not completed_cycles:
             return {
             return {
                 'win_rate': 0,
                 'win_rate': 0,
                 'profit_factor': 0,
                 'profit_factor': 0,
@@ -385,9 +382,9 @@ class TradingStats:
                 'expectancy': 0
                 'expectancy': 0
             }
             }
         
         
-        # Separate wins and losses
-        wins = [t['pnl'] for t in completed_trades if t['pnl'] > 0]
-        losses = [abs(t['pnl']) for t in completed_trades if t['pnl'] < 0]
+        # Separate wins and losses by trade cycles
+        wins = [c['total_pnl'] for c in completed_cycles if c['total_pnl'] > 0]
+        losses = [abs(c['total_pnl']) for c in completed_cycles if c['total_pnl'] < 0]
         
         
         # Basic metrics
         # Basic metrics
         total_wins = len(wins)
         total_wins = len(wins)
@@ -407,14 +404,14 @@ class TradingStats:
         largest_win = max(wins) if wins else 0
         largest_win = max(wins) if wins else 0
         largest_loss = max(losses) if losses else 0
         largest_loss = max(losses) if losses else 0
         
         
-        # Consecutive wins/losses
+        # Consecutive wins/losses (by trade cycles)
         consecutive_wins = 0
         consecutive_wins = 0
         consecutive_losses = 0
         consecutive_losses = 0
         current_wins = 0
         current_wins = 0
         current_losses = 0
         current_losses = 0
         
         
-        for trade in completed_trades:
-            if trade['pnl'] > 0:
+        for cycle in completed_cycles:
+            if cycle['total_pnl'] > 0:
                 current_wins += 1
                 current_wins += 1
                 current_losses = 0
                 current_losses = 0
                 consecutive_wins = max(consecutive_wins, current_wins)
                 consecutive_wins = max(consecutive_wins, current_wins)
@@ -616,77 +613,81 @@ Please try again in a moment. If the issue persists, contact support.
 
 
     def get_token_performance(self) -> Dict[str, Dict[str, Any]]:
     def get_token_performance(self) -> Dict[str, Dict[str, Any]]:
         """Get performance statistics grouped by token."""
         """Get performance statistics grouped by token."""
-        trades_with_pnl = self.calculate_trade_pnl()
+        completed_cycles = self.calculate_completed_trade_cycles()
         
         
-        # Group trades by token
-        token_trades = {}
+        # Group cycles by token
+        token_cycles = {}
         
         
-        for trade in trades_with_pnl:
-            symbol = trade['symbol']
-            token = symbol.split('/')[0] if '/' in symbol else symbol
+        for cycle in completed_cycles:
+            token = cycle['token']
             
             
-            if token not in token_trades:
-                token_trades[token] = []
-            token_trades[token].append(trade)
+            if token not in token_cycles:
+                token_cycles[token] = []
+            token_cycles[token].append(cycle)
         
         
         # Calculate performance for each token
         # Calculate performance for each token
         token_performance = {}
         token_performance = {}
         
         
-        for token, trades in token_trades.items():
-            completed_trades = [t for t in trades if t.get('pnl', 0) != 0]
+        for token, cycles in token_cycles.items():
+            # Only include tokens that have actual completed trade cycles
+            if not cycles:
+                continue  # Skip tokens with no completed cycles
             
             
-            # Only include tokens that have actual completed trades with P&L
-            if not completed_trades:
-                continue  # Skip tokens with no completed trades
-            
-            # Calculate metrics for completed trades
-            total_pnl = sum(t['pnl'] for t in completed_trades)
-            total_volume = sum(t['value'] for t in completed_trades)
+            # Calculate metrics for completed cycles
+            total_pnl = sum(c['total_pnl'] for c in cycles)
+            total_volume = sum(c['sell_value'] for c in cycles)  # Use sell value for volume
             pnl_percentage = (total_pnl / total_volume * 100) if total_volume > 0 else 0.0
             pnl_percentage = (total_pnl / total_volume * 100) if total_volume > 0 else 0.0
             
             
             # Win/loss analysis
             # Win/loss analysis
-            wins = [t['pnl'] for t in completed_trades if t['pnl'] > 0]
-            losses = [abs(t['pnl']) for t in completed_trades if t['pnl'] < 0]
+            wins = [c['total_pnl'] for c in cycles if c['total_pnl'] > 0]
+            losses = [abs(c['total_pnl']) for c in cycles if c['total_pnl'] < 0]
             
             
             total_wins = len(wins)
             total_wins = len(wins)
             total_losses = len(losses)
             total_losses = len(losses)
             total_completed = total_wins + total_losses
             total_completed = total_wins + total_losses
             
             
-            win_rate = (total_wins / total_completed * 100) if total_completed > 0 else 0
-            avg_win = sum(wins) / total_wins if wins else 0
-            avg_loss = sum(losses) / total_losses if losses else 0
-            largest_win = max(wins) if wins else 0
-            largest_loss = max(losses) if losses else 0
+            win_rate = (total_wins / total_completed * 100) if total_completed > 0 else 0.0
             
             
+            # Calculate profit factor
             total_profit = sum(wins) if wins else 0
             total_profit = sum(wins) if wins else 0
-            total_loss_amount = sum(losses) if losses else 0
-            profit_factor = (total_profit / total_loss_amount) if total_loss_amount > 0 else float('inf') if total_profit > 0 else 0
+            total_loss = sum(losses) if losses else 0
+            profit_factor = (total_profit / total_loss) if total_loss > 0 else float('inf') if total_profit > 0 else 0
+            
+            # Calculate expectancy
+            avg_win = total_profit / total_wins if total_wins > 0 else 0
+            avg_loss = total_loss / total_losses if total_losses > 0 else 0
+            expectancy = avg_win * (win_rate / 100) - avg_loss * ((100 - win_rate) / 100)
+            
+            # Largest win/loss
+            largest_win = max(wins) if wins else 0
+            largest_loss = max(losses) if losses else 0
             
             
             token_performance[token] = {
             token_performance[token] = {
                 'total_pnl': total_pnl,
                 'total_pnl': total_pnl,
                 'pnl_percentage': pnl_percentage,
                 'pnl_percentage': pnl_percentage,
-                'total_trades': len(trades),
-                'completed_trades': total_completed,
-                'buy_trades': len([t for t in trades if t['side'] == 'buy']),
-                'sell_trades': len([t for t in trades if t['side'] == 'sell']),
+                'completed_trades': len(cycles),  # Now correctly counts trade cycles
+                'total_volume': total_volume,
                 'win_rate': win_rate,
                 'win_rate': win_rate,
-                'avg_win': avg_win,
-                'avg_loss': avg_loss,
+                'total_wins': total_wins,
+                'total_losses': total_losses,
+                'profit_factor': profit_factor,
+                'expectancy': expectancy,
                 'largest_win': largest_win,
                 'largest_win': largest_win,
                 'largest_loss': largest_loss,
                 'largest_loss': largest_loss,
-                'total_volume': total_volume,
-                'profit_factor': profit_factor,
-                'total_wins': total_wins,
-                'total_losses': total_losses
+                'avg_win': avg_win,
+                'avg_loss': avg_loss,
+                'cycles': cycles  # Include cycle details for further analysis
             }
             }
         
         
         return token_performance
         return token_performance
 
 
     def get_token_detailed_stats(self, token: str) -> Dict[str, Any]:
     def get_token_detailed_stats(self, token: str) -> Dict[str, Any]:
         """Get detailed statistics for a specific token."""
         """Get detailed statistics for a specific token."""
+        completed_cycles = self.calculate_completed_trade_cycles()
         trades_with_pnl = self.calculate_trade_pnl()
         trades_with_pnl = self.calculate_trade_pnl()
         
         
-        # Filter trades for this token
+        # Filter cycles and trades for this token
+        token_cycles = [c for c in completed_cycles if c['token'].upper() == token.upper()]
         token_trades = []
         token_trades = []
         for trade in trades_with_pnl:
         for trade in trades_with_pnl:
             symbol = trade['symbol']
             symbol = trade['symbol']
@@ -703,40 +704,38 @@ Please try again in a moment. If the issue persists, contact support.
                 'message': f"No trading history found for {token.upper()}"
                 'message': f"No trading history found for {token.upper()}"
             }
             }
         
         
-        completed_trades = [t for t in token_trades if t.get('pnl', 0) != 0]
-        
-        # Basic trade info
+        # Basic trade info (individual orders)
         total_trades = len(token_trades)
         total_trades = len(token_trades)
         buy_trades = len([t for t in token_trades if t['side'] == 'buy'])
         buy_trades = len([t for t in token_trades if t['side'] == 'buy'])
         sell_trades = len([t for t in token_trades if t['side'] == 'sell'])
         sell_trades = len([t for t in token_trades if t['side'] == 'sell'])
         total_volume = sum(t['value'] for t in token_trades)
         total_volume = sum(t['value'] for t in token_trades)
         
         
-        if not completed_trades:
+        if not token_cycles:
             return {
             return {
                 'token': token.upper(),
                 'token': token.upper(),
                 'total_trades': total_trades,
                 'total_trades': total_trades,
                 'buy_trades': buy_trades,
                 'buy_trades': buy_trades,
                 'sell_trades': sell_trades,
                 'sell_trades': sell_trades,
                 'total_volume': total_volume,
                 'total_volume': total_volume,
-                'completed_trades': 0,
+                'completed_trades': 0,  # No completed cycles
                 'total_pnl': 0.0,
                 'total_pnl': 0.0,
                 'pnl_percentage': 0.0,
                 'pnl_percentage': 0.0,
                 'win_rate': 0.0,
                 'win_rate': 0.0,
-                'message': f"{token.upper()} has open positions but no completed trades yet"
+                'message': f"{token.upper()} has open positions but no completed trade cycles yet"
             }
             }
         
         
-        # Performance calculations
-        total_pnl = sum(t['pnl'] for t in completed_trades)
-        completed_volume = sum(t['value'] for t in completed_trades)
+        # Performance calculations based on completed cycles
+        total_pnl = sum(c['total_pnl'] for c in token_cycles)
+        completed_volume = sum(c['sell_value'] for c in token_cycles)
         pnl_percentage = (total_pnl / completed_volume * 100) if completed_volume > 0 else 0.0
         pnl_percentage = (total_pnl / completed_volume * 100) if completed_volume > 0 else 0.0
         
         
-        # Win/loss analysis
-        wins = [t['pnl'] for t in completed_trades if t['pnl'] > 0]
-        losses = [abs(t['pnl']) for t in completed_trades if t['pnl'] < 0]
+        # Win/loss analysis (by trade cycles)
+        wins = [c['total_pnl'] for c in token_cycles if c['total_pnl'] > 0]
+        losses = [abs(c['total_pnl']) for c in token_cycles if c['total_pnl'] < 0]
         
         
         total_wins = len(wins)
         total_wins = len(wins)
         total_losses = len(losses)
         total_losses = len(losses)
-        completed_count = total_wins + total_losses
+        completed_count = len(token_cycles)  # Number of completed cycles
         
         
         win_rate = (total_wins / completed_count * 100) if completed_count > 0 else 0
         win_rate = (total_wins / completed_count * 100) if completed_count > 0 else 0
         avg_win = sum(wins) / total_wins if wins else 0
         avg_win = sum(wins) / total_wins if wins else 0
@@ -749,13 +748,13 @@ Please try again in a moment. If the issue persists, contact support.
         profit_factor = (total_profit / total_loss_amount) if total_loss_amount > 0 else float('inf') if total_profit > 0 else 0
         profit_factor = (total_profit / total_loss_amount) if total_loss_amount > 0 else float('inf') if total_profit > 0 else 0
         expectancy = avg_win * (win_rate/100) - avg_loss * ((100-win_rate)/100)
         expectancy = avg_win * (win_rate/100) - avg_loss * ((100-win_rate)/100)
         
         
-        # Recent trades
-        recent_trades = token_trades[-5:]  # Last 5 trades
+        # Recent trades (still show individual orders for detail)
+        recent_trades = token_trades[-5:]  # Last 5 individual orders
         
         
         return {
         return {
             'token': token.upper(),
             'token': token.upper(),
-            'total_trades': total_trades,
-            'completed_trades': completed_count,
+            'total_trades': total_trades,  # Total individual orders
+            'completed_trades': completed_count,  # Number of completed cycles
             'buy_trades': buy_trades,
             'buy_trades': buy_trades,
             'sell_trades': sell_trades,
             'sell_trades': sell_trades,
             'total_volume': total_volume,
             'total_volume': total_volume,
@@ -771,25 +770,25 @@ Please try again in a moment. If the issue persists, contact support.
             'expectancy': expectancy,
             'expectancy': expectancy,
             'total_wins': total_wins,
             'total_wins': total_wins,
             'total_losses': total_losses,
             'total_losses': total_losses,
-            'recent_trades': recent_trades
+            'recent_trades': recent_trades,
+            'cycles': token_cycles  # Include cycle details
         }
         }
 
 
     def _update_period_stats(self):
     def _update_period_stats(self):
         """Update daily, weekly, and monthly stats."""
         """Update daily, weekly, and monthly stats."""
-        # Recalculate all period stats from completed trades
-        trades_with_pnl = self.calculate_trade_pnl()
-        completed_trades = [t for t in trades_with_pnl if t.get('pnl', 0) != 0]
+        # Recalculate all period stats from completed trade cycles
+        completed_cycles = self.calculate_completed_trade_cycles()
         
         
         # Reset period stats
         # Reset period stats
         self.data['daily_stats'] = {}
         self.data['daily_stats'] = {}
         self.data['weekly_stats'] = {}
         self.data['weekly_stats'] = {}
         self.data['monthly_stats'] = {}
         self.data['monthly_stats'] = {}
         
         
-        # Group completed trades by periods and calculate stats
-        for trade in completed_trades:
-            timestamp = trade['timestamp']
-            pnl = trade['pnl']
-            value = trade['value']
+        # Group completed cycles by periods and calculate stats
+        for cycle in completed_cycles:
+            timestamp = cycle['cycle_end']  # Use cycle end time
+            pnl = cycle['total_pnl']
+            value = cycle['sell_value']  # Use sell value for volume
             
             
             try:
             try:
                 trade_date = datetime.fromisoformat(timestamp)
                 trade_date = datetime.fromisoformat(timestamp)
@@ -799,7 +798,7 @@ Please try again in a moment. If the issue persists, contact support.
                 if day_key not in self.data['daily_stats']:
                 if day_key not in self.data['daily_stats']:
                     self.data['daily_stats'][day_key] = {'trades': 0, 'pnl': 0.0, 'volume': 0.0}
                     self.data['daily_stats'][day_key] = {'trades': 0, 'pnl': 0.0, 'volume': 0.0}
                 
                 
-                self.data['daily_stats'][day_key]['trades'] += 1
+                self.data['daily_stats'][day_key]['trades'] += 1  # Count as 1 completed cycle
                 self.data['daily_stats'][day_key]['pnl'] += pnl
                 self.data['daily_stats'][day_key]['pnl'] += pnl
                 self.data['daily_stats'][day_key]['volume'] += value
                 self.data['daily_stats'][day_key]['volume'] += value
                 
                 
@@ -808,7 +807,7 @@ Please try again in a moment. If the issue persists, contact support.
                 if week_key not in self.data['weekly_stats']:
                 if week_key not in self.data['weekly_stats']:
                     self.data['weekly_stats'][week_key] = {'trades': 0, 'pnl': 0.0, 'volume': 0.0}
                     self.data['weekly_stats'][week_key] = {'trades': 0, 'pnl': 0.0, 'volume': 0.0}
                 
                 
-                self.data['weekly_stats'][week_key]['trades'] += 1
+                self.data['weekly_stats'][week_key]['trades'] += 1  # Count as 1 completed cycle
                 self.data['weekly_stats'][week_key]['pnl'] += pnl
                 self.data['weekly_stats'][week_key]['pnl'] += pnl
                 self.data['weekly_stats'][week_key]['volume'] += value
                 self.data['weekly_stats'][week_key]['volume'] += value
                 
                 
@@ -817,12 +816,12 @@ Please try again in a moment. If the issue persists, contact support.
                 if month_key not in self.data['monthly_stats']:
                 if month_key not in self.data['monthly_stats']:
                     self.data['monthly_stats'][month_key] = {'trades': 0, 'pnl': 0.0, 'volume': 0.0}
                     self.data['monthly_stats'][month_key] = {'trades': 0, 'pnl': 0.0, 'volume': 0.0}
                 
                 
-                self.data['monthly_stats'][month_key]['trades'] += 1
+                self.data['monthly_stats'][month_key]['trades'] += 1  # Count as 1 completed cycle
                 self.data['monthly_stats'][month_key]['pnl'] += pnl
                 self.data['monthly_stats'][month_key]['pnl'] += pnl
                 self.data['monthly_stats'][month_key]['volume'] += value
                 self.data['monthly_stats'][month_key]['volume'] += value
                 
                 
             except (ValueError, TypeError):
             except (ValueError, TypeError):
-                # Skip trades with invalid timestamps
+                # Skip cycles with invalid timestamps
                 continue
                 continue
         
         
         # Calculate percentages for all periods
         # Calculate percentages for all periods
@@ -1056,4 +1055,107 @@ Please try again in a moment. If the issue persists, contact support.
             'net_adjustment': net_adjustment,
             'net_adjustment': net_adjustment,
             'adjustment_count': len(adjustments),
             'adjustment_count': len(adjustments),
             'last_adjustment': latest_adjustment['timestamp'] if latest_adjustment else None
             'last_adjustment': latest_adjustment['timestamp'] if latest_adjustment else None
-        } 
+        }
+
+    def calculate_completed_trade_cycles(self) -> List[Dict[str, Any]]:
+        """Calculate completed trade cycles (full position open to close) using FIFO method."""
+        completed_cycles = []
+        positions = {}  # Track open positions by symbol
+        
+        for trade in self.data['trades']:
+            symbol = trade['symbol']
+            side = trade['side']
+            amount = trade['amount']
+            price = trade['price']
+            timestamp = trade['timestamp']
+            
+            if symbol not in positions:
+                positions[symbol] = {
+                    'amount': 0, 
+                    'avg_price': 0, 
+                    'total_cost': 0,
+                    'cycle_trades': [],  # Track all trades in current cycle
+                    'cycle_start': None
+                }
+            
+            pos = positions[symbol]
+            
+            if side == 'buy':
+                # Add to position
+                was_zero = pos['amount'] == 0
+                pos['total_cost'] += amount * price
+                pos['amount'] += amount
+                pos['avg_price'] = pos['total_cost'] / pos['amount'] if pos['amount'] > 0 else 0
+                
+                # If starting a new cycle, record start time
+                if was_zero:
+                    pos['cycle_start'] = timestamp
+                    pos['cycle_trades'] = []
+                
+                # Add trade to current cycle
+                pos['cycle_trades'].append({
+                    'side': side,
+                    'amount': amount,
+                    'price': price,
+                    'timestamp': timestamp,
+                    'value': amount * price
+                })
+                
+            elif side == 'sell':
+                # Reduce position and calculate PnL
+                if pos['amount'] > 0:
+                    sold_amount = min(amount, pos['amount'])
+                    pnl = sold_amount * (price - pos['avg_price'])
+                    
+                    # Add trade to current cycle
+                    pos['cycle_trades'].append({
+                        'side': side,
+                        'amount': sold_amount,
+                        'price': price,
+                        'timestamp': timestamp,
+                        'value': sold_amount * price,
+                        'pnl': pnl
+                    })
+                    
+                    # Update position
+                    pos['amount'] -= sold_amount
+                    if pos['amount'] > 0:
+                        pos['total_cost'] = pos['amount'] * pos['avg_price']
+                    else:
+                        # Position fully closed - complete trade cycle
+                        pos['total_cost'] = 0
+                        pos['avg_price'] = 0
+                        
+                        # Calculate cycle totals
+                        cycle_buys = [t for t in pos['cycle_trades'] if t['side'] == 'buy']
+                        cycle_sells = [t for t in pos['cycle_trades'] if t['side'] == 'sell']
+                        
+                        total_buy_value = sum(t['value'] for t in cycle_buys)
+                        total_sell_value = sum(t['value'] for t in cycle_sells)
+                        total_cycle_pnl = sum(t.get('pnl', 0) for t in cycle_sells)
+                        
+                        # Create completed cycle record
+                        completed_cycle = {
+                            'symbol': symbol,
+                            'token': symbol.split('/')[0] if '/' in symbol else symbol,
+                            'cycle_start': pos['cycle_start'],
+                            'cycle_end': timestamp,
+                            'buy_orders': len(cycle_buys),
+                            'sell_orders': len(cycle_sells),
+                            'total_orders': len(pos['cycle_trades']),
+                            'total_amount': sum(t['amount'] for t in cycle_buys),
+                            'avg_entry_price': total_buy_value / sum(t['amount'] for t in cycle_buys) if cycle_buys else 0,
+                            'avg_exit_price': total_sell_value / sum(t['amount'] for t in cycle_sells) if cycle_sells else 0,
+                            'total_pnl': total_cycle_pnl,
+                            'buy_value': total_buy_value,
+                            'sell_value': total_sell_value,
+                            'cycle_trades': pos['cycle_trades'].copy()
+                        }
+                        
+                        completed_cycles.append(completed_cycle)
+                        
+                        # Reset for next cycle
+                        pos['cycle_trades'] = []
+                        pos['cycle_start'] = None
+        
+        return completed_cycles 

+ 1 - 1
trading_bot.py

@@ -15,7 +15,7 @@ from datetime import datetime
 from pathlib import Path
 from pathlib import Path
 
 
 # Bot version
 # Bot version
-BOT_VERSION = "2.1.1"
+BOT_VERSION = "2.1.2"
 
 
 # Add src directory to Python path
 # Add src directory to Python path
 sys.path.insert(0, str(Path(__file__).parent / "src"))
 sys.path.insert(0, str(Path(__file__).parent / "src"))