소스 검색

Enhance ROE calculations and data handling across multiple modules.

- Updated MarketMonitor to correctly calculate unrealized P&L percentage by multiplying ROE from the exchange by 100.
- Integrated exchange position information into SimplePositionTracker for improved notification details upon position closure.
- Added ROE percentage calculation in AggregationManager and updated database schema to store this value.
- Refactored PerformanceCalculator to retrieve ROE directly from token data, ensuring consistent performance metrics.
- Adjusted TradeLifecycleManager to store ROE as a decimal for better display handling.
Carles Sentis 1 주 전
부모
커밋
f2781b85f5

+ 3 - 1
src/monitoring/market_monitor.py

@@ -334,7 +334,9 @@ class MarketMonitor:
                                 position_value = abs(current_position_size) * mark_price
 
                             roe_from_ex = ex_pos.get('percentage')
-                            unrealized_pnl_percentage_val = float(roe_from_ex) if roe_from_ex is not None else None
+                            # The exchange provides ROE as a decimal (e.g., -0.326 for -32.6%)
+                            # We need to multiply by 100 and keep the sign
+                            unrealized_pnl_percentage_val = float(roe_from_ex) * 100 if roe_from_ex is not None else None
 
                             stats.update_trade_market_data(
                                 trade_lifecycle_id=lifecycle_id, 

+ 12 - 1
src/monitoring/simple_position_tracker.py

@@ -14,6 +14,7 @@ import logging
 import asyncio
 from datetime import datetime, timezone
 from typing import Optional, Dict, Any, List
+from src.utils.token_display_formatter import get_formatter
 
 logger = logging.getLogger(__name__)
 
@@ -174,6 +175,15 @@ class SimplePositionTracker:
             if success:
                 logger.info(f"🎯 POSITION CLOSED: {symbol} {position_side.upper()} PnL: {realized_pnl:.2f}")
                 
+                # Get exchange position info for ROE
+                exchange_positions = self.trading_engine.get_positions()
+                exchange_pos = None
+                if exchange_positions:
+                    for pos in exchange_positions:
+                        if pos.get('symbol') == symbol:
+                            exchange_pos = pos
+                            break
+                
                 # Send notification
                 await self._send_position_notification('closed', symbol, {
                     'side': position_side,
@@ -181,7 +191,8 @@ class SimplePositionTracker:
                     'entry_price': entry_price,
                     'exit_price': exit_price,
                     'realized_pnl': realized_pnl,
-                    'timestamp': timestamp
+                    'timestamp': timestamp,
+                    'info': exchange_pos.get('info', {}) if exchange_pos else {}
                 })
                 
                 # Clear any pending stop losses for this symbol

+ 7 - 3
src/stats/aggregation_manager.py

@@ -73,14 +73,17 @@ class AggregationManager:
             except Exception:
                 duration_seconds = 0
 
+        # Calculate ROE percentage
+        roe_percentage = (realized_pnl / entry_value * 100) if entry_value > 0 else 0.0
+
         # Update token_stats
         token_upsert_query = """
             INSERT INTO token_stats (
                 token, total_realized_pnl, total_completed_cycles, winning_cycles, losing_cycles,
                 total_entry_volume, total_exit_volume, sum_of_winning_pnl, sum_of_losing_pnl,
                 largest_winning_cycle_pnl, largest_losing_cycle_pnl, largest_winning_cycle_entry_volume, largest_losing_cycle_entry_volume,
-                first_cycle_closed_at, last_cycle_closed_at, total_duration_seconds, updated_at
-            ) VALUES (?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 
+                first_cycle_closed_at, last_cycle_closed_at, total_duration_seconds, roe_percentage, updated_at
+            ) VALUES (?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 
             ON CONFLICT(token) DO UPDATE SET
                 total_realized_pnl = total_realized_pnl + excluded.total_realized_pnl,
                 total_completed_cycles = total_completed_cycles + 1,
@@ -109,6 +112,7 @@ class AggregationManager:
                 first_cycle_closed_at = MIN(first_cycle_closed_at, excluded.first_cycle_closed_at),
                 last_cycle_closed_at = MAX(last_cycle_closed_at, excluded.last_cycle_closed_at),
                 total_duration_seconds = total_duration_seconds + excluded.total_duration_seconds,
+                roe_percentage = excluded.roe_percentage,
                 updated_at = excluded.updated_at
         """
         is_win = 1 if realized_pnl > 0 else 0
@@ -124,7 +128,7 @@ class AggregationManager:
             token, realized_pnl, is_win, is_loss, entry_value, exit_value,
             win_pnl_contrib, loss_pnl_contrib, win_pnl_contrib, loss_pnl_contrib,
             largest_win_entry_volume, largest_loss_entry_volume,
-            closed_at_str, closed_at_str, duration_seconds, now_iso
+            closed_at_str, closed_at_str, duration_seconds, roe_percentage, now_iso
         ))
 
         # Update daily_aggregated_stats

+ 2 - 1
src/stats/database_manager.py

@@ -193,7 +193,8 @@ class DatabaseManager:
                 last_cycle_closed_at TEXT,
                 total_cancelled_cycles INTEGER DEFAULT 0,
                 updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
-                total_duration_seconds INTEGER DEFAULT 0
+                total_duration_seconds INTEGER DEFAULT 0,
+                roe_percentage REAL DEFAULT 0.0
             )
             """,
             """

+ 2 - 16
src/stats/performance_calculator.py

@@ -229,22 +229,8 @@ class PerformanceCalculator:
             sum_losing = token.get('sum_of_losing_pnl', 0)
             token['profit_factor'] = sum_winning / sum_losing if sum_losing > 0 else float('inf') if sum_winning > 0 else 0
             
-            # Get ROE directly from exchange data
-            info_data = token.get('info', {})
-            position_info = info_data.get('position', {})
-            roe_raw = position_info.get('percentage')  # This is the same field used by /positions
-            
-            if roe_raw is not None:
-                try:
-                    # The exchange provides ROE as a decimal (e.g., -0.326 for -32.6%)
-                    # We need to multiply by 100 and keep the sign
-                    token['roe_percentage'] = float(roe_raw) * 100
-                except (ValueError, TypeError):
-                    logger.warning(f"Could not parse ROE value: {roe_raw} for {token['token']}")
-                    token['roe_percentage'] = 0.0
-            else:
-                logger.warning(f"No ROE data available from exchange for {token['token']}")
-                token['roe_percentage'] = 0.0
+            # Get ROE directly from the exchange data
+            token['roe_percentage'] = token.get('roe_percentage', 0.0)
             
             # Format durations
             total_duration = token.get('total_duration_seconds', 0)

+ 2 - 0
src/stats/trade_lifecycle_manager.py

@@ -362,6 +362,8 @@ class TradeLifecycleManager:
                 updates.append("position_value = ?")
                 params.append(position_value)
             if unrealized_pnl_percentage is not None:
+                # Store ROE as a decimal (e.g., -0.326 for -32.6%)
+                # We'll handle the conversion to percentage when displaying
                 updates.append("unrealized_pnl_percentage = ?")
                 params.append(unrealized_pnl_percentage)
 

+ 1 - 1
trading_bot.py

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