Files
gogo2/web/dashboard_fix.py
2025-07-23 16:59:35 +03:00

253 lines
12 KiB
Python

"""
Dashboard Fix
This module provides fixes for the trading dashboard to address:
1. Trade display issues
2. P&L calculation and display
3. Position tracking and synchronization
Apply these fixes by importing and applying the patch in the dashboard initialization
"""
import logging
from datetime import datetime
from typing import Dict, Any, List, Optional
import time
logger = logging.getLogger(__name__)
class DashboardFix:
"""Fixes for the Dashboard class"""
@staticmethod
def apply_fixes(dashboard):
"""Apply all fixes to the dashboard"""
logger.info("Applying Dashboard fixes...")
# Apply fixes
DashboardFix._fix_trade_display(dashboard)
DashboardFix._fix_position_sync(dashboard)
DashboardFix._fix_pnl_calculation(dashboard)
DashboardFix._add_trade_validation(dashboard)
logger.info("Dashboard fixes applied successfully")
return dashboard
@staticmethod
def _fix_trade_display(dashboard):
"""Fix trade display to ensure accurate information"""
# Store original format_closed_trades_table method
if hasattr(dashboard.component_manager, 'format_closed_trades_table'):
original_format_closed_trades = dashboard.component_manager.format_closed_trades_table
def format_closed_trades_table_fixed(self, closed_trades, trading_stats=None):
"""Fixed closed trades table formatter with accurate P&L calculation"""
# Recalculate P&L for each trade to ensure accuracy
for trade in closed_trades:
# Skip if already validated
if getattr(trade, 'pnl_validated', False):
continue
# Handle both trade objects and dictionary formats
if hasattr(trade, 'entry_price'):
# This is a trade object
entry_price = getattr(trade, 'entry_price', 0)
exit_price = getattr(trade, 'exit_price', 0)
size = getattr(trade, 'size', 0)
side = getattr(trade, 'side', 'UNKNOWN')
fees = getattr(trade, 'fees', 0)
else:
# This is a dictionary format
entry_price = trade.get('entry_price', 0)
exit_price = trade.get('exit_price', 0)
size = trade.get('size', trade.get('quantity', 0))
side = trade.get('side', 'UNKNOWN')
fees = trade.get('fees', 0)
# Recalculate P&L
if side == 'LONG' or side == 'BUY':
pnl = (exit_price - entry_price) * size
else: # SHORT or SELL
pnl = (entry_price - exit_price) * size
# Update P&L value
if hasattr(trade, 'entry_price'):
trade.pnl = pnl
trade.net_pnl = pnl - fees
trade.pnl_validated = True
else:
trade['pnl'] = pnl
trade['net_pnl'] = pnl - fees
trade['pnl_validated'] = True
# Call original method with validated trades
return original_format_closed_trades(closed_trades, trading_stats)
# Apply the patch
dashboard.component_manager.format_closed_trades_table = format_closed_trades_table_fixed.__get__(dashboard.component_manager)
logger.info("Trade display fix applied")
@staticmethod
def _fix_position_sync(dashboard):
"""Fix position synchronization to ensure accurate position tracking"""
# Store original _sync_position_from_executor method
if hasattr(dashboard, '_sync_position_from_executor'):
original_sync_position = dashboard._sync_position_from_executor
def sync_position_from_executor_fixed(self, symbol):
"""Fixed position sync with validation and logging"""
try:
# Call original sync method
result = original_sync_position(symbol)
# Add validation and logging
if self.trading_executor and hasattr(self.trading_executor, 'positions'):
if symbol in self.trading_executor.positions:
position = self.trading_executor.positions[symbol]
# Log position details for debugging
logger.debug(f"Position sync for {symbol}: "
f"Side={position.side}, "
f"Size={position.size}, "
f"Entry=${position.entry_price:.2f}")
# Validate position data
if position.entry_price <= 0:
logger.warning(f"Invalid entry price for {symbol}: ${position.entry_price:.2f}")
# Store last sync time
if not hasattr(self, 'last_position_sync'):
self.last_position_sync = {}
self.last_position_sync[symbol] = time.time()
return result
except Exception as e:
logger.error(f"Error in sync_position_from_executor_fixed: {e}")
return None
# Apply the patch
dashboard._sync_position_from_executor = sync_position_from_executor_fixed.__get__(dashboard)
logger.info("Position sync fix applied")
@staticmethod
def _fix_pnl_calculation(dashboard):
"""Fix P&L calculation to ensure accuracy"""
# Add a method to recalculate P&L for all closed trades
def recalculate_all_pnl(self):
"""Recalculate P&L for all closed trades"""
if not hasattr(self, 'closed_trades') or not self.closed_trades:
return
for trade in self.closed_trades:
# Handle both trade objects and dictionary formats
if hasattr(trade, 'entry_price'):
# This is a trade object
entry_price = getattr(trade, 'entry_price', 0)
exit_price = getattr(trade, 'exit_price', 0)
size = getattr(trade, 'size', 0)
side = getattr(trade, 'side', 'UNKNOWN')
fees = getattr(trade, 'fees', 0)
else:
# This is a dictionary format
entry_price = trade.get('entry_price', 0)
exit_price = trade.get('exit_price', 0)
size = trade.get('size', trade.get('quantity', 0))
side = trade.get('side', 'UNKNOWN')
fees = trade.get('fees', 0)
# Recalculate P&L
if side == 'LONG' or side == 'BUY':
pnl = (exit_price - entry_price) * size
else: # SHORT or SELL
pnl = (entry_price - exit_price) * size
# Update P&L value
if hasattr(trade, 'entry_price'):
trade.pnl = pnl
trade.net_pnl = pnl - fees
else:
trade['pnl'] = pnl
trade['net_pnl'] = pnl - fees
logger.info(f"Recalculated P&L for {len(self.closed_trades)} closed trades")
# Add the method
dashboard.recalculate_all_pnl = recalculate_all_pnl.__get__(dashboard)
# Call it once to fix existing trades
dashboard.recalculate_all_pnl()
logger.info("P&L calculation fix applied")
@staticmethod
def _add_trade_validation(dashboard):
"""Add trade validation to prevent invalid trades"""
# Store original _on_trade_closed method if it exists
original_on_trade_closed = getattr(dashboard, '_on_trade_closed', None)
if original_on_trade_closed:
def on_trade_closed_fixed(self, trade_data):
"""Fixed trade closed handler with validation"""
try:
# Validate trade data
is_valid = True
validation_errors = []
# Check for required fields
required_fields = ['symbol', 'side', 'entry_price', 'exit_price', 'size']
for field in required_fields:
if field not in trade_data:
is_valid = False
validation_errors.append(f"Missing required field: {field}")
# Check for valid prices
if 'entry_price' in trade_data and trade_data['entry_price'] <= 0:
is_valid = False
validation_errors.append(f"Invalid entry price: {trade_data['entry_price']}")
if 'exit_price' in trade_data and trade_data['exit_price'] <= 0:
is_valid = False
validation_errors.append(f"Invalid exit price: {trade_data['exit_price']}")
# Check for valid size
if 'size' in trade_data and trade_data['size'] <= 0:
is_valid = False
validation_errors.append(f"Invalid size: {trade_data['size']}")
# If invalid, log errors and skip
if not is_valid:
logger.warning(f"Invalid trade data: {validation_errors}")
return
# Calculate correct P&L
if 'side' in trade_data and 'entry_price' in trade_data and 'exit_price' in trade_data and 'size' in trade_data:
side = trade_data['side']
entry_price = trade_data['entry_price']
exit_price = trade_data['exit_price']
size = trade_data['size']
if side == 'LONG' or side == 'BUY':
pnl = (exit_price - entry_price) * size
else: # SHORT or SELL
pnl = (entry_price - exit_price) * size
# Update P&L in trade data
trade_data['pnl'] = pnl
# Calculate net P&L (after fees)
fees = trade_data.get('fees', 0)
trade_data['net_pnl'] = pnl - fees
# Call original method with validated data
return original_on_trade_closed(trade_data)
except Exception as e:
logger.error(f"Error in on_trade_closed_fixed: {e}")
# Apply the patch
dashboard._on_trade_closed = on_trade_closed_fixed.__get__(dashboard)
logger.info("Trade validation fix applied")
else:
logger.warning("_on_trade_closed method not found, skipping trade validation fix")