diff --git a/main_clean.py b/main_clean.py
index d3018c8..1c44e6b 100644
--- a/main_clean.py
+++ b/main_clean.py
@@ -295,8 +295,8 @@ def run_web_dashboard():
if test_data is None or test_data.empty:
logger.warning("⚠️ No data available - starting dashboard with demo mode...")
else:
- logger.info("✅ Data connection verified")
- logger.info(f"✅ Fetched {len(test_data)} candles for validation")
+ logger.info("[SUCCESS] Data connection verified")
+ logger.info(f"[SUCCESS] Fetched {len(test_data)} candles for validation")
# Initialize orchestrator with real data only
orchestrator = TradingOrchestrator(data_provider)
@@ -305,8 +305,8 @@ def run_web_dashboard():
from web.dashboard import TradingDashboard
dashboard = TradingDashboard(data_provider, orchestrator)
- logger.info("🎯 LAUNCHING DASHBOARD")
- logger.info(f"🌐 Access at: http://127.0.0.1:8050")
+ logger.info("[LAUNCH] LAUNCHING DASHBOARD")
+ logger.info(f"[ACCESS] Access at: http://127.0.0.1:8050")
# Run the dashboard
dashboard.run(host='127.0.0.1', port=8050, debug=False)
diff --git a/web/dashboard.py b/web/dashboard.py
index cf23b70..1eac85b 100644
--- a/web/dashboard.py
+++ b/web/dashboard.py
@@ -13,7 +13,7 @@ import asyncio
import json
import logging
import time
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
from threading import Thread
from typing import Dict, List, Optional, Any
@@ -71,15 +71,15 @@ class TradingDashboard:
def _setup_layout(self):
"""Setup the dashboard layout"""
self.app.layout = html.Div([
- # Header
+ # Compact Header
html.Div([
- html.H1([
- html.I(className="fas fa-chart-line me-3"),
- "Trading System Dashboard"
- ], className="text-white mb-0"),
- html.P(f"Multi-Modal AI Trading • Memory: {self.model_registry.total_memory_limit_mb/1024:.1f}GB Limit",
- className="text-light mb-0 opacity-75")
- ], className="bg-dark p-4 mb-4"),
+ html.H3([
+ html.I(className="fas fa-chart-line me-2"),
+ "Live Trading Dashboard"
+ ], className="text-white mb-1"),
+ html.P(f"Ultra-Fast Updates • Memory: {self.model_registry.total_memory_limit_mb/1024:.1f}GB",
+ className="text-light mb-0 opacity-75 small")
+ ], className="bg-dark p-2 mb-2"),
# Auto-refresh component
dcc.Interval(
@@ -88,88 +88,95 @@ class TradingDashboard:
n_intervals=0
),
- # Main content
+ # Main content - Compact layout
html.Div([
- # Top row - Key metrics
+ # Top row - Key metrics (more compact)
html.Div([
html.Div([
html.Div([
- html.H4(id="current-price", className="text-success mb-1"),
- html.P("Current Price", className="text-muted mb-0 small")
- ], className="card-body text-center")
- ], className="card bg-light"),
+ html.H5(id="current-price", className="text-success mb-0 small"),
+ html.P("Live Price", className="text-muted mb-0 tiny")
+ ], className="card-body text-center p-2")
+ ], className="card bg-light", style={"height": "60px"}),
html.Div([
html.Div([
- html.H4(id="total-pnl", className="mb-1"),
- html.P("Total P&L", className="text-muted mb-0 small")
- ], className="card-body text-center")
- ], className="card bg-light"),
+ html.H5(id="session-pnl", className="mb-0 small"),
+ html.P("Session P&L", className="text-muted mb-0 tiny")
+ ], className="card-body text-center p-2")
+ ], className="card bg-light", style={"height": "60px"}),
html.Div([
html.Div([
- html.H4(id="win-rate", className="text-info mb-1"),
- html.P("Win Rate", className="text-muted mb-0 small")
- ], className="card-body text-center")
- ], className="card bg-light"),
+ html.H5(id="current-position", className="text-info mb-0 small"),
+ html.P("Position", className="text-muted mb-0 tiny")
+ ], className="card-body text-center p-2")
+ ], className="card bg-light", style={"height": "60px"}),
html.Div([
html.Div([
- html.H4(id="memory-usage", className="text-warning mb-1"),
- html.P("Memory Usage", className="text-muted mb-0 small")
- ], className="card-body text-center")
- ], className="card bg-light"),
- ], className="row g-3 mb-4"),
+ html.H5(id="trade-count", className="text-warning mb-0 small"),
+ html.P("Trades", className="text-muted mb-0 tiny")
+ ], className="card-body text-center p-2")
+ ], className="card bg-light", style={"height": "60px"}),
+
+ html.Div([
+ html.Div([
+ html.H5(id="memory-usage", className="text-secondary mb-0 small"),
+ html.P("Memory", className="text-muted mb-0 tiny")
+ ], className="card-body text-center p-2")
+ ], className="card bg-light", style={"height": "60px"}),
+ ], className="row g-2 mb-3"),
- # Charts row
+ # Charts row - More compact
html.Div([
- # Price chart
+ # Price chart - Full width
html.Div([
html.Div([
- html.H5([
+ html.H6([
html.I(className="fas fa-chart-candlestick me-2"),
- "Price Chart"
- ], className="card-title"),
- dcc.Graph(id="price-chart", style={"height": "400px"})
- ], className="card-body")
- ], className="card"),
-
- # Model performance chart
- html.Div([
- html.Div([
- html.H5([
- html.I(className="fas fa-brain me-2"),
- "Model Performance"
- ], className="card-title"),
- dcc.Graph(id="model-performance-chart", style={"height": "400px"})
- ], className="card-body")
- ], className="card")
- ], className="row g-3 mb-4"),
+ "Live Price Chart with Trading Signals"
+ ], className="card-title mb-2"),
+ dcc.Graph(id="price-chart", style={"height": "350px"})
+ ], className="card-body p-2")
+ ], className="card", style={"width": "100%"}),
+ ], className="row g-2 mb-3"),
- # Bottom row - Recent decisions and system status
+ # Bottom row - Trading info and performance
html.Div([
- # Recent decisions
+ # Recent decisions - More compact
html.Div([
html.Div([
- html.H5([
+ html.H6([
html.I(className="fas fa-robot me-2"),
- "Recent Trading Decisions"
- ], className="card-title"),
- html.Div(id="recent-decisions", style={"maxHeight": "300px", "overflowY": "auto"})
- ], className="card-body")
+ "Recent Trading Signals"
+ ], className="card-title mb-2"),
+ html.Div(id="recent-decisions", style={"maxHeight": "200px", "overflowY": "auto"})
+ ], className="card-body p-2")
], className="card"),
- # System status
+ # Session performance
html.Div([
html.Div([
- html.H5([
+ html.H6([
+ html.I(className="fas fa-chart-pie me-2"),
+ "Session Performance"
+ ], className="card-title mb-2"),
+ html.Div(id="session-performance")
+ ], className="card-body p-2")
+ ], className="card"),
+
+ # System status - More compact
+ html.Div([
+ html.Div([
+ html.H6([
html.I(className="fas fa-server me-2"),
"System Status"
- ], className="card-title"),
+ ], className="card-title mb-2"),
html.Div(id="system-status")
- ], className="card-body")
+ ], className="card-body p-2")
], className="card")
- ], className="row g-3")
+ ], className="row g-2")
], className="container-fluid")
])
@@ -179,22 +186,25 @@ class TradingDashboard:
@self.app.callback(
[
Output('current-price', 'children'),
- Output('total-pnl', 'children'),
- Output('total-pnl', 'className'),
- Output('win-rate', 'children'),
+ Output('session-pnl', 'children'),
+ Output('session-pnl', 'className'),
+ Output('current-position', 'children'),
+ Output('trade-count', 'children'),
Output('memory-usage', 'children'),
Output('price-chart', 'figure'),
- Output('model-performance-chart', 'figure'),
Output('recent-decisions', 'children'),
+ Output('session-performance', 'children'),
Output('system-status', 'children')
],
[Input('interval-component', 'n_intervals')]
)
def update_dashboard(n_intervals):
- """Update all dashboard components"""
+ """Update all dashboard components with trading signals"""
try:
# Get current prices with fallback
symbol = self.config.symbols[0] if self.config.symbols else "ETH/USDT"
+ current_price = None
+ chart_data = None
try:
# Try to get fresh current price from latest data - OPTIMIZED FOR SPEED
@@ -223,11 +233,42 @@ class TradingDashboard:
logger.warning(f"[ERROR] Error getting price for {symbol}: {e}")
current_price = None
- # Get model performance metrics with fallback
+ # Get chart data for signal generation
try:
- performance_metrics = self.orchestrator.get_performance_metrics()
- except:
- performance_metrics = {}
+ chart_data = self.data_provider.get_historical_data(symbol, '1m', limit=50, refresh=False)
+ except Exception as e:
+ logger.warning(f"[ERROR] Error getting chart data: {e}")
+ chart_data = None
+
+ # Generate trading signal EVERY update (more aggressive for demo)
+ try:
+ if current_price and chart_data is not None and not chart_data.empty and len(chart_data) >= 10:
+ # Only generate demo signals occasionally since we now have real orchestrator signals
+ # Generate signal with lower frequency for demo (every 30 seconds instead of every update)
+ if n_intervals % 30 == 0: # Every 30 seconds for demo
+ signal = self._generate_trading_signal(symbol, current_price, chart_data)
+ if signal:
+ signal['reason'] = 'Dashboard demo signal' # Mark as demo
+ logger.info(f"[DEMO_SIGNAL] Generated {signal['action']} signal @ ${signal['price']:.2f} (confidence: {signal['confidence']:.1%})")
+ self._process_trading_decision(signal)
+
+ # Force a demo signal only if no recent orchestrator signals (every 60 updates = 1 minute)
+ elif n_intervals % 60 == 0:
+ # Check if we have recent orchestrator signals
+ recent_orchestrator_signals = [
+ d for d in self.recent_decisions[-10:]
+ if isinstance(d, dict) and 'reason' in d and 'Orchestrator' in str(d['reason'])
+ ]
+
+ if len(recent_orchestrator_signals) == 0:
+ logger.info("[DEMO] No recent orchestrator signals - forcing demo signal for visualization")
+ self._force_demo_signal(symbol, current_price)
+ except Exception as e:
+ logger.warning(f"[ERROR] Error generating trading signal: {e}")
+
+ # Calculate PnL metrics
+ unrealized_pnl = self._calculate_unrealized_pnl(current_price) if current_price else 0.0
+ total_session_pnl = self.total_realized_pnl + unrealized_pnl
# Get memory stats with fallback
try:
@@ -235,17 +276,6 @@ class TradingDashboard:
except:
memory_stats = {'utilization_percent': 0, 'total_used_mb': 0, 'total_limit_mb': 1024}
- # Calculate P&L from recent decisions
- total_pnl = 0.0
- wins = 0
- total_trades = len(self.recent_decisions)
-
- for decision in self.recent_decisions[-20:]: # Last 20 decisions
- if hasattr(decision, 'pnl') and decision.pnl:
- total_pnl += decision.pnl
- if decision.pnl > 0:
- wins += 1
-
# Format outputs with safe defaults and update indicators
update_time = datetime.now().strftime("%H:%M:%S.%f")[:-3] # Include milliseconds
price_text = f"${current_price:.2f}" if current_price else "No Data"
@@ -253,9 +283,22 @@ class TradingDashboard:
# Add tick indicator and precise timestamp (no emojis to avoid Unicode issues)
tick_indicator = "[LIVE]" if (datetime.now().microsecond // 100000) % 2 else "[TICK]" # Alternating indicator
price_text += f" {tick_indicator} @ {update_time}"
- pnl_text = f"${total_pnl:.2f}"
- pnl_class = "text-success mb-1" if total_pnl >= 0 else "text-danger mb-1"
- win_rate_text = f"{(wins/total_trades*100):.1f}%" if total_trades > 0 else "0.0%"
+
+ # PnL formatting
+ pnl_text = f"${total_session_pnl:.2f}"
+ pnl_class = "text-success mb-0 small" if total_session_pnl >= 0 else "text-danger mb-0 small"
+
+ # Position info
+ if self.current_position:
+ pos_side = self.current_position['side']
+ pos_size = self.current_position['size']
+ pos_price = self.current_position['price']
+ position_text = f"{pos_side} {pos_size} @ ${pos_price:.2f}"
+ else:
+ position_text = "None"
+
+ # Trade count
+ trade_count_text = f"{len(self.session_trades)}"
memory_text = f"{memory_stats['utilization_percent']:.1f}%"
# Create charts with error handling
@@ -265,12 +308,6 @@ class TradingDashboard:
logger.warning(f"Price chart error: {e}")
price_chart = self._create_empty_chart("Price Chart", "No price data available")
- try:
- performance_chart = self._create_performance_chart(performance_metrics)
- except Exception as e:
- logger.warning(f"Performance chart error: {e}")
- performance_chart = self._create_empty_chart("Performance", "No performance data available")
-
# Create recent decisions list
try:
decisions_list = self._create_decisions_list()
@@ -278,6 +315,13 @@ class TradingDashboard:
logger.warning(f"Decisions list error: {e}")
decisions_list = [html.P("No decisions available", className="text-muted")]
+ # Create session performance
+ try:
+ session_perf = self._create_session_performance()
+ except Exception as e:
+ logger.warning(f"Session performance error: {e}")
+ session_perf = [html.P("Performance data unavailable", className="text-muted")]
+
# Create system status
try:
system_status = self._create_system_status(memory_stats)
@@ -286,8 +330,8 @@ class TradingDashboard:
system_status = [html.P("System status unavailable", className="text-muted")]
return (
- price_text, pnl_text, pnl_class, win_rate_text, memory_text,
- price_chart, performance_chart, decisions_list, system_status
+ price_text, pnl_text, pnl_class, position_text, trade_count_text, memory_text,
+ price_chart, decisions_list, session_perf, system_status
)
except Exception as e:
@@ -296,9 +340,10 @@ class TradingDashboard:
empty_fig = self._create_empty_chart("Error", "Dashboard error - check logs")
return (
- "Error", "$0.00", "text-muted mb-1", "0.0%", "0.0%",
- empty_fig, empty_fig,
+ "Error", "$0.00", "text-muted mb-0 small", "None", "0", "0.0%",
+ empty_fig,
[html.P("Error loading decisions", className="text-danger")],
+ [html.P("Error loading performance", className="text-danger")],
[html.P("Error loading status", className="text-danger")]
)
@@ -422,25 +467,80 @@ class TradingDashboard:
opacity=0.8
))
- # Mark recent trading decisions
- for decision in self.recent_decisions[-5:]: # Show last 5 decisions
- if hasattr(decision, 'timestamp') and hasattr(decision, 'price'):
- color = '#00ff88' if decision.action == 'BUY' else '#ff6b6b' if decision.action == 'SELL' else '#ffa500'
- symbol_shape = 'triangle-up' if decision.action == 'BUY' else 'triangle-down' if decision.action == 'SELL' else 'circle'
-
+ # Mark recent trading decisions with proper markers - SHOW ALL SIGNALS IN CHART TIMEFRAME
+ if self.recent_decisions and not df.empty:
+ # Get the timeframe of displayed candles
+ chart_start_time = df['timestamp'].min() if 'timestamp' in df.columns else df.index.min()
+ chart_end_time = df['timestamp'].max() if 'timestamp' in df.columns else df.index.max()
+
+ # Filter decisions to only those within the chart timeframe
+ buy_decisions = []
+ sell_decisions = []
+
+ for decision in self.recent_decisions: # Check ALL decisions, not just last 10
+ if isinstance(decision, dict) and 'timestamp' in decision and 'price' in decision and 'action' in decision:
+ decision_time = decision['timestamp']
+
+ # Convert decision timestamp to match chart timezone if needed
+ if isinstance(decision_time, datetime):
+ if decision_time.tzinfo is not None:
+ # Convert to UTC for comparison
+ decision_time_utc = decision_time.astimezone(timezone.utc).replace(tzinfo=None)
+ else:
+ decision_time_utc = decision_time
+ else:
+ continue
+
+ # Convert chart times to UTC for comparison
+ if isinstance(chart_start_time, pd.Timestamp):
+ chart_start_utc = chart_start_time.tz_localize(None) if chart_start_time.tz is None else chart_start_time.tz_convert('UTC').tz_localize(None)
+ chart_end_utc = chart_end_time.tz_localize(None) if chart_end_time.tz is None else chart_end_time.tz_convert('UTC').tz_localize(None)
+ else:
+ chart_start_utc = pd.to_datetime(chart_start_time).tz_localize(None)
+ chart_end_utc = pd.to_datetime(chart_end_time).tz_localize(None)
+
+ # Check if decision falls within chart timeframe
+ decision_time_pd = pd.to_datetime(decision_time_utc)
+ if chart_start_utc <= decision_time_pd <= chart_end_utc:
+ if decision['action'] == 'BUY':
+ buy_decisions.append(decision)
+ elif decision['action'] == 'SELL':
+ sell_decisions.append(decision)
+
+ logger.info(f"[CHART] Showing {len(buy_decisions)} BUY and {len(sell_decisions)} SELL signals in chart timeframe")
+
+ # Add BUY markers (green triangles pointing up)
+ if buy_decisions:
fig.add_trace(go.Scatter(
- x=[decision.timestamp],
- y=[decision.price],
+ x=[d['timestamp'] for d in buy_decisions],
+ y=[d['price'] for d in buy_decisions],
mode='markers',
marker=dict(
- color=color,
+ color='#00ff88',
size=12,
- symbol=symbol_shape,
+ symbol='triangle-up',
line=dict(color='white', width=2)
),
- name=f"{decision.action}",
- showlegend=False,
- hovertemplate=f"{decision.action}
Price: ${decision.price:.2f}
Time: %{{x}}
Confidence: {decision.confidence:.1%}"
+ name="BUY Signals",
+ showlegend=True,
+ hovertemplate="BUY SIGNAL
Price: $%{y:.2f}
Time: %{x}
"
+ ))
+
+ # Add SELL markers (red triangles pointing down)
+ if sell_decisions:
+ fig.add_trace(go.Scatter(
+ x=[d['timestamp'] for d in sell_decisions],
+ y=[d['price'] for d in sell_decisions],
+ mode='markers',
+ marker=dict(
+ color='#ff6b6b',
+ size=12,
+ symbol='triangle-down',
+ line=dict(color='white', width=2)
+ ),
+ name="SELL Signals",
+ showlegend=True,
+ hovertemplate="SELL SIGNAL
Price: $%{y:.2f}
Time: %{x}
"
))
# Update layout with current timestamp
@@ -559,27 +659,53 @@ class TradingDashboard:
decisions_html = []
for decision in self.recent_decisions[-10:][::-1]: # Last 10, newest first
+ # Handle both dict and object formats
+ if isinstance(decision, dict):
+ action = decision.get('action', 'UNKNOWN')
+ price = decision.get('price', 0)
+ confidence = decision.get('confidence', 0)
+ timestamp = decision.get('timestamp', datetime.now(timezone.utc))
+ symbol = decision.get('symbol', 'N/A')
+ else:
+ # Legacy object format
+ action = getattr(decision, 'action', 'UNKNOWN')
+ price = getattr(decision, 'price', 0)
+ confidence = getattr(decision, 'confidence', 0)
+ timestamp = getattr(decision, 'timestamp', datetime.now(timezone.utc))
+ symbol = getattr(decision, 'symbol', 'N/A')
+
# Determine action color and icon
- if decision.action == 'BUY':
+ if action == 'BUY':
action_class = "text-success"
icon_class = "fas fa-arrow-up"
- elif decision.action == 'SELL':
+ elif action == 'SELL':
action_class = "text-danger"
icon_class = "fas fa-arrow-down"
else:
action_class = "text-secondary"
icon_class = "fas fa-minus"
- time_str = decision.timestamp.strftime("%H:%M:%S") if hasattr(decision, 'timestamp') else "N/A"
- confidence_pct = f"{decision.confidence*100:.1f}%" if hasattr(decision, 'confidence') else "N/A"
+ # Convert UTC timestamp to local time for display
+ if isinstance(timestamp, datetime):
+ if timestamp.tzinfo is not None:
+ # Convert from UTC to local time for display
+ local_timestamp = timestamp.astimezone()
+ time_str = local_timestamp.strftime("%H:%M:%S")
+ else:
+ # Assume UTC if no timezone info
+ time_str = timestamp.strftime("%H:%M:%S")
+ else:
+ time_str = "N/A"
+
+ confidence_pct = f"{confidence*100:.1f}%" if confidence else "N/A"
decisions_html.append(
html.Div([
html.Div([
html.I(className=f"{icon_class} me-2"),
- html.Strong(decision.action, className=action_class),
- html.Span(f" {decision.symbol} ", className="text-muted"),
- html.Small(f"@${decision.price:.2f}", className="text-muted")
+ html.Strong(action, className=action_class),
+ html.Span(f" {symbol} ", className="text-muted"),
+ html.Small(f"@${price:.2f}", className="text-muted")
], className="d-flex align-items-center"),
html.Small([
html.Span(f"Confidence: {confidence_pct} • ", className="text-info"),
@@ -654,9 +780,8 @@ class TradingDashboard:
def add_trading_decision(self, decision: TradingDecision):
"""Add a trading decision to the dashboard"""
self.recent_decisions.append(decision)
- # Keep only last 100 decisions
- if len(self.recent_decisions) > 100:
- self.recent_decisions = self.recent_decisions[-100:]
+ if len(self.recent_decisions) > 500: # Keep last 500 decisions (increased from 50) to cover chart timeframe
+ self.recent_decisions = self.recent_decisions[-500:]
def _get_real_model_accuracies(self) -> List[float]:
"""
@@ -708,6 +833,196 @@ class TradingDashboard:
logger.error(f"❌ Error retrieving real model accuracies: {e}")
return []
+ def _generate_trading_signal(self, symbol: str, current_price: float, df: pd.DataFrame) -> Optional[Dict]:
+ """
+ Generate realistic trading signals based on price action and indicators
+ Returns trading decision dict or None
+ """
+ try:
+ if df is None or df.empty or len(df) < 20:
+ return None
+
+ # Get recent price action
+ recent_prices = df['close'].tail(20).values # More data for better signals
+
+ if len(recent_prices) >= 10:
+ # More balanced signal generation for demo visualization
+ short_ma = np.mean(recent_prices[-3:]) # 3-period MA
+ medium_ma = np.mean(recent_prices[-7:]) # 7-period MA
+ long_ma = np.mean(recent_prices[-15:]) # 15-period MA
+
+ # Calculate momentum and trend strength
+ momentum = (short_ma - long_ma) / long_ma
+ trend_strength = abs(momentum)
+ price_change_pct = (current_price - recent_prices[0]) / recent_prices[0]
+
+ # Add randomness to make signals more frequent and balanced for demo
+ import random
+ random_factor = random.uniform(0.2, 1.0) # Lower threshold for more signals
+
+ # Create more balanced signal conditions (less strict)
+ buy_conditions = [
+ (short_ma > medium_ma and momentum > 0.0003), # Trend alignment + momentum
+ (price_change_pct > 0.0008 and random_factor > 0.4), # Price movement + luck
+ (momentum > 0.0001 and random_factor > 0.6), # Weak momentum + higher luck
+ (random_factor > 0.85) # Pure luck for demo balance
+ ]
+
+ sell_conditions = [
+ (short_ma < medium_ma and momentum < -0.0003), # Trend alignment + momentum
+ (price_change_pct < -0.0008 and random_factor > 0.4), # Price movement + luck
+ (momentum < -0.0001 and random_factor > 0.6), # Weak momentum + higher luck
+ (random_factor < 0.15) # Pure luck for demo balance
+ ]
+
+ buy_signal = any(buy_conditions)
+ sell_signal = any(sell_conditions)
+
+ # Ensure we don't have both signals at once, prioritize the stronger one
+ if buy_signal and sell_signal:
+ if abs(momentum) > 0.0005:
+ # Use momentum to decide
+ buy_signal = momentum > 0
+ sell_signal = momentum < 0
+ else:
+ # Use random to break tie for demo
+ if random_factor > 0.5:
+ sell_signal = False
+ else:
+ buy_signal = False
+
+ if buy_signal:
+ confidence = min(0.95, trend_strength * 80 + random.uniform(0.6, 0.85))
+ return {
+ 'action': 'BUY',
+ 'symbol': symbol,
+ 'price': current_price,
+ 'confidence': confidence,
+ 'timestamp': datetime.now(timezone.utc), # Use UTC to match candle data
+ 'size': 0.1,
+ 'reason': f'Bullish momentum: {momentum:.5f}, trend: {trend_strength:.5f}, random: {random_factor:.3f}'
+ }
+ elif sell_signal:
+ confidence = min(0.95, trend_strength * 80 + random.uniform(0.6, 0.85))
+ return {
+ 'action': 'SELL',
+ 'symbol': symbol,
+ 'price': current_price,
+ 'confidence': confidence,
+ 'timestamp': datetime.now(timezone.utc), # Use UTC to match candle data
+ 'size': 0.1,
+ 'reason': f'Bearish momentum: {momentum:.5f}, trend: {trend_strength:.5f}, random: {random_factor:.3f}'
+ }
+
+ return None
+
+ except Exception as e:
+ logger.warning(f"Error generating trading signal: {e}")
+ return None
+
+ def _process_trading_decision(self, decision: Dict) -> None:
+ """Process a trading decision and update PnL tracking"""
+ try:
+ if not decision:
+ return
+
+ current_time = datetime.now(timezone.utc) # Use UTC for consistency
+ fee_rate = 0.001 # 0.1% trading fee
+
+ if decision['action'] == 'BUY':
+ if self.current_position is None:
+ # Open long position
+ fee = decision['price'] * decision['size'] * fee_rate
+ self.current_position = {
+ 'side': 'LONG',
+ 'price': decision['price'],
+ 'size': decision['size'],
+ 'timestamp': current_time,
+ 'fees': fee
+ }
+ self.total_fees += fee
+
+ trade_record = decision.copy()
+ trade_record['position_action'] = 'OPEN_LONG'
+ trade_record['fees'] = fee
+ self.session_trades.append(trade_record)
+
+ logger.info(f"[TRADE] OPENED LONG: {decision['size']} @ ${decision['price']:.2f}")
+
+ elif decision['action'] == 'SELL':
+ if self.current_position and self.current_position['side'] == 'LONG':
+ # Close long position
+ entry_price = self.current_position['price']
+ exit_price = decision['price']
+ size = self.current_position['size']
+
+ # Calculate PnL
+ gross_pnl = (exit_price - entry_price) * size
+ fee = exit_price * size * fee_rate
+ net_pnl = gross_pnl - fee - self.current_position['fees']
+
+ self.total_realized_pnl += net_pnl
+ self.total_fees += fee
+
+ trade_record = decision.copy()
+ trade_record['position_action'] = 'CLOSE_LONG'
+ trade_record['entry_price'] = entry_price
+ trade_record['pnl'] = net_pnl
+ trade_record['fees'] = fee
+ self.session_trades.append(trade_record)
+
+ logger.info(f"[TRADE] CLOSED LONG: {size} @ ${exit_price:.2f} | PnL: ${net_pnl:.2f}")
+
+ # Clear position
+ self.current_position = None
+
+ elif self.current_position is None:
+ # Open short position (for demo)
+ fee = decision['price'] * decision['size'] * fee_rate
+ self.current_position = {
+ 'side': 'SHORT',
+ 'price': decision['price'],
+ 'size': decision['size'],
+ 'timestamp': current_time,
+ 'fees': fee
+ }
+ self.total_fees += fee
+
+ trade_record = decision.copy()
+ trade_record['position_action'] = 'OPEN_SHORT'
+ trade_record['fees'] = fee
+ self.session_trades.append(trade_record)
+
+ logger.info(f"[TRADE] OPENED SHORT: {decision['size']} @ ${decision['price']:.2f}")
+
+ # Add to recent decisions
+ self.recent_decisions.append(decision)
+ if len(self.recent_decisions) > 500: # Keep last 500 decisions (increased from 50) to cover chart timeframe
+ self.recent_decisions = self.recent_decisions[-500:]
+
+ except Exception as e:
+ logger.error(f"Error processing trading decision: {e}")
+
+ def _calculate_unrealized_pnl(self, current_price: float) -> float:
+ """Calculate unrealized PnL for open position"""
+ try:
+ if not self.current_position:
+ return 0.0
+
+ entry_price = self.current_position['price']
+ size = self.current_position['size']
+
+ if self.current_position['side'] == 'LONG':
+ return (current_price - entry_price) * size
+ elif self.current_position['side'] == 'SHORT':
+ return (entry_price - current_price) * size
+
+ return 0.0
+
+ except Exception as e:
+ logger.warning(f"Error calculating unrealized PnL: {e}")
+ return 0.0
+
def run(self, host: str = '127.0.0.1', port: int = 8050, debug: bool = False):
"""Run the dashboard server"""
try:
@@ -719,6 +1034,10 @@ class TradingDashboard:
logger.info("Memory usage tracking")
logger.info("="*60)
+ # Start the orchestrator's real trading loop in background
+ logger.info("🚀 Starting REAL orchestrator trading loop...")
+ self._start_orchestrator_trading()
+
# Run the app (updated API for newer Dash versions)
self.app.run(
host=host,
@@ -732,6 +1051,144 @@ class TradingDashboard:
logger.error(f"Error running dashboard: {e}")
raise
+ def _start_orchestrator_trading(self):
+ """Start the orchestrator's continuous trading in a background thread"""
+ def orchestrator_loop():
+ """Run the orchestrator trading loop"""
+ try:
+ # Use asyncio.run for the orchestrator's async methods
+ import asyncio
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ # Add callback to integrate orchestrator decisions with dashboard
+ async def orchestrator_callback(decision):
+ """Callback to integrate orchestrator decisions with dashboard"""
+ try:
+ # Convert orchestrator decision to dashboard format
+ dashboard_decision = {
+ 'action': decision.action,
+ 'symbol': decision.symbol,
+ 'price': decision.price,
+ 'confidence': decision.confidence,
+ 'timestamp': decision.timestamp,
+ 'size': 0.1, # Default size
+ 'reason': f"Orchestrator decision: {decision.reasoning}"
+ }
+
+ # Process the real trading decision
+ self._process_trading_decision(dashboard_decision)
+
+ logger.info(f"[ORCHESTRATOR] Real trading decision: {decision.action} {decision.symbol} @ ${decision.price:.2f} (conf: {decision.confidence:.1%})")
+
+ except Exception as e:
+ logger.error(f"Error processing orchestrator decision: {e}")
+
+ # Add the callback to orchestrator
+ self.orchestrator.add_decision_callback(orchestrator_callback)
+
+ # Start continuous trading for configured symbols
+ symbols = self.config.symbols if self.config.symbols else ['ETH/USDT']
+ logger.info(f"[ORCHESTRATOR] Starting continuous trading for: {symbols}")
+
+ # Run the orchestrator
+ loop.run_until_complete(self.orchestrator.start_continuous_trading(symbols))
+
+ except Exception as e:
+ logger.error(f"Error in orchestrator trading loop: {e}")
+ import traceback
+ logger.error(traceback.format_exc())
+
+ # Start orchestrator in background thread
+ orchestrator_thread = Thread(target=orchestrator_loop, daemon=True)
+ orchestrator_thread.start()
+ logger.info("[ORCHESTRATOR] Real trading loop started in background")
+
+ def _create_session_performance(self) -> List:
+ """Create session performance display"""
+ try:
+ session_duration = datetime.now() - self.session_start
+ duration_str = f"{session_duration.seconds//3600:02d}:{(session_duration.seconds//60)%60:02d}:{session_duration.seconds%60:02d}"
+
+ # Calculate win rate
+ winning_trades = [t for t in self.session_trades if 'pnl' in t and t['pnl'] > 0]
+ losing_trades = [t for t in self.session_trades if 'pnl' in t and t['pnl'] < 0]
+ closed_trades = len(winning_trades) + len(losing_trades)
+ win_rate = (len(winning_trades) / closed_trades * 100) if closed_trades > 0 else 0
+
+ # Calculate other metrics
+ total_volume = sum(t.get('price', 0) * t.get('size', 0) for t in self.session_trades)
+ avg_trade_pnl = (self.total_realized_pnl / closed_trades) if closed_trades > 0 else 0
+
+ performance_items = [
+ html.Div([
+ html.Strong("Session Duration: "),
+ html.Span(duration_str, className="text-info")
+ ], className="mb-1 small"),
+
+ html.Div([
+ html.Strong("Realized P&L: "),
+ html.Span(f"${self.total_realized_pnl:.2f}",
+ className="text-success" if self.total_realized_pnl >= 0 else "text-danger")
+ ], className="mb-1 small"),
+
+ html.Div([
+ html.Strong("Total Trades: "),
+ html.Span(f"{len(self.session_trades)}", className="text-info")
+ ], className="mb-1 small"),
+
+ html.Div([
+ html.Strong("Win Rate: "),
+ html.Span(f"{win_rate:.1f}%",
+ className="text-success" if win_rate >= 50 else "text-warning")
+ ], className="mb-1 small"),
+
+ html.Div([
+ html.Strong("Avg Trade: "),
+ html.Span(f"${avg_trade_pnl:.2f}",
+ className="text-success" if avg_trade_pnl >= 0 else "text-danger")
+ ], className="mb-1 small"),
+
+ html.Div([
+ html.Strong("Total Fees: "),
+ html.Span(f"${self.total_fees:.2f}", className="text-muted")
+ ], className="mb-1 small"),
+ ]
+
+ return performance_items
+
+ except Exception as e:
+ logger.error(f"Error creating session performance: {e}")
+ return [html.P(f"Error: {str(e)}", className="text-danger")]
+
+ def _force_demo_signal(self, symbol: str, current_price: float) -> None:
+ """Force a demo trading signal for visualization"""
+ try:
+ import random
+
+ if not current_price:
+ return
+
+ # Randomly choose BUY or SELL for demo
+ action = random.choice(['BUY', 'SELL'])
+ confidence = random.uniform(0.65, 0.85)
+
+ signal = {
+ 'action': action,
+ 'symbol': symbol,
+ 'price': current_price,
+ 'confidence': confidence,
+ 'timestamp': datetime.now(timezone.utc), # Use UTC to match candle data
+ 'size': 0.1,
+ 'reason': 'Demo signal for visualization'
+ }
+
+ logger.info(f"[DEMO] Forced {action} signal @ ${current_price:.2f} (confidence: {confidence:.1%})")
+ self._process_trading_decision(signal)
+
+ except Exception as e:
+ logger.warning(f"Error forcing demo signal: {e}")
+
# Convenience function for integration
def create_dashboard(data_provider: DataProvider = None, orchestrator: TradingOrchestrator = None) -> TradingDashboard:
"""Create and return a trading dashboard instance"""