fixed dash

This commit is contained in:
Dobromir Popov 2025-05-25 11:01:28 +03:00
parent 3c23e4ec42
commit ed9df06855
2 changed files with 577 additions and 120 deletions

View File

@ -295,8 +295,8 @@ def run_web_dashboard():
if test_data is None or test_data.empty: if test_data is None or test_data.empty:
logger.warning("⚠️ No data available - starting dashboard with demo mode...") logger.warning("⚠️ No data available - starting dashboard with demo mode...")
else: else:
logger.info(" Data connection verified") logger.info("[SUCCESS] Data connection verified")
logger.info(f" Fetched {len(test_data)} candles for validation") logger.info(f"[SUCCESS] Fetched {len(test_data)} candles for validation")
# Initialize orchestrator with real data only # Initialize orchestrator with real data only
orchestrator = TradingOrchestrator(data_provider) orchestrator = TradingOrchestrator(data_provider)
@ -305,8 +305,8 @@ def run_web_dashboard():
from web.dashboard import TradingDashboard from web.dashboard import TradingDashboard
dashboard = TradingDashboard(data_provider, orchestrator) dashboard = TradingDashboard(data_provider, orchestrator)
logger.info("🎯 LAUNCHING DASHBOARD") logger.info("[LAUNCH] LAUNCHING DASHBOARD")
logger.info(f"🌐 Access at: http://127.0.0.1:8050") logger.info(f"[ACCESS] Access at: http://127.0.0.1:8050")
# Run the dashboard # Run the dashboard
dashboard.run(host='127.0.0.1', port=8050, debug=False) dashboard.run(host='127.0.0.1', port=8050, debug=False)

View File

@ -13,7 +13,7 @@ import asyncio
import json import json
import logging import logging
import time import time
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
from threading import Thread from threading import Thread
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
@ -71,15 +71,15 @@ class TradingDashboard:
def _setup_layout(self): def _setup_layout(self):
"""Setup the dashboard layout""" """Setup the dashboard layout"""
self.app.layout = html.Div([ self.app.layout = html.Div([
# Header # Compact Header
html.Div([ html.Div([
html.H1([ html.H3([
html.I(className="fas fa-chart-line me-3"), html.I(className="fas fa-chart-line me-2"),
"Trading System Dashboard" "Live Trading Dashboard"
], className="text-white mb-0"), ], className="text-white mb-1"),
html.P(f"Multi-Modal AI Trading • Memory: {self.model_registry.total_memory_limit_mb/1024:.1f}GB Limit", html.P(f"Ultra-Fast Updates • Memory: {self.model_registry.total_memory_limit_mb/1024:.1f}GB",
className="text-light mb-0 opacity-75") className="text-light mb-0 opacity-75 small")
], className="bg-dark p-4 mb-4"), ], className="bg-dark p-2 mb-2"),
# Auto-refresh component # Auto-refresh component
dcc.Interval( dcc.Interval(
@ -88,88 +88,95 @@ class TradingDashboard:
n_intervals=0 n_intervals=0
), ),
# Main content # Main content - Compact layout
html.Div([ html.Div([
# Top row - Key metrics # Top row - Key metrics (more compact)
html.Div([ html.Div([
html.Div([ html.Div([
html.Div([ html.Div([
html.H4(id="current-price", className="text-success mb-1"), html.H5(id="current-price", className="text-success mb-0 small"),
html.P("Current Price", className="text-muted mb-0 small") html.P("Live Price", className="text-muted mb-0 tiny")
], className="card-body text-center") ], className="card-body text-center p-2")
], className="card bg-light"), ], className="card bg-light", style={"height": "60px"}),
html.Div([ html.Div([
html.Div([ html.Div([
html.H4(id="total-pnl", className="mb-1"), html.H5(id="session-pnl", className="mb-0 small"),
html.P("Total P&L", className="text-muted mb-0 small") html.P("Session P&L", className="text-muted mb-0 tiny")
], className="card-body text-center") ], className="card-body text-center p-2")
], className="card bg-light"), ], className="card bg-light", style={"height": "60px"}),
html.Div([ html.Div([
html.Div([ html.Div([
html.H4(id="win-rate", className="text-info mb-1"), html.H5(id="current-position", className="text-info mb-0 small"),
html.P("Win Rate", className="text-muted mb-0 small") html.P("Position", className="text-muted mb-0 tiny")
], className="card-body text-center") ], className="card-body text-center p-2")
], className="card bg-light"), ], className="card bg-light", style={"height": "60px"}),
html.Div([ html.Div([
html.Div([ html.Div([
html.H4(id="memory-usage", className="text-warning mb-1"), html.H5(id="trade-count", className="text-warning mb-0 small"),
html.P("Memory Usage", className="text-muted mb-0 small") html.P("Trades", className="text-muted mb-0 tiny")
], className="card-body text-center") ], className="card-body text-center p-2")
], className="card bg-light"), ], className="card bg-light", style={"height": "60px"}),
], className="row g-3 mb-4"),
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([ html.Div([
# Price chart # Price chart - Full width
html.Div([ html.Div([
html.Div([ html.Div([
html.H5([ html.H6([
html.I(className="fas fa-chart-candlestick me-2"), html.I(className="fas fa-chart-candlestick me-2"),
"Price Chart" "Live Price Chart with Trading Signals"
], className="card-title"), ], className="card-title mb-2"),
dcc.Graph(id="price-chart", style={"height": "400px"}) dcc.Graph(id="price-chart", style={"height": "350px"})
], className="card-body") ], className="card-body p-2")
], className="card"), ], className="card", style={"width": "100%"}),
], className="row g-2 mb-3"),
# 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"),
# Bottom row - Recent decisions and system status # Bottom row - Trading info and performance
html.Div([ html.Div([
# Recent decisions # Recent decisions - More compact
html.Div([ html.Div([
html.Div([ html.Div([
html.H5([ html.H6([
html.I(className="fas fa-robot me-2"), html.I(className="fas fa-robot me-2"),
"Recent Trading Decisions" "Recent Trading Signals"
], className="card-title"), ], className="card-title mb-2"),
html.Div(id="recent-decisions", style={"maxHeight": "300px", "overflowY": "auto"}) html.Div(id="recent-decisions", style={"maxHeight": "200px", "overflowY": "auto"})
], className="card-body") ], className="card-body p-2")
], className="card"), ], className="card"),
# System status # Session performance
html.Div([ html.Div([
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"), html.I(className="fas fa-server me-2"),
"System Status" "System Status"
], className="card-title"), ], className="card-title mb-2"),
html.Div(id="system-status") html.Div(id="system-status")
], className="card-body") ], className="card-body p-2")
], className="card") ], className="card")
], className="row g-3") ], className="row g-2")
], className="container-fluid") ], className="container-fluid")
]) ])
@ -179,22 +186,25 @@ class TradingDashboard:
@self.app.callback( @self.app.callback(
[ [
Output('current-price', 'children'), Output('current-price', 'children'),
Output('total-pnl', 'children'), Output('session-pnl', 'children'),
Output('total-pnl', 'className'), Output('session-pnl', 'className'),
Output('win-rate', 'children'), Output('current-position', 'children'),
Output('trade-count', 'children'),
Output('memory-usage', 'children'), Output('memory-usage', 'children'),
Output('price-chart', 'figure'), Output('price-chart', 'figure'),
Output('model-performance-chart', 'figure'),
Output('recent-decisions', 'children'), Output('recent-decisions', 'children'),
Output('session-performance', 'children'),
Output('system-status', 'children') Output('system-status', 'children')
], ],
[Input('interval-component', 'n_intervals')] [Input('interval-component', 'n_intervals')]
) )
def update_dashboard(n_intervals): def update_dashboard(n_intervals):
"""Update all dashboard components""" """Update all dashboard components with trading signals"""
try: try:
# Get current prices with fallback # Get current prices with fallback
symbol = self.config.symbols[0] if self.config.symbols else "ETH/USDT" symbol = self.config.symbols[0] if self.config.symbols else "ETH/USDT"
current_price = None
chart_data = None
try: try:
# Try to get fresh current price from latest data - OPTIMIZED FOR SPEED # 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}") logger.warning(f"[ERROR] Error getting price for {symbol}: {e}")
current_price = None current_price = None
# Get model performance metrics with fallback # Get chart data for signal generation
try: try:
performance_metrics = self.orchestrator.get_performance_metrics() chart_data = self.data_provider.get_historical_data(symbol, '1m', limit=50, refresh=False)
except: except Exception as e:
performance_metrics = {} 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 # Get memory stats with fallback
try: try:
@ -235,17 +276,6 @@ class TradingDashboard:
except: except:
memory_stats = {'utilization_percent': 0, 'total_used_mb': 0, 'total_limit_mb': 1024} 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 # Format outputs with safe defaults and update indicators
update_time = datetime.now().strftime("%H:%M:%S.%f")[:-3] # Include milliseconds update_time = datetime.now().strftime("%H:%M:%S.%f")[:-3] # Include milliseconds
price_text = f"${current_price:.2f}" if current_price else "No Data" 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) # 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 tick_indicator = "[LIVE]" if (datetime.now().microsecond // 100000) % 2 else "[TICK]" # Alternating indicator
price_text += f" {tick_indicator} @ {update_time}" 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" # PnL formatting
win_rate_text = f"{(wins/total_trades*100):.1f}%" if total_trades > 0 else "0.0%" 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}%" memory_text = f"{memory_stats['utilization_percent']:.1f}%"
# Create charts with error handling # Create charts with error handling
@ -265,12 +308,6 @@ class TradingDashboard:
logger.warning(f"Price chart error: {e}") logger.warning(f"Price chart error: {e}")
price_chart = self._create_empty_chart("Price Chart", "No price data available") 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 # Create recent decisions list
try: try:
decisions_list = self._create_decisions_list() decisions_list = self._create_decisions_list()
@ -278,6 +315,13 @@ class TradingDashboard:
logger.warning(f"Decisions list error: {e}") logger.warning(f"Decisions list error: {e}")
decisions_list = [html.P("No decisions available", className="text-muted")] 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 # Create system status
try: try:
system_status = self._create_system_status(memory_stats) system_status = self._create_system_status(memory_stats)
@ -286,8 +330,8 @@ class TradingDashboard:
system_status = [html.P("System status unavailable", className="text-muted")] system_status = [html.P("System status unavailable", className="text-muted")]
return ( return (
price_text, pnl_text, pnl_class, win_rate_text, memory_text, price_text, pnl_text, pnl_class, position_text, trade_count_text, memory_text,
price_chart, performance_chart, decisions_list, system_status price_chart, decisions_list, session_perf, system_status
) )
except Exception as e: except Exception as e:
@ -296,9 +340,10 @@ class TradingDashboard:
empty_fig = self._create_empty_chart("Error", "Dashboard error - check logs") empty_fig = self._create_empty_chart("Error", "Dashboard error - check logs")
return ( return (
"Error", "$0.00", "text-muted mb-1", "0.0%", "0.0%", "Error", "$0.00", "text-muted mb-0 small", "None", "0", "0.0%",
empty_fig, empty_fig, empty_fig,
[html.P("Error loading decisions", className="text-danger")], [html.P("Error loading decisions", className="text-danger")],
[html.P("Error loading performance", className="text-danger")],
[html.P("Error loading status", className="text-danger")] [html.P("Error loading status", className="text-danger")]
) )
@ -422,25 +467,80 @@ class TradingDashboard:
opacity=0.8 opacity=0.8
)) ))
# Mark recent trading decisions # Mark recent trading decisions with proper markers - SHOW ALL SIGNALS IN CHART TIMEFRAME
for decision in self.recent_decisions[-5:]: # Show last 5 decisions if self.recent_decisions and not df.empty:
if hasattr(decision, 'timestamp') and hasattr(decision, 'price'): # Get the timeframe of displayed candles
color = '#00ff88' if decision.action == 'BUY' else '#ff6b6b' if decision.action == 'SELL' else '#ffa500' chart_start_time = df['timestamp'].min() if 'timestamp' in df.columns else df.index.min()
symbol_shape = 'triangle-up' if decision.action == 'BUY' else 'triangle-down' if decision.action == 'SELL' else 'circle' 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( fig.add_trace(go.Scatter(
x=[decision.timestamp], x=[d['timestamp'] for d in buy_decisions],
y=[decision.price], y=[d['price'] for d in buy_decisions],
mode='markers', mode='markers',
marker=dict( marker=dict(
color=color, color='#00ff88',
size=12, size=12,
symbol=symbol_shape, symbol='triangle-up',
line=dict(color='white', width=2) line=dict(color='white', width=2)
), ),
name=f"{decision.action}", name="BUY Signals",
showlegend=False, showlegend=True,
hovertemplate=f"<b>{decision.action}</b><br>Price: ${decision.price:.2f}<br>Time: %{{x}}<br>Confidence: {decision.confidence:.1%}<extra></extra>" hovertemplate="<b>BUY SIGNAL</b><br>Price: $%{y:.2f}<br>Time: %{x}<br><extra></extra>"
))
# 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="<b>SELL SIGNAL</b><br>Price: $%{y:.2f}<br>Time: %{x}<br><extra></extra>"
)) ))
# Update layout with current timestamp # Update layout with current timestamp
@ -559,27 +659,53 @@ class TradingDashboard:
decisions_html = [] decisions_html = []
for decision in self.recent_decisions[-10:][::-1]: # Last 10, newest first 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 # Determine action color and icon
if decision.action == 'BUY': if action == 'BUY':
action_class = "text-success" action_class = "text-success"
icon_class = "fas fa-arrow-up" icon_class = "fas fa-arrow-up"
elif decision.action == 'SELL': elif action == 'SELL':
action_class = "text-danger" action_class = "text-danger"
icon_class = "fas fa-arrow-down" icon_class = "fas fa-arrow-down"
else: else:
action_class = "text-secondary" action_class = "text-secondary"
icon_class = "fas fa-minus" icon_class = "fas fa-minus"
time_str = decision.timestamp.strftime("%H:%M:%S") if hasattr(decision, 'timestamp') else "N/A" # Convert UTC timestamp to local time for display
confidence_pct = f"{decision.confidence*100:.1f}%" if hasattr(decision, 'confidence') else "N/A" 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( decisions_html.append(
html.Div([ html.Div([
html.Div([ html.Div([
html.I(className=f"{icon_class} me-2"), html.I(className=f"{icon_class} me-2"),
html.Strong(decision.action, className=action_class), html.Strong(action, className=action_class),
html.Span(f" {decision.symbol} ", className="text-muted"), html.Span(f" {symbol} ", className="text-muted"),
html.Small(f"@${decision.price:.2f}", className="text-muted") html.Small(f"@${price:.2f}", className="text-muted")
], className="d-flex align-items-center"), ], className="d-flex align-items-center"),
html.Small([ html.Small([
html.Span(f"Confidence: {confidence_pct}", className="text-info"), html.Span(f"Confidence: {confidence_pct}", className="text-info"),
@ -654,9 +780,8 @@ class TradingDashboard:
def add_trading_decision(self, decision: TradingDecision): def add_trading_decision(self, decision: TradingDecision):
"""Add a trading decision to the dashboard""" """Add a trading decision to the dashboard"""
self.recent_decisions.append(decision) self.recent_decisions.append(decision)
# Keep only last 100 decisions if len(self.recent_decisions) > 500: # Keep last 500 decisions (increased from 50) to cover chart timeframe
if len(self.recent_decisions) > 100: self.recent_decisions = self.recent_decisions[-500:]
self.recent_decisions = self.recent_decisions[-100:]
def _get_real_model_accuracies(self) -> List[float]: def _get_real_model_accuracies(self) -> List[float]:
""" """
@ -708,6 +833,196 @@ class TradingDashboard:
logger.error(f"❌ Error retrieving real model accuracies: {e}") logger.error(f"❌ Error retrieving real model accuracies: {e}")
return [] 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): def run(self, host: str = '127.0.0.1', port: int = 8050, debug: bool = False):
"""Run the dashboard server""" """Run the dashboard server"""
try: try:
@ -719,6 +1034,10 @@ class TradingDashboard:
logger.info("Memory usage tracking") logger.info("Memory usage tracking")
logger.info("="*60) 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) # Run the app (updated API for newer Dash versions)
self.app.run( self.app.run(
host=host, host=host,
@ -732,6 +1051,144 @@ class TradingDashboard:
logger.error(f"Error running dashboard: {e}") logger.error(f"Error running dashboard: {e}")
raise 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 # Convenience function for integration
def create_dashboard(data_provider: DataProvider = None, orchestrator: TradingOrchestrator = None) -> TradingDashboard: def create_dashboard(data_provider: DataProvider = None, orchestrator: TradingOrchestrator = None) -> TradingDashboard:
"""Create and return a trading dashboard instance""" """Create and return a trading dashboard instance"""