fix chart and trade actions

This commit is contained in:
Dobromir Popov
2025-06-24 23:21:33 +03:00
parent 5243c65fb6
commit 34b988bc69

View File

@ -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,
@ -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
@ -1844,14 +1868,23 @@ class TradingDashboard:
if not entry_time or not exit_time:
continue
# Convert times to UTC for comparison
# Convert times to UTC for comparison - FIXED timezone handling
try:
if isinstance(entry_time, datetime):
entry_time_utc = entry_time.astimezone(timezone.utc).replace(tzinfo=None) if entry_time.tzinfo else entry_time
# 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):
exit_time_utc = exit_time.astimezone(timezone.utc).replace(tzinfo=None) if exit_time.tzinfo else exit_time
# 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
@ -1861,6 +1894,9 @@ class TradingDashboard:
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
# 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)