fix chart and trade actions
This commit is contained in:
170
web/dashboard.py
170
web/dashboard.py
@ -1209,11 +1209,11 @@ class TradingDashboard:
|
|||||||
if time.time() - cache_time < 3: # Use cached chart if < 3s old for faster updates
|
if time.time() - cache_time < 3: # Use cached chart if < 3s old for faster updates
|
||||||
price_chart = getattr(self, '_cached_price_chart', None)
|
price_chart = getattr(self, '_cached_price_chart', None)
|
||||||
else:
|
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_price_chart = price_chart
|
||||||
self._cached_chart_data_time = time.time()
|
self._cached_chart_data_time = time.time()
|
||||||
else:
|
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_price_chart = price_chart
|
||||||
self._cached_chart_data_time = time.time()
|
self._cached_chart_data_time = time.time()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -1343,13 +1343,29 @@ class TradingDashboard:
|
|||||||
"""Clear trade history and reset session stats"""
|
"""Clear trade history and reset session stats"""
|
||||||
if n_clicks and n_clicks > 0:
|
if n_clicks and n_clicks > 0:
|
||||||
try:
|
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()
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Error clearing trade history: {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
|
return dash.no_update
|
||||||
|
|
||||||
# Leverage slider callback
|
# Leverage slider callback
|
||||||
@ -1426,7 +1442,7 @@ class TradingDashboard:
|
|||||||
'price': current_price,
|
'price': current_price,
|
||||||
'size': 0.001, # Small test size (max 1 lot)
|
'size': 0.001, # Small test size (max 1 lot)
|
||||||
'confidence': 1.0, # Manual trades have 100% confidence
|
'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',
|
'source': 'MANUAL_BUY',
|
||||||
'mexc_executed': False, # Mark as manual/test trade
|
'mexc_executed': False, # Mark as manual/test trade
|
||||||
'usd_size': current_price * 0.001
|
'usd_size': current_price * 0.001
|
||||||
@ -1464,7 +1480,7 @@ class TradingDashboard:
|
|||||||
'price': current_price,
|
'price': current_price,
|
||||||
'size': 0.001, # Small test size (max 1 lot)
|
'size': 0.001, # Small test size (max 1 lot)
|
||||||
'confidence': 1.0, # Manual trades have 100% confidence
|
'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',
|
'source': 'MANUAL_SELL',
|
||||||
'mexc_executed': False, # Mark as manual/test trade
|
'mexc_executed': False, # Mark as manual/test trade
|
||||||
'usd_size': current_price * 0.001
|
'usd_size': current_price * 0.001
|
||||||
@ -1643,15 +1659,17 @@ class TradingDashboard:
|
|||||||
logger.debug(f"[CHART] Added Williams pivot points using {actual_timeframe} data")
|
logger.debug(f"[CHART] Added Williams pivot points using {actual_timeframe} data")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Error adding Williams pivot points to chart: {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:
|
if len(df) >= 20:
|
||||||
# 20-period SMA
|
# 20-period SMA - use .copy() to avoid SettingWithCopyWarning
|
||||||
df['sma_20'] = df['close'].rolling(window=20).mean()
|
df_with_sma = df.copy()
|
||||||
|
df_with_sma.loc[:, 'sma_20'] = df_with_sma['close'].rolling(window=20).mean()
|
||||||
fig.add_trace(
|
fig.add_trace(
|
||||||
go.Scatter(
|
go.Scatter(
|
||||||
x=df.index,
|
x=df_with_sma.index,
|
||||||
y=df['sma_20'],
|
y=df_with_sma['sma_20'],
|
||||||
name='SMA 20',
|
name='SMA 20',
|
||||||
line=dict(color='#ff1493', width=1),
|
line=dict(color='#ff1493', width=1),
|
||||||
opacity=0.8,
|
opacity=0.8,
|
||||||
@ -1661,12 +1679,14 @@ class TradingDashboard:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if len(df) >= 50:
|
if len(df) >= 50:
|
||||||
# 50-period SMA
|
# 50-period SMA - use .copy() to avoid SettingWithCopyWarning
|
||||||
df['sma_50'] = df['close'].rolling(window=50).mean()
|
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(
|
fig.add_trace(
|
||||||
go.Scatter(
|
go.Scatter(
|
||||||
x=df.index,
|
x=df_with_sma.index,
|
||||||
y=df['sma_50'],
|
y=df_with_sma['sma_50'],
|
||||||
name='SMA 50',
|
name='SMA 50',
|
||||||
line=dict(color='#ffa500', width=1),
|
line=dict(color='#ffa500', width=1),
|
||||||
opacity=0.8,
|
opacity=0.8,
|
||||||
@ -1686,7 +1706,7 @@ class TradingDashboard:
|
|||||||
hovertemplate='<b>Volume: %{y:.0f}</b><br>%{x}<extra></extra>'
|
hovertemplate='<b>Volume: %{y:.0f}</b><br>%{x}<extra></extra>'
|
||||||
),
|
),
|
||||||
row=2, col=1
|
row=2, col=1
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mark recent trading decisions with proper markers
|
# Mark recent trading decisions with proper markers
|
||||||
if self.recent_decisions and df is not None and not df.empty:
|
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
|
# Convert decision timestamp to match chart timezone if needed
|
||||||
if isinstance(decision_time, datetime):
|
if isinstance(decision_time, datetime):
|
||||||
if decision_time.tzinfo is not None:
|
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:
|
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:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -1723,10 +1747,10 @@ class TradingDashboard:
|
|||||||
decision_time_pd = pd.to_datetime(decision_time_utc)
|
decision_time_pd = pd.to_datetime(decision_time_utc)
|
||||||
if chart_start_utc <= decision_time_pd <= chart_end_utc:
|
if chart_start_utc <= decision_time_pd <= chart_end_utc:
|
||||||
signal_type = decision.get('signal_type', 'UNKNOWN')
|
signal_type = decision.get('signal_type', 'UNKNOWN')
|
||||||
if decision['action'] == 'BUY':
|
if decision['action'] == 'BUY':
|
||||||
buy_decisions.append((decision, signal_type))
|
buy_decisions.append((decision, signal_type))
|
||||||
elif decision['action'] == 'SELL':
|
elif decision['action'] == 'SELL':
|
||||||
sell_decisions.append((decision, signal_type))
|
sell_decisions.append((decision, signal_type))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1844,23 +1868,35 @@ class TradingDashboard:
|
|||||||
if not entry_time or not exit_time:
|
if not entry_time or not exit_time:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Convert times to UTC for comparison
|
# Convert times to UTC for comparison - FIXED timezone handling
|
||||||
if isinstance(entry_time, datetime):
|
try:
|
||||||
entry_time_utc = entry_time.astimezone(timezone.utc).replace(tzinfo=None) if entry_time.tzinfo else entry_time
|
if isinstance(entry_time, datetime):
|
||||||
else:
|
# If naive datetime, assume it's in local timezone
|
||||||
continue
|
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):
|
# Check if trade overlaps with chart timeframe
|
||||||
exit_time_utc = exit_time.astimezone(timezone.utc).replace(tzinfo=None) if exit_time.tzinfo else exit_time
|
entry_time_pd = pd.to_datetime(entry_time_utc)
|
||||||
else:
|
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
|
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
|
# Minimal logging - only show count
|
||||||
if len(chart_trades) > 0:
|
if len(chart_trades) > 0:
|
||||||
@ -2415,7 +2451,7 @@ class TradingDashboard:
|
|||||||
'symbol': symbol,
|
'symbol': symbol,
|
||||||
'price': current_price,
|
'price': current_price,
|
||||||
'confidence': confidence,
|
'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
|
'size': 0.1, # Will be adjusted by confidence in processing
|
||||||
'reason': f'Scalping BUY: momentum={momentum:.6f}, trend={trend_strength:.6f}, conf={confidence:.3f}'
|
'reason': f'Scalping BUY: momentum={momentum:.6f}, trend={trend_strength:.6f}, conf={confidence:.3f}'
|
||||||
}
|
}
|
||||||
@ -2434,7 +2470,7 @@ class TradingDashboard:
|
|||||||
'symbol': symbol,
|
'symbol': symbol,
|
||||||
'price': current_price,
|
'price': current_price,
|
||||||
'confidence': confidence,
|
'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
|
'size': 0.1, # Will be adjusted by confidence in processing
|
||||||
'reason': f'Scalping SELL: momentum={momentum:.6f}, trend={trend_strength:.6f}, conf={confidence:.3f}'
|
'reason': f'Scalping SELL: momentum={momentum:.6f}, trend={trend_strength:.6f}, conf={confidence:.3f}'
|
||||||
}
|
}
|
||||||
@ -2451,7 +2487,7 @@ class TradingDashboard:
|
|||||||
if not decision:
|
if not decision:
|
||||||
return
|
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)
|
# Get fee structure from config (fallback to hardcoded values)
|
||||||
try:
|
try:
|
||||||
@ -2998,14 +3034,22 @@ class TradingDashboard:
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime
|
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 = []
|
trades_for_json = []
|
||||||
for trade in self.closed_trades:
|
for trade in self.closed_trades:
|
||||||
trade_copy = trade.copy()
|
trade_copy = trade.copy()
|
||||||
if isinstance(trade_copy.get('entry_time'), datetime):
|
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):
|
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):
|
if isinstance(trade_copy.get('duration'), timedelta):
|
||||||
trade_copy['duration'] = str(trade_copy['duration'])
|
trade_copy['duration'] = str(trade_copy['duration'])
|
||||||
trades_for_json.append(trade_copy)
|
trades_for_json.append(trade_copy)
|
||||||
@ -3031,12 +3075,20 @@ class TradingDashboard:
|
|||||||
trades_data = json.load(f)
|
trades_data = json.load(f)
|
||||||
logger.info(f"LOAD_TRADES: Raw data loaded: {len(trades_data)} trades")
|
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:
|
for trade in trades_data:
|
||||||
if isinstance(trade.get('entry_time'), str):
|
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):
|
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):
|
if isinstance(trade.get('duration'), str):
|
||||||
# Parse duration string back to timedelta
|
# Parse duration string back to timedelta
|
||||||
duration_parts = trade['duration'].split(':')
|
duration_parts = trade['duration'].split(':')
|
||||||
@ -3054,16 +3106,30 @@ class TradingDashboard:
|
|||||||
self.closed_trades = []
|
self.closed_trades = []
|
||||||
|
|
||||||
def clear_closed_trades_history(self):
|
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:
|
try:
|
||||||
|
# Clear closed trades history only
|
||||||
self.closed_trades = []
|
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
|
# Remove file if it exists
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
if Path('closed_trades_history.json').exists():
|
if Path('closed_trades_history.json').exists():
|
||||||
Path('closed_trades_history.json').unlink()
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Error clearing closed trades history: {e}")
|
logger.error(f"Error clearing closed trades history: {e}")
|
||||||
@ -6318,8 +6384,8 @@ class TradingDashboard:
|
|||||||
logger.error(f"Optimized chart error: {e}")
|
logger.error(f"Optimized chart error: {e}")
|
||||||
return self._create_empty_chart(f"{symbol} Chart", f"Chart Error: {str(e)}")
|
return self._create_empty_chart(f"{symbol} Chart", f"Chart Error: {str(e)}")
|
||||||
|
|
||||||
def _create_price_chart_optimized(self, symbol, current_price):
|
def _get_williams_pivot_points_for_chart(self, df: pd.DataFrame, chart_df: pd.DataFrame = None) -> List[Dict]:
|
||||||
"""Optimized chart creation with minimal data fetching"""
|
"""Get Williams pivot points for chart display"""
|
||||||
try:
|
try:
|
||||||
# Use minimal data for chart
|
# Use minimal data for chart
|
||||||
df = self.data_provider.get_historical_data(symbol, '1m', limit=20, refresh=False)
|
df = self.data_provider.get_historical_data(symbol, '1m', limit=20, refresh=False)
|
||||||
|
Reference in New Issue
Block a user