fix chart and trade actions
This commit is contained in:
136
web/dashboard.py
136
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,
|
||||
@ -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)
|
||||
|
Reference in New Issue
Block a user