diff --git a/web/dashboard.py b/web/dashboard.py index 5169216..dad7df5 100644 --- a/web/dashboard.py +++ b/web/dashboard.py @@ -1209,11 +1209,11 @@ class TradingDashboard: if time.time() - cache_time < 3: # Use cached chart if < 3s old for faster updates price_chart = getattr(self, '_cached_price_chart', None) else: - price_chart = self._create_price_chart_optimized_v2(symbol) + price_chart = self._create_price_chart(symbol) self._cached_price_chart = price_chart self._cached_chart_data_time = time.time() else: - price_chart = self._create_price_chart_optimized_v2(symbol) + price_chart = self._create_price_chart(symbol) self._cached_price_chart = price_chart self._cached_chart_data_time = time.time() except Exception as e: @@ -1343,13 +1343,29 @@ class TradingDashboard: """Clear trade history and reset session stats""" if n_clicks and n_clicks > 0: try: - # Clear both closed trades and session stats (they're the same now) + # Store current position status before clearing + has_position = bool(self.current_position) + position_info = "" + if has_position: + side = self.current_position.get('side', 'UNKNOWN') + price = self.current_position.get('price', 0) + size = self.current_position.get('size', 0) + position_info = f" (Current {side} position preserved: {size:.6f} @ ${price:.2f})" + + # Clear trade history and session stats self.clear_closed_trades_history() - logger.info("DASHBOARD: Trade history and session stats cleared by user") - return [html.P("Trade history cleared", className="text-success text-center")] + + logger.info(f"DASHBOARD: Trade history cleared by user{position_info}") + + # Provide detailed feedback to user + feedback_message = "✅ Trade history and session stats cleared" + if has_position: + feedback_message += f" • Current {self.current_position.get('side', 'UNKNOWN')} position preserved" + + return [html.P(feedback_message, className="text-success text-center")] except Exception as e: logger.error(f"Error clearing trade history: {e}") - return [html.P(f"Error clearing history: {str(e)}", className="text-danger text-center")] + return [html.P(f"❌ Error clearing history: {str(e)}", className="text-danger text-center")] return dash.no_update # Leverage slider callback @@ -1426,7 +1442,7 @@ class TradingDashboard: 'price': current_price, 'size': 0.001, # Small test size (max 1 lot) 'confidence': 1.0, # Manual trades have 100% confidence - 'timestamp': datetime.now(), + 'timestamp': self._now_local(), # Use local timezone for consistency with manual decisions 'source': 'MANUAL_BUY', 'mexc_executed': False, # Mark as manual/test trade 'usd_size': current_price * 0.001 @@ -1464,7 +1480,7 @@ class TradingDashboard: 'price': current_price, 'size': 0.001, # Small test size (max 1 lot) 'confidence': 1.0, # Manual trades have 100% confidence - 'timestamp': datetime.now(), + 'timestamp': self._now_local(), # Use local timezone for consistency with manual decisions 'source': 'MANUAL_SELL', 'mexc_executed': False, # Mark as manual/test trade 'usd_size': current_price * 0.001 @@ -1643,15 +1659,17 @@ class TradingDashboard: logger.debug(f"[CHART] Added Williams pivot points using {actual_timeframe} data") except Exception as e: logger.debug(f"Error adding Williams pivot points to chart: {e}") + # Continue without pivot points if there's an error - # Add moving averages if we have enough data + # Add moving averages if we have enough data - FIXED pandas warnings if len(df) >= 20: - # 20-period SMA - df['sma_20'] = df['close'].rolling(window=20).mean() + # 20-period SMA - use .copy() to avoid SettingWithCopyWarning + df_with_sma = df.copy() + df_with_sma.loc[:, 'sma_20'] = df_with_sma['close'].rolling(window=20).mean() fig.add_trace( go.Scatter( - x=df.index, - y=df['sma_20'], + x=df_with_sma.index, + y=df_with_sma['sma_20'], name='SMA 20', line=dict(color='#ff1493', width=1), opacity=0.8, @@ -1661,12 +1679,14 @@ class TradingDashboard: ) if len(df) >= 50: - # 50-period SMA - df['sma_50'] = df['close'].rolling(window=50).mean() + # 50-period SMA - use .copy() to avoid SettingWithCopyWarning + if 'df_with_sma' not in locals(): + df_with_sma = df.copy() + df_with_sma.loc[:, 'sma_50'] = df_with_sma['close'].rolling(window=50).mean() fig.add_trace( go.Scatter( - x=df.index, - y=df['sma_50'], + x=df_with_sma.index, + y=df_with_sma['sma_50'], name='SMA 50', line=dict(color='#ffa500', width=1), opacity=0.8, @@ -1686,7 +1706,7 @@ class TradingDashboard: hovertemplate='Volume: %{y:.0f}
%{x}' ), row=2, col=1 - ) + ) # Mark recent trading decisions with proper markers if self.recent_decisions and df is not None and not df.empty: @@ -1705,9 +1725,13 @@ class TradingDashboard: # Convert decision timestamp to match chart timezone if needed if isinstance(decision_time, datetime): if decision_time.tzinfo is not None: - decision_time_utc = decision_time.astimezone(timezone.utc).replace(tzinfo=None) + # Decision has timezone info, convert to local timezone first, then UTC for comparison + decision_time_local = decision_time.astimezone(self.timezone) + decision_time_utc = decision_time_local.astimezone(timezone.utc).replace(tzinfo=None) else: - decision_time_utc = decision_time + # Decision is naive datetime, assume it's already in local timezone + decision_time_local = self.timezone.localize(decision_time) + decision_time_utc = decision_time_local.astimezone(timezone.utc).replace(tzinfo=None) else: continue @@ -1723,10 +1747,10 @@ class TradingDashboard: decision_time_pd = pd.to_datetime(decision_time_utc) if chart_start_utc <= decision_time_pd <= chart_end_utc: signal_type = decision.get('signal_type', 'UNKNOWN') - if decision['action'] == 'BUY': - buy_decisions.append((decision, signal_type)) - elif decision['action'] == 'SELL': - sell_decisions.append((decision, signal_type)) + if decision['action'] == 'BUY': + buy_decisions.append((decision, signal_type)) + elif decision['action'] == 'SELL': + sell_decisions.append((decision, signal_type)) @@ -1844,23 +1868,35 @@ class TradingDashboard: if not entry_time or not exit_time: continue - # Convert times to UTC for comparison - if isinstance(entry_time, datetime): - entry_time_utc = entry_time.astimezone(timezone.utc).replace(tzinfo=None) if entry_time.tzinfo else entry_time - else: - continue + # Convert times to UTC for comparison - FIXED timezone handling + try: + if isinstance(entry_time, datetime): + # If naive datetime, assume it's in local timezone + if entry_time.tzinfo is None: + entry_time_utc = self.timezone.localize(entry_time).astimezone(timezone.utc).replace(tzinfo=None) + else: + entry_time_utc = entry_time.astimezone(timezone.utc).replace(tzinfo=None) + else: + continue + + if isinstance(exit_time, datetime): + # If naive datetime, assume it's in local timezone + if exit_time.tzinfo is None: + exit_time_utc = self.timezone.localize(exit_time).astimezone(timezone.utc).replace(tzinfo=None) + else: + exit_time_utc = exit_time.astimezone(timezone.utc).replace(tzinfo=None) + else: + continue - if isinstance(exit_time, datetime): - exit_time_utc = exit_time.astimezone(timezone.utc).replace(tzinfo=None) if exit_time.tzinfo else exit_time - else: + # Check if trade overlaps with chart timeframe + entry_time_pd = pd.to_datetime(entry_time_utc) + exit_time_pd = pd.to_datetime(exit_time_utc) + + if (chart_start_utc <= entry_time_pd <= chart_end_utc) or (chart_start_utc <= exit_time_pd <= chart_end_utc): + chart_trades.append(trade) + except Exception as e: + logger.debug(f"Error processing trade timestamps: {e}") continue - - # Check if trade overlaps with chart timeframe - entry_time_pd = pd.to_datetime(entry_time_utc) - exit_time_pd = pd.to_datetime(exit_time_utc) - - if (chart_start_utc <= entry_time_pd <= chart_end_utc) or (chart_start_utc <= exit_time_pd <= chart_end_utc): - chart_trades.append(trade) # Minimal logging - only show count if len(chart_trades) > 0: @@ -2415,7 +2451,7 @@ class TradingDashboard: 'symbol': symbol, 'price': current_price, 'confidence': confidence, - 'timestamp': datetime.now(timezone.utc), # Use UTC to match candle data + 'timestamp': self._now_local(), # Use local timezone for consistency with manual decisions 'size': 0.1, # Will be adjusted by confidence in processing 'reason': f'Scalping BUY: momentum={momentum:.6f}, trend={trend_strength:.6f}, conf={confidence:.3f}' } @@ -2434,7 +2470,7 @@ class TradingDashboard: 'symbol': symbol, 'price': current_price, 'confidence': confidence, - 'timestamp': datetime.now(timezone.utc), # Use UTC to match candle data + 'timestamp': self._now_local(), # Use local timezone for consistency with manual decisions 'size': 0.1, # Will be adjusted by confidence in processing 'reason': f'Scalping SELL: momentum={momentum:.6f}, trend={trend_strength:.6f}, conf={confidence:.3f}' } @@ -2451,7 +2487,7 @@ class TradingDashboard: if not decision: return - current_time = datetime.now(timezone.utc) # Use UTC for consistency + current_time = self._now_local() # Use local timezone for consistency # Get fee structure from config (fallback to hardcoded values) try: @@ -2998,14 +3034,22 @@ class TradingDashboard: import json from datetime import datetime - # Convert datetime objects to strings for JSON serialization + # Convert datetime objects to strings for JSON serialization with timezone info trades_for_json = [] for trade in self.closed_trades: trade_copy = trade.copy() if isinstance(trade_copy.get('entry_time'), datetime): - trade_copy['entry_time'] = trade_copy['entry_time'].isoformat() + # Ensure timezone is set before saving + dt = trade_copy['entry_time'] + if dt.tzinfo is None: + dt = self.timezone.localize(dt) + trade_copy['entry_time'] = dt.isoformat() if isinstance(trade_copy.get('exit_time'), datetime): - trade_copy['exit_time'] = trade_copy['exit_time'].isoformat() + # Ensure timezone is set before saving + dt = trade_copy['exit_time'] + if dt.tzinfo is None: + dt = self.timezone.localize(dt) + trade_copy['exit_time'] = dt.isoformat() if isinstance(trade_copy.get('duration'), timedelta): trade_copy['duration'] = str(trade_copy['duration']) trades_for_json.append(trade_copy) @@ -3031,12 +3075,20 @@ class TradingDashboard: trades_data = json.load(f) logger.info(f"LOAD_TRADES: Raw data loaded: {len(trades_data)} trades") - # Convert string dates back to datetime objects + # Convert string dates back to datetime objects with proper timezone handling for trade in trades_data: if isinstance(trade.get('entry_time'), str): - trade['entry_time'] = datetime.fromisoformat(trade['entry_time']) + dt = datetime.fromisoformat(trade['entry_time']) + # If loaded datetime is naive, assume it's in local timezone (Sofia) + if dt.tzinfo is None: + dt = self.timezone.localize(dt) + trade['entry_time'] = dt if isinstance(trade.get('exit_time'), str): - trade['exit_time'] = datetime.fromisoformat(trade['exit_time']) + dt = datetime.fromisoformat(trade['exit_time']) + # If loaded datetime is naive, assume it's in local timezone (Sofia) + if dt.tzinfo is None: + dt = self.timezone.localize(dt) + trade['exit_time'] = dt if isinstance(trade.get('duration'), str): # Parse duration string back to timedelta duration_parts = trade['duration'].split(':') @@ -3054,16 +3106,30 @@ class TradingDashboard: self.closed_trades = [] def clear_closed_trades_history(self): - """Clear closed trades history and remove file""" + """Clear closed trades history and reset session stats (but keep current positions)""" try: + # Clear closed trades history only self.closed_trades = [] + # Reset session statistics (but NOT current position) + self.total_realized_pnl = 0.0 + self.total_fees = 0.0 + self.session_pnl = 0.0 + + # Clear recent decisions related to closed trades but keep current position decisions + # Keep only the last few decisions that might be related to current open position + if self.recent_decisions: + # Keep last 5 decisions in case they're related to current position + self.recent_decisions = self.recent_decisions[-5:] if len(self.recent_decisions) > 5 else self.recent_decisions + # Remove file if it exists from pathlib import Path if Path('closed_trades_history.json').exists(): Path('closed_trades_history.json').unlink() - logger.info("Cleared closed trades history") + # Log what was preserved + position_status = "PRESERVED" if self.current_position else "NONE" + logger.info(f"Cleared closed trades history - Current position: {position_status}") except Exception as e: logger.error(f"Error clearing closed trades history: {e}") @@ -6318,8 +6384,8 @@ class TradingDashboard: logger.error(f"Optimized chart error: {e}") return self._create_empty_chart(f"{symbol} Chart", f"Chart Error: {str(e)}") - def _create_price_chart_optimized(self, symbol, current_price): - """Optimized chart creation with minimal data fetching""" + def _get_williams_pivot_points_for_chart(self, df: pd.DataFrame, chart_df: pd.DataFrame = None) -> List[Dict]: + """Get Williams pivot points for chart display""" try: # Use minimal data for chart df = self.data_provider.get_historical_data(symbol, '1m', limit=20, refresh=False)