fixed dash
This commit is contained in:
parent
3c23e4ec42
commit
ed9df06855
@ -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)
|
||||||
|
687
web/dashboard.py
687
web/dashboard.py
@ -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"),
|
|
||||||
|
|
||||||
# Charts row
|
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 - 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
|
# Bottom row - Trading info and performance
|
||||||
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([
|
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"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user