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:
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)

View File

@ -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"}),
# Charts row
html.Div([
# Price chart
html.Div([
html.Div([
html.H5([
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 - More compact
html.Div([
# Price chart - Full width
html.Div([
html.Div([
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"),
"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"),
# Model performance chart
# Bottom row - Trading info and performance
html.Div([
# Recent decisions - More compact
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
html.Div([
# Recent decisions
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"<b>{decision.action}</b><br>Price: ${decision.price:.2f}<br>Time: %{{x}}<br>Confidence: {decision.confidence:.1%}<extra></extra>"
name="BUY Signals",
showlegend=True,
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
@ -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"""