Merge commit 'd49a473ed6f4aef55bfdd47d6370e53582be6b7b' into cleanup

This commit is contained in:
Dobromir Popov
2025-10-01 00:32:19 +03:00
353 changed files with 81004 additions and 35899 deletions

View File

@@ -1 +0,0 @@
# Web module for trading system dashboard

File diff suppressed because it is too large Load Diff

View File

@@ -356,7 +356,8 @@ class COBDashboardServer:
if mid_price <= 0:
return
now = datetime.now()
from datetime import timezone
now = datetime.now(timezone.utc)
current_second = now.replace(microsecond=0)
# Get or create current candle
@@ -377,7 +378,7 @@ class COBDashboardServer:
if current_second > current_candle['timestamp']:
# Close previous candle
finished_candle = {
'timestamp': current_candle['timestamp'].isoformat(),
'timestamp': current_candle['timestamp'].isoformat(), # UTC ISO8601
'open': current_candle['open'],
'high': current_candle['high'],
'low': current_candle['low'],

View File

@@ -45,6 +45,10 @@ class DashboardComponentManager:
blocked = decision.get('blocked', False)
manual = decision.get('manual', False)
# FILTER OUT INVALID PRICES - Skip signals with price 0 or None
if price is None or price <= 0:
continue
# Determine signal style
if executed:
badge_class = "bg-success"
@@ -136,7 +140,8 @@ class DashboardComponentManager:
# Create table headers
headers = html.Thead([
html.Tr([
html.Th("Time", className="small"),
html.Th("Entry Time", className="small"),
html.Th("Exit Time", className="small"),
html.Th("Side", className="small"),
html.Th("Size", className="small"),
html.Th("Entry", className="small"),
@@ -154,6 +159,7 @@ class DashboardComponentManager:
if hasattr(trade, 'entry_time'):
# This is a trade object
entry_time = getattr(trade, 'entry_time', 'Unknown')
exit_time = getattr(trade, 'exit_time', 'Unknown')
side = getattr(trade, 'side', 'UNKNOWN')
size = getattr(trade, 'size', 0)
entry_price = getattr(trade, 'entry_price', 0)
@@ -164,6 +170,7 @@ class DashboardComponentManager:
else:
# This is a dictionary format
entry_time = trade.get('entry_time', 'Unknown')
exit_time = trade.get('exit_time', 'Unknown')
side = trade.get('side', 'UNKNOWN')
size = trade.get('quantity', trade.get('size', 0)) # Try 'quantity' first, then 'size'
entry_price = trade.get('entry_price', 0)
@@ -172,24 +179,41 @@ class DashboardComponentManager:
fees = trade.get('fees', 0)
hold_time_seconds = trade.get('hold_time_seconds', 0.0)
# Format time
# Format entry time
if isinstance(entry_time, datetime):
time_str = entry_time.strftime('%H:%M:%S')
entry_time_str = entry_time.strftime('%H:%M:%S')
else:
time_str = str(entry_time)
entry_time_str = str(entry_time)
# Format exit time
if isinstance(exit_time, datetime):
exit_time_str = exit_time.strftime('%H:%M:%S')
else:
exit_time_str = str(exit_time)
# Determine P&L color
pnl_class = "text-success" if pnl >= 0 else "text-danger"
side_class = "text-success" if side == "BUY" else "text-danger"
# Calculate position size in USD
position_size_usd = size * entry_price
# Get leverage from trade or use default
leverage = trade.get('leverage', 1.0) if not hasattr(trade, 'entry_time') else getattr(trade, 'leverage', 1.0)
# Calculate leveraged PnL (already included in pnl value, but ensure it's displayed correctly)
# Ensure fees are subtracted from PnL for accurate profitability
net_pnl = pnl - fees
row = html.Tr([
html.Td(time_str, className="small"),
html.Td(entry_time_str, className="small"),
html.Td(exit_time_str, className="small"),
html.Td(side, className=f"small {side_class}"),
html.Td(f"{size:.3f}", className="small"),
html.Td(f"${position_size_usd:.2f}", className="small"), # Show size in USD
html.Td(f"${entry_price:.2f}", className="small"),
html.Td(f"${exit_price:.2f}", className="small"),
html.Td(f"{hold_time_seconds:.0f}", className="small text-info"),
html.Td(f"${pnl:.2f}", className=f"small {pnl_class}"),
html.Td(f"${net_pnl:.2f}", className=f"small {pnl_class}"), # Show net PnL after fees
html.Td(f"${fees:.3f}", className="small text-muted")
])
rows.append(row)
@@ -272,37 +296,92 @@ class DashboardComponentManager:
logger.error(f"Error formatting system status: {e}")
return [html.P(f"Error: {str(e)}", className="text-danger small")]
<<<<<<< HEAD
def format_cob_data(self, cob_snapshot, symbol, cumulative_imbalance_stats=None, cob_mode="Unknown", imbalance_ma_data=None):
"""Format COB data into a split view with summary, imbalance stats, and a compact ladder."""
=======
def format_cob_data(self, cob_snapshot, symbol, cumulative_imbalance_stats=None, cob_mode="Unknown", update_info: dict = None):
"""Format COB data into a split view with summary, imbalance stats, and a compact ladder.
update_info can include keys: 'update_rate', 'aggregated_1s', 'recent_ticks'.
"""
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
try:
if not cob_snapshot:
return html.Div([
html.H6(f"{symbol} COB", className="mb-2"),
html.P("No COB data available", className="text-muted small"),
html.P(f"Mode: {cob_mode}", className="text-muted small"),
html.P(f"Update: {(update_info or {}).get('update_rate', 'n/a')}", className="text-muted small")
])
# Defensive: If cob_snapshot is a list, log and return error
if isinstance(cob_snapshot, list):
logger.error(f"COB snapshot for {symbol} is a list, expected object. Data: {cob_snapshot}")
return html.Div([
html.H6(f"{symbol} COB", className="mb-2"),
html.P("Invalid COB data format (list)", className="text-danger small"),
html.P(f"Mode: {cob_mode}", className="text-muted small"),
html.P(f"Update: {(update_info or {}).get('update_rate', 'n/a')}", className="text-muted small")
])
# Debug: Log the type and structure of cob_snapshot
logger.debug(f"COB snapshot type for {symbol}: {type(cob_snapshot)}")
# Handle case where cob_snapshot is a list (error case)
if isinstance(cob_snapshot, list):
logger.error(f"COB snapshot is a list for {symbol}, expected object or dict")
return html.Div([
html.H6(f"{symbol} COB", className="mb-2"),
html.P("Invalid COB data format (list)", className="text-danger small"),
html.P(f"Mode: {cob_mode}", className="text-muted small")
])
# Handle both old format (with stats attribute) and new format (direct attributes)
if hasattr(cob_snapshot, 'stats'):
# Normalize snapshot to support dict-based COB (bids/asks arrays) and legacy object formats
stats = {}
bids = []
asks = []
mid_price = 0
spread_bps = 0
imbalance = 0
if isinstance(cob_snapshot, dict):
stats = cob_snapshot.get('stats', {}) if isinstance(cob_snapshot.get('stats', {}), dict) else {}
mid_price = float(stats.get('mid_price', 0) or 0)
spread_bps = float(stats.get('spread_bps', 0) or 0)
imbalance = float(stats.get('imbalance', 0) or 0)
bids = cob_snapshot.get('bids', []) or []
asks = cob_snapshot.get('asks', []) or []
elif hasattr(cob_snapshot, 'stats'):
# Old format with stats attribute
<<<<<<< HEAD
stats = cob_snapshot.stats
mid_price = stats.get('mid_price', 0)
spread_bps = stats.get('spread_bps', 0)
imbalance = stats.get('imbalance', 0)
bids = getattr(cob_snapshot, 'consolidated_bids', [])
asks = getattr(cob_snapshot, 'consolidated_asks', [])
=======
stats = cob_snapshot.stats if isinstance(cob_snapshot.stats, dict) else {}
mid_price = float((stats or {}).get('mid_price', 0) or 0)
spread_bps = float((stats or {}).get('spread_bps', 0) or 0)
imbalance = float((stats or {}).get('imbalance', 0) or 0)
bids = getattr(cob_snapshot, 'consolidated_bids', []) or []
asks = getattr(cob_snapshot, 'consolidated_asks', []) or []
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
else:
# New COBSnapshot format with direct attributes
mid_price = getattr(cob_snapshot, 'volume_weighted_mid', 0)
spread_bps = getattr(cob_snapshot, 'spread_bps', 0)
imbalance = getattr(cob_snapshot, 'liquidity_imbalance', 0)
bids = getattr(cob_snapshot, 'consolidated_bids', [])
asks = getattr(cob_snapshot, 'consolidated_asks', [])
# New object-like snapshot with direct attributes
mid_price = float(getattr(cob_snapshot, 'volume_weighted_mid', 0) or 0)
spread_bps = float(getattr(cob_snapshot, 'spread_bps', 0) or 0)
imbalance = float(getattr(cob_snapshot, 'liquidity_imbalance', 0) or 0)
bids = getattr(cob_snapshot, 'consolidated_bids', []) or []
asks = getattr(cob_snapshot, 'consolidated_asks', []) or []
if mid_price == 0 or not bids or not asks:
return html.Div([
html.H6(f"{symbol} COB", className="mb-2"),
html.P("Awaiting valid order book data...", className="text-muted small")
html.P("Awaiting valid order book data...", className="text-muted small"),
html.P(f"Mode: {cob_mode}", className="text-muted small"),
html.P(f"Update: {(update_info or {}).get('update_rate', 'n/a')}", className="text-muted small")
])
# Create stats dict for compatibility with existing code
@@ -315,17 +394,83 @@ class DashboardComponentManager:
'bid_levels': len(bids),
'ask_levels': len(asks)
}
# Show staleness if provided via provider (age_ms)
try:
age_ms = None
if hasattr(cob_snapshot, 'stats') and isinstance(cob_snapshot.stats, dict):
age_ms = cob_snapshot.stats.get('age_ms')
if age_ms is not None:
stats['age_ms'] = age_ms
except Exception:
pass
# --- Left Panel: Overview and Stats ---
<<<<<<< HEAD
overview_panel = self._create_cob_overview_panel(symbol, stats, cumulative_imbalance_stats, cob_mode, imbalance_ma_data)
=======
# Prepend update info to overview
overview_panel = self._create_cob_overview_panel(symbol, stats, cumulative_imbalance_stats, cob_mode)
if update_info and update_info.get('update_rate'):
# Wrap with a small header line for update rate
overview_panel = html.Div([
html.Div(html.Small(f"Update: {update_info['update_rate']}", className="text-muted"), className="mb-1"),
overview_panel
])
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
# --- Right Panel: Compact Ladder ---
# --- Right Panel: Compact Ladder with optional exchange stats ---
exchange_stats = (update_info or {}).get('exchanges') if isinstance(update_info, dict) else None
ladder_panel = self._create_cob_ladder_panel(bids, asks, mid_price, symbol)
if exchange_stats:
# Render a tiny exchange contribution summary above ladder
try:
rows = []
for ex, stats_ex in exchange_stats.items():
rows.append(html.Small(f"{ex}: {stats_ex.get('bids',0)}/{stats_ex.get('asks',0)}", className="text-muted me-2"))
ladder_panel = html.Div([
html.Div(rows, className="mb-1"),
ladder_panel
])
except Exception:
pass
# Append small extras line from aggregated_1s and recent_ticks (robust to numeric fields)
extras = []
if update_info:
agg = (update_info.get('aggregated_1s') or [])
if agg and isinstance(agg[-1], dict):
last = agg[-1]
spread_field = last.get('spread', {})
if isinstance(spread_field, dict):
avg_spread = spread_field.get('average_bps', 0)
elif isinstance(spread_field, (int, float)):
avg_spread = spread_field
else:
avg_spread = 0
imb_field = last.get('imbalance', {})
if isinstance(imb_field, dict):
avg_imb = imb_field.get('average', 0)
elif isinstance(imb_field, (int, float)):
avg_imb = imb_field
else:
avg_imb = 0
tick_count = last.get('tick_count', 0)
extras.append(html.Small(f"1s agg: {tick_count} ticks, spread {avg_spread:.1f} bps, imb {avg_imb:.2f}", className="text-muted"))
recent = (update_info.get('recent_ticks') or [])
if recent:
extras.append(html.Small(f"Recent ticks: {len(recent)}", className="text-muted ms-2"))
extras_div = html.Div(extras, className="mb-1") if extras else None
return dbc.Row([
dbc.Col(overview_panel, width=5, className="pe-1"),
dbc.Col(ladder_panel, width=7, className="ps-1")
], className="g-0") # g-0 removes gutters
# Heatmap is rendered in dedicated tiles (avoid duplicate component IDs)
heatmap_graph = None
children = [dbc.Col(overview_panel, width=5, className="pe-1")]
right_children = []
# Do not append inline heatmap here to prevent duplicate IDs
right_children.append(ladder_panel)
if extras_div:
right_children.insert(0, extras_div)
children.append(dbc.Col(html.Div(right_children), width=7, className="ps-1"))
return dbc.Row(children, className="g-0") # g-0 removes gutters
except Exception as e:
logger.error(f"Error formatting split COB data: {e}")
@@ -347,12 +492,16 @@ class DashboardComponentManager:
mode_color = "text-success" if cob_mode == "WS" else "text-warning" if cob_mode == "REST" else "text-muted"
mode_icon = "fas fa-wifi" if cob_mode == "WS" else "fas fa-globe" if cob_mode == "REST" else "fas fa-question"
imbalance_stats_display = []
if cumulative_imbalance_stats:
imbalance_stats_display.append(html.H6("Cumulative Imbalance", className="mt-3 mb-2 small text-muted text-uppercase"))
for period, value in cumulative_imbalance_stats.items():
imbalance_stats_display.append(self._create_imbalance_stat_row(period, value))
def _safe_imb(stats_val, key, fallback):
try:
if isinstance(stats_val, dict):
return float(stats_val.get(key, fallback))
if isinstance(stats_val, (int, float)):
return float(stats_val)
except Exception:
pass
return float(fallback)
return html.Div([
html.H6(f"{symbol} - COB Overview", className="mb-2"),
html.Div([
@@ -371,7 +520,17 @@ class DashboardComponentManager:
html.Span(imbalance_text, className=f"fw-bold small {imbalance_color}")
]),
html.Div(imbalance_stats_display),
# Multi-timeframe imbalance metrics (single display, not duplicate)
html.Div([
html.Strong("Timeframe Imbalances:", className="small d-block mt-2 mb-1")
]),
html.Div([
self._create_timeframe_imbalance("1s", _safe_imb(cumulative_imbalance_stats, '1s', imbalance)),
self._create_timeframe_imbalance("5s", _safe_imb(cumulative_imbalance_stats, '5s', imbalance)),
self._create_timeframe_imbalance("15s", _safe_imb(cumulative_imbalance_stats, '15s', imbalance)),
self._create_timeframe_imbalance("60s", _safe_imb(cumulative_imbalance_stats, '60s', imbalance)),
], className="d-flex justify-content-between mb-2"),
# COB Imbalance Moving Averages
html.Div([
@@ -415,6 +574,22 @@ class DashboardComponentManager:
html.Div(title, className="small text-muted"),
html.Div(value, className="fw-bold")
], className="text-center")
def _create_timeframe_imbalance(self, timeframe, value):
"""Helper for creating timeframe imbalance indicators."""
color = "text-success" if value > 0 else "text-danger" if value < 0 else "text-muted"
icon = "fas fa-chevron-up" if value > 0 else "fas fa-chevron-down" if value < 0 else "fas fa-minus"
# Format the value with sign and 2 decimal places
formatted_value = f"{value:+.2f}"
return html.Div([
html.Div(timeframe, className="small text-muted"),
html.Div([
html.I(className=f"{icon} me-1"),
html.Span(formatted_value, className="small")
], className=color)
], className="text-center")
def _create_cob_ladder_panel(self, bids, asks, mid_price, symbol=""):
"""Creates Bookmap-style COB display with horizontal bars extending from center price."""
@@ -425,6 +600,7 @@ class DashboardComponentManager:
def aggregate_buckets(orders):
buckets = {}
for order in orders:
<<<<<<< HEAD
# Handle both dictionary format and ConsolidatedOrderBookLevel objects
if hasattr(order, 'price'):
price = order.price
@@ -434,6 +610,30 @@ class DashboardComponentManager:
price = order.get('price', 0)
size = order.get('total_size', order.get('size', 0))
volume_usd = order.get('total_volume_usd', size * price)
=======
# Handle multiple formats: object, dict, or [price, size]
price = 0.0
size = 0.0
volume_usd = 0.0
try:
if hasattr(order, 'price'):
# ConsolidatedOrderBookLevel object
price = float(getattr(order, 'price', 0) or 0)
size = float(getattr(order, 'total_size', getattr(order, 'size', 0)) or 0)
volume_usd = float(getattr(order, 'total_volume_usd', price * size) or (price * size))
elif isinstance(order, dict):
price = float(order.get('price', 0) or 0)
size = float(order.get('total_size', order.get('size', 0)) or 0)
volume_usd = float(order.get('total_volume_usd', price * size) or (price * size))
elif isinstance(order, (list, tuple)) and len(order) >= 2:
price = float(order[0] or 0)
size = float(order[1] or 0)
volume_usd = price * size
else:
continue
except Exception:
continue
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
if price > 0:
bucket_key = round(price / bucket_size) * bucket_size
@@ -773,7 +973,15 @@ class DashboardComponentManager:
def format_training_metrics(self, metrics_data):
"""Format training metrics for display - Enhanced with loaded models"""
try:
# DEBUG: Log what we're receiving
logger.info(f"format_training_metrics received: {type(metrics_data)}")
if metrics_data:
logger.info(f"Metrics keys: {list(metrics_data.keys()) if isinstance(metrics_data, dict) else 'Not a dict'}")
if isinstance(metrics_data, dict) and 'loaded_models' in metrics_data:
logger.info(f"Loaded models: {list(metrics_data['loaded_models'].keys())}")
if not metrics_data or 'error' in metrics_data:
logger.warning(f"No training data or error in metrics_data: {metrics_data}")
return [html.P("No training data", className="text-muted small")]
content = []
@@ -824,6 +1032,7 @@ class DashboardComponentManager:
checkpoint_status = "LOADED" if model_info.get('checkpoint_loaded', False) else "FRESH"
# Model card
logger.info(f"Creating model card for {model_name} with toggles: inference={model_info.get('inference_enabled', True)}, training={model_info.get('training_enabled', True)}")
model_card = html.Div([
# Header with model name and toggle
html.Div([
@@ -835,16 +1044,29 @@ class DashboardComponentManager:
html.Span(f" [{checkpoint_status}]", className=f"small {'text-success' if checkpoint_status == 'LOADED' else 'text-warning'} ms-1")
], style={"flex": "1"}),
# Activation toggle (if easy to implement)
# Inference and Training toggles
html.Div([
dcc.Checklist(
id=f"toggle-{model_name}",
options=[{"label": "", "value": "active"}],
value=["active"] if is_active else [],
className="form-check-input",
style={"transform": "scale(0.8)"}
)
], className="form-check form-switch")
html.Div([
html.Label("Inf", className="text-muted small me-1", style={"font-size": "10px"}),
dcc.Checklist(
id=f"{model_name}-inference-toggle",
options=[{"label": "", "value": True}],
value=[True] if model_info.get('inference_enabled', True) else [],
className="form-check-input me-2",
style={"transform": "scale(0.7)"}
)
], className="d-flex align-items-center me-2"),
html.Div([
html.Label("Trn", className="text-muted small me-1", style={"font-size": "10px"}),
dcc.Checklist(
id=f"{model_name}-training-toggle",
options=[{"label": "", "value": True}],
value=[True] if model_info.get('training_enabled', True) else [],
className="form-check-input",
style={"transform": "scale(0.7)"}
)
], className="d-flex align-items-center")
], className="d-flex")
], className="d-flex align-items-center mb-1"),
# Model metrics
@@ -883,7 +1105,11 @@ class DashboardComponentManager:
html.Br(),
html.Span(f"Rate: {model_info.get('timing', {}).get('inferences_per_second', '0.00')}/s", className="text-success small"),
html.Span(" | ", className="text-muted small"),
html.Span(f"24h: {model_info.get('timing', {}).get('predictions_24h', 0)}", className="text-primary small")
html.Span(f"24h: {model_info.get('timing', {}).get('predictions_24h', 0)}", className="text-primary small"),
html.Br(),
html.Span(f"Avg Inf: {model_info.get('timing', {}).get('average_inference_time_ms', 'N/A')}ms", className="text-info small"),
html.Span(" | ", className="text-muted small"),
html.Span(f"Avg Train: {model_info.get('timing', {}).get('average_training_time_ms', 'N/A')}ms", className="text-warning small")
], className="mb-1"),
# Loss metrics with improvement tracking
@@ -1078,10 +1304,15 @@ class DashboardComponentManager:
html.Span(f"{enhanced_stats['recent_validation_score']:.3f}", className="text-primary small fw-bold")
], className="mb-1"))
logger.info(f"format_training_metrics returning {len(content)} components")
for i, component in enumerate(content[:3]): # Log first 3 components
logger.info(f" Component {i}: {type(component)}")
return content
except Exception as e:
logger.error(f"Error formatting training metrics: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
return [html.P(f"Error: {str(e)}", className="text-danger small")]
def _format_cnn_pivot_prediction(self, model_info):

View File

@@ -10,11 +10,13 @@ from datetime import datetime
class DashboardLayoutManager:
"""Manages dashboard layout and structure"""
def __init__(self, starting_balance: float = 100.0, trading_executor=None):
def __init__(self, starting_balance: float = 100.0, trading_executor=None, dashboard=None):
self.starting_balance = starting_balance
self.trading_executor = trading_executor
self.dashboard = dashboard
def create_main_layout(self):
<<<<<<< HEAD
"""Create the main dashboard layout with dark theme"""
return html.Div([
self._create_header(),
@@ -27,6 +29,35 @@ class DashboardLayoutManager:
"minHeight": "100vh",
"color": "#f8f9fa"
})
=======
"""Create the main dashboard layout"""
try:
print("Creating main layout...")
header = self._create_header()
print("Header created")
interval_component = self._create_interval_component()
print("Interval component created")
main_content = self._create_main_content()
print("Main content created")
layout = html.Div([
header,
interval_component,
main_content
], className="container-fluid")
print("Main layout created successfully")
return layout
except Exception as e:
print(f"Error creating main layout: {e}")
import traceback
traceback.print_exc()
# Return a simple error layout
return html.Div([
html.H1("Dashboard Error", className="text-danger"),
html.P(f"Error creating layout: {str(e)}", className="text-danger")
])
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
def _create_prediction_tracking_section(self):
"""Create prediction tracking and model performance section"""
@@ -248,7 +279,7 @@ class DashboardLayoutManager:
"Clean Trading Dashboard"
], className="text-light mb-0"),
html.P(
f"Ultra-Fast Updates • Portfolio: ${self.starting_balance:,.0f}{trading_mode}",
f"Ultra-Fast Updates • Live Account Balance Sync{trading_mode}",
className="text-light mb-0 opacity-75 small"
)
], className="bg-dark p-2 mb-2")
@@ -261,6 +292,7 @@ class DashboardLayoutManager:
], className="bg-dark p-2 mb-2")
def _create_interval_component(self):
<<<<<<< HEAD
"""Create the auto-refresh interval component"""
return html.Div([
dcc.Interval(
@@ -273,6 +305,32 @@ class DashboardLayoutManager:
interval=60000, # Update every 60 seconds for chained inference
n_intervals=0
)
=======
"""Create the auto-refresh interval components with different frequencies"""
return html.Div([
# Fast interval for critical updates (2 seconds - reduced from 1s)
dcc.Interval(
id='interval-component',
interval=2000, # Update every 2000 ms (0.5 Hz) - OPTIMIZED
n_intervals=0
),
# Slow interval for non-critical updates (10 seconds - increased from 5s)
dcc.Interval(
id='slow-interval-component',
interval=10000, # Update every 10 seconds (0.1 Hz) - OPTIMIZED
n_intervals=0,
disabled=False
),
# Fast interval for testing (5 seconds)
dcc.Interval(
id='fast-interval-component',
interval=5000, # Update every 5 seconds for testing
n_intervals=0,
disabled=False
),
# WebSocket-based updates for high-frequency data (no interval needed)
html.Div(id='websocket-updates-container', style={'display': 'none'})
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
])
def _create_main_content(self):
@@ -297,6 +355,25 @@ class DashboardLayoutManager:
def _create_metrics_grid(self):
"""Create the metrics grid with compact cards"""
# Get exchange name dynamically
exchange_name = "Exchange"
if self.trading_executor:
if hasattr(self.trading_executor, 'primary_name'):
exchange_name = self.trading_executor.primary_name.upper()
elif hasattr(self.trading_executor, 'exchange') and self.trading_executor.exchange:
# Try to get exchange name from exchange interface
exchange_class_name = self.trading_executor.exchange.__class__.__name__
if 'Bybit' in exchange_class_name:
exchange_name = "BYBIT"
elif 'Mexc' in exchange_class_name or 'MEXC' in exchange_class_name:
exchange_name = "MEXC"
elif 'Binance' in exchange_class_name:
exchange_name = "BINANCE"
elif 'Deribit' in exchange_class_name:
exchange_name = "DERIBIT"
else:
exchange_name = "EXCHANGE"
metrics_cards = [
("current-price", "Live Price", "text-success"),
("session-pnl", "Session P&L", ""),
@@ -304,7 +381,9 @@ class DashboardLayoutManager:
# ("leverage-info", "Leverage", "text-primary"),
("trade-count", "Trades", "text-warning"),
("portfolio-value", "Portfolio", "text-secondary"),
("mexc-status", "MEXC API", "text-info")
("profitability-multiplier", "Profit Boost", "text-primary"),
("cob-websocket-status", "COB WebSocket", "text-warning"),
("mexc-status", f"{exchange_name} API", "text-info")
]
cards = []
@@ -356,7 +435,64 @@ class DashboardLayoutManager:
"Session Controls"
], className="card-title mb-2"),
# Trading Agent Mode Toggle
html.Div([
html.Label([
html.I(className="fas fa-robot me-1"),
"Trading Agent: ",
html.Span(
id="trading-mode-display",
children="LIVE" if getattr(self.dashboard, 'trading_mode_live', False) else "SIM",
className="fw-bold text-danger" if getattr(self.dashboard, 'trading_mode_live', False) else "fw-bold text-warning"
)
], className="form-label small mb-1"),
dcc.Checklist(
id='trading-mode-switch',
options=[{'label': '', 'value': 'live'}],
value=['live'] if getattr(self.dashboard, 'trading_mode_live', False) else [],
className="form-check-input"
),
html.Small("SIM = Simulation Mode, LIVE = Real Trading", className="text-muted d-block")
], className="mb-2"),
# Cold Start Training Toggle
html.Div([
html.Label([
html.I(className="fas fa-fire me-1"),
"Cold Start Training: ",
html.Span(
id="cold-start-display",
children="ON" if getattr(self.dashboard, 'cold_start_enabled', True) else "OFF",
className="fw-bold text-success" if getattr(self.dashboard, 'cold_start_enabled', True) else "fw-bold text-secondary"
)
], className="form-label small mb-1"),
dcc.Checklist(
id='cold-start-switch',
options=[{'label': '', 'value': 'enabled'}],
value=['enabled'] if getattr(self.dashboard, 'cold_start_enabled', True) else [],
className="form-check-input"
),
html.Small("Excessive training during cold start", className="text-muted d-block")
], className="mb-2"),
html.Hr(className="my-2"),
# Leverage Control
html.Div([
html.Label([
html.I(className="fas fa-compass me-1"),
"Show Pivot Points: ",
html.Span(id="pivots-display", children="ON", className="fw-bold text-success")
], className="form-label small mb-1"),
dcc.Checklist(
id='show-pivots-switch',
options=[{'label': '', 'value': 'enabled'}],
value=['enabled'],
className="form-check-input"
),
html.Small("Toggle pivot overlays on the chart", className="text-muted d-block")
], className="mb-2"),
html.Div([
html.Label([
html.I(className="fas fa-sliders-h me-1"),
@@ -455,6 +591,37 @@ class DashboardLayoutManager:
html.I(className="fas fa-save me-1"),
"Store All Models"
], id="store-models-btn", className="btn btn-info btn-sm w-100 mt-2"),
html.Button([
html.I(className="fas fa-arrows-rotate me-1"),
"Sync Positions/Orders"
], id="manual-sync-btn", className="btn btn-primary btn-sm w-100 mt-2"),
# Text Export Controls
html.Hr(className="my-2"),
html.Small("Text Export & LLM", className="text-muted d-block mb-1"),
html.Div([
html.Button([
html.I(className="fas fa-file-export me-1"),
"Start Text Export"
], id="start-text-export-btn", className="btn btn-success btn-sm me-1", style={"fontSize": "10px"}),
html.Button([
html.I(className="fas fa-stop me-1"),
"Stop"
], id="stop-text-export-btn", className="btn btn-danger btn-sm", style={"fontSize": "10px"})
], className="d-flex mb-2"),
html.Div([
html.Button([
html.I(className="fas fa-robot me-1"),
"Start LLM"
], id="start-llm-btn", className="btn btn-info btn-sm me-1", style={"fontSize": "10px"}),
html.Button([
html.I(className="fas fa-stop me-1"),
"Stop"
], id="stop-llm-btn", className="btn btn-warning btn-sm", style={"fontSize": "10px"})
], className="d-flex mb-2"),
html.Small(id="text-export-status", children="Export: Stopped", className="text-muted d-block"),
html.Small(id="llm-status", children="LLM: Stopped", className="text-muted d-block"),
html.Hr(className="my-2"),
html.Small("System Status", className="text-muted d-block mb-1"),
html.Div([
@@ -478,11 +645,12 @@ class DashboardLayoutManager:
return html.Div([
html.Div([
html.Div([
# Chart header with manual trading buttons
# Chart header with manual trading buttons and live price
html.Div([
html.H6([
html.I(className="fas fa-chart-candlestick me-2"),
"Live 1m Price Chart (3h) + 1s Mini Chart (5min) - Updated Every Second"
"Live Price (WebSocket): ",
html.Span(id="chart-current-price", className="ms-2 text-primary", style={"fontWeight": "bold", "fontSize": "1.2em"})
], className="card-title mb-0"),
html.Div([
html.Button([
@@ -493,20 +661,33 @@ class DashboardLayoutManager:
html.Button([
html.I(className="fas fa-arrow-down me-1"),
"SELL"
], id="manual-sell-btn", className="btn btn-danger btn-sm",
], id="manual-sell-btn", className="btn btn-danger btn-sm me-2",
style={"fontSize": "10px", "padding": "2px 8px"}),
html.Button([
html.I(className="fas fa-times me-1"),
"CLOSE"
], id="manual-close-btn", className="btn btn-secondary btn-sm",
style={"fontSize": "10px", "padding": "2px 8px"})
], className="d-flex")
], className="d-flex justify-content-between align-items-center mb-2"),
html.Div([
dcc.Graph(id="price-chart", style={"height": "500px"})
]),
html.Hr(className="my-2"),
html.Div([
html.H6([
html.I(className="fas fa-chart-line me-2"),
"Polymarket vs CoinGecko (BTC/ETH, next 5 days)"
], className="card-title mb-2"),
dcc.Graph(id="polymarket-eth-btc-chart", style={"height": "350px"})
])
], className="card-body p-2")
], className="card")
])
def _create_cob_and_trades_row(self):
"""Creates the row for COB ladders, closed trades, and model status - REORGANIZED LAYOUT"""
"""Creates the row for COB ladders, closed trades, pending orders, and model status"""
return html.Div([
# Top row: COB Ladders (left) and Models/Training (right)
html.Div([
@@ -531,38 +712,77 @@ class DashboardLayoutManager:
], className="d-flex")
], style={"width": "60%"}),
# Right side: Models & Training Progress (40% width) - MOVED UP
# Right side: Models & Training Progress (40% width)
html.Div([
html.Div([
html.Div([
html.H6([
html.I(className="fas fa-brain me-2"),
"Models & Training Progress",
], className="card-title mb-2"),
html.Div([
html.H6([
html.I(className="fas fa-brain me-2"),
"Models & Training Progress",
], className="card-title mb-2"),
html.Button([
html.I(className="fas fa-sync-alt me-1"),
"Refresh"
], id="refresh-training-metrics-btn", className="btn btn-sm btn-outline-primary")
], className="d-flex justify-content-between align-items-center mb-2"),
html.Div(
id="training-metrics",
style={"height": "300px", "overflowY": "auto"}, # Increased height
style={"height": "300px", "overflowY": "auto"},
),
], className="card-body p-2")
], className="card")
], style={"width": "38%", "marginLeft": "2%"}),
], className="d-flex mb-3"),
# Bottom row: Closed Trades (full width) - MOVED BELOW COB
# Mini COB Heatmaps (ETH and BTC)
html.Div([
html.Div([
html.Div([
html.H6([
html.I(className="fas fa-history me-2"),
"Recent Closed Trades",
], className="card-title mb-2"),
html.Div(
id="closed-trades-table",
style={"height": "200px", "overflowY": "auto"}, # Reduced height
),
], className="card-body p-2")
], className="card")
])
html.H6([html.I(className="fas fa-fire me-2"), "ETH Heatmap"], className="card-title mb-2"),
dcc.Graph(id='cob-heatmap-eth', config={'displayModeBar': False}, style={"height": "220px"})
], className="card-body p-2", style={"flex": "1"}),
html.Div([
html.H6([html.I(className="fas fa-fire me-2"), "BTC Heatmap"], className="card-title mb-2"),
dcc.Graph(id='cob-heatmap-btc', config={'displayModeBar': False}, style={"height": "220px"})
], className="card-body p-2", style={"flex": "1", "marginLeft": "1rem"})
], className="d-flex")
], className="card"),
# Second row: Pending Orders (left) and Closed Trades (right)
html.Div([
# Left side: Pending Orders (40% width)
html.Div([
html.Div([
html.Div([
html.H6([
html.I(className="fas fa-clock me-2"),
"Pending Orders & Position Sync",
], className="card-title mb-2"),
html.Div(
id="pending-orders-content",
style={"height": "200px", "overflowY": "auto"},
),
], className="card-body p-2")
], className="card")
], style={"width": "40%"}),
# Right side: Closed Trades (58% width)
html.Div([
html.Div([
html.Div([
html.H6([
html.I(className="fas fa-history me-2"),
"Recent Closed Trades",
], className="card-title mb-2"),
html.Div(
id="closed-trades-table",
style={"height": "200px", "overflowY": "auto"},
),
], className="card-body p-2")
], className="card")
], style={"width": "58%", "marginLeft": "2%"}),
], className="d-flex")
])
def _create_analytics_and_performance_row(self):
@@ -618,6 +838,9 @@ class DashboardLayoutManager:
], className="card-body p-2")
], className="card", style={"width": "30%", "marginLeft": "2%"})
], className="d-flex")
<<<<<<< HEAD
=======
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b

View File

@@ -0,0 +1,290 @@
"""
Models & Training Progress Panel - Clean Implementation
Displays real-time model status, training metrics, and performance data
"""
import logging
from typing import Dict, Any, List, Optional
from datetime import datetime
from dash import html, dcc
logger = logging.getLogger(__name__)
class ModelsTrainingPanel:
"""Clean implementation of the Models & Training Progress panel.
- Uses orchestrator.get_model_statistics_summary() and model_states
- No synthetic or placeholder data; shows empty when unavailable
- Avoids duplicate class definitions and invalid imports
"""
def __init__(self, orchestrator=None):
self.orchestrator = orchestrator
def create_panel(self) -> Any:
try:
data = self._gather_data()
content: List[html.Div] = []
content.append(self._create_models_section(data.get("models", {})))
if data.get("training_status"):
content.append(self._create_training_status_section(data["training_status"]))
if data.get("system_metrics"):
content.append(self._create_system_metrics_section(data["system_metrics"]))
# Return children (to be assigned to 'training-metrics' container)
return content
except Exception as e:
logger.error(f"Error creating models training panel: {e}")
return [
html.P(f"Error loading training panel: {str(e)}", className="text-danger small")
]
def _gather_data(self) -> Dict[str, Any]:
result: Dict[str, Any] = {"models": {}, "training_status": {}, "system_metrics": {}}
if not self.orchestrator:
return result
try:
# Model statistics summary (serializable)
stats_summary: Dict[str, Dict[str, Any]] = {}
if hasattr(self.orchestrator, "get_model_statistics_summary"):
stats = self.orchestrator.get_model_statistics_summary()
if isinstance(stats, dict):
stats_summary = stats
# Model states (for best_loss and checkpoint flags) - use get_model_states() for updated checkpoint info
model_states: Dict[str, Dict[str, Any]] = {}
if hasattr(self.orchestrator, "get_model_states"):
try:
model_states = self.orchestrator.get_model_states() or {}
except Exception as e:
logger.debug(f"Error getting model states: {e}")
model_states = getattr(self.orchestrator, "model_states", {}) or {}
# Build models block from stats_summary
for model_name, s in stats_summary.items():
model_info: Dict[str, Any] = {}
try:
# Use actual model name - no mapping needed
display_name = model_name.upper()
# Status: active if we have any inference info
total_inf = int(s.get("total_inferences", 0) or 0)
status = "active" if (total_inf > 0 or s.get("last_inference_time")) else "registered"
# Last prediction
last_pred_action = s.get("last_prediction")
last_confidence = s.get("last_confidence")
last_inference_time = s.get("last_inference_time")
# Loss metrics
loss_metrics = {
"current_loss": s.get("current_loss"),
"best_loss": s.get("best_loss"),
}
# Timing metrics
timing_metrics = {
"inferences_per_second": s.get("inference_rate_per_second"),
"last_inference": last_inference_time,
"last_training": s.get("last_training_time"),
}
# Checkpoint flags from orchestrator.model_states - map external names to internal keys
ckpt_loaded = False
ckpt_failed = False
checkpoint_filename = None
# Map external model names to internal model state keys
state_key_map = {
"dqn_agent": "dqn",
"enhanced_cnn": "cnn",
"extrema_trainer": "extrema_trainer",
"decision": "decision",
"cob_rl_model": "cob_rl"
}
state_key = state_key_map.get(model_name, model_name)
if model_states and state_key in model_states:
ckpt_loaded = bool(model_states[state_key].get("checkpoint_loaded", False))
ckpt_failed = bool(model_states[state_key].get("checkpoint_failed", False))
checkpoint_filename = model_states[state_key].get("checkpoint_filename")
# If model started fresh, mark as stored after first successful autosave/best_loss update
if not ckpt_loaded:
cur = model_states[state_key].get("current_loss")
best = model_states[state_key].get("best_loss")
if isinstance(cur, (int, float)) and isinstance(best, (int, float)) and best is not None and cur is not None and cur <= best:
ckpt_loaded = True
model_info = {
"name": display_name,
"status": status,
"parameters": 0, # unknown; do not synthesize
"last_prediction": {
"action": last_pred_action if last_pred_action is not None else "",
"confidence": last_confidence if last_confidence is not None else None,
"timestamp": last_inference_time if last_inference_time else "",
},
"training_enabled": True,
"inference_enabled": True,
"routing_enabled": True,
"checkpoint_loaded": ckpt_loaded,
"checkpoint_failed": ckpt_failed,
"checkpoint_filename": checkpoint_filename,
"loss_metrics": loss_metrics,
"timing_metrics": timing_metrics,
"signal_stats": {},
}
except Exception as e:
logger.debug(f"Error building model info for {model_name}: {e}")
model_info = {"name": model_name, "status": "error", "error": str(e)}
result["models"][model_name] = model_info
# Enhanced training system status
training_status: Dict[str, Any] = {}
try:
if hasattr(self.orchestrator, "get_enhanced_training_stats"):
training_status = self.orchestrator.get_enhanced_training_stats() or {}
except Exception as e:
logger.debug(f"Enhanced training stats unavailable: {e}")
result["training_status"] = training_status
# System metrics (decision fusion, cob integration)
system_metrics = {
"decision_fusion_active": bool(getattr(self.orchestrator, "decision_fusion_enabled", False)),
"cob_integration_active": bool(getattr(self.orchestrator, "cob_integration", None) is not None),
"symbols_tracking": len(getattr(getattr(self.orchestrator, "cob_integration", None), "symbols", []) or []),
}
result["system_metrics"] = system_metrics
return result
except Exception as e:
logger.error(f"Error gathering panel data: {e}")
return result
def _create_models_section(self, models: Dict[str, Any]) -> html.Div:
cards = [self._create_model_card(name, info) for name, info in models.items()]
return html.Div([
html.H6([html.I(className="fas fa-microchip me-2 text-success"), f"Loaded Models ({len(models)})"], className="mb-2"),
html.Div(cards)
])
def _create_model_card(self, model_name: str, model_data: Dict[str, Any]) -> html.Div:
status = model_data.get("status", "unknown")
if status == "active":
status_class = "text-success"; status_icon = "fas fa-check-circle"; status_text = "ACTIVE"
elif status == "registered":
status_class = "text-warning"; status_icon = "fas fa-circle"; status_text = "REGISTERED"
elif status == "inactive":
status_class = "text-muted"; status_icon = "fas fa-pause-circle"; status_text = "INACTIVE"
else:
status_class = "text-danger"; status_icon = "fas fa-exclamation-circle"; status_text = "UNKNOWN"
last_pred = model_data.get("last_prediction", {})
pred_action = last_pred.get("action") or "NONE"
pred_confidence = last_pred.get("confidence")
pred_time = last_pred.get("timestamp") or "N/A"
loss_metrics = model_data.get("loss_metrics", {})
current_loss = loss_metrics.get("current_loss")
best_loss = loss_metrics.get("best_loss")
loss_class = (
"text-success" if (isinstance(current_loss, (int, float)) and current_loss < 0.1)
else "text-warning" if (isinstance(current_loss, (int, float)) and current_loss < 0.5)
else "text-danger"
) if current_loss is not None else "text-muted"
timing = model_data.get("timing_metrics", {})
rate = timing.get("inferences_per_second")
# Tooltip title showing checkpoint info (if any)
title_parts: List[str] = [f"{status_text}"]
ckpt_name = model_data.get('checkpoint_filename')
if ckpt_name:
title_parts.append(f"CKPT: {ckpt_name}")
header_title = " | ".join(title_parts)
return html.Div([
html.Div([
html.Div([
html.I(className=f"{status_icon} me-2 {status_class}"),
html.Strong(f"{model_name.upper()}", className=status_class, title=header_title),
html.Span(f" - {status_text}", className=f"{status_class} small ms-1"),
html.Span(" [CKPT]" if model_data.get("checkpoint_loaded") else (" [FAILED]" if model_data.get("checkpoint_failed") else " [FRESH]"), className=f"small {'text-success' if model_data.get('checkpoint_loaded') else 'text-danger' if model_data.get('checkpoint_failed') else 'text-warning'} ms-1")
], style={"flex": "1"}),
html.Div([
html.Div([
html.Label("Inf", className="text-muted small me-1", style={"fontSize": "10px"}),
dcc.Checklist(id={'type': 'model-toggle', 'model': model_name, 'toggle_type': 'inference'}, options=[{"label": "", "value": True}], value=[True] if model_data.get('inference_enabled', True) else [], className="form-check-input me-2", style={"transform": "scale(0.7)"})
], className="d-flex align-items-center me-2"),
html.Div([
html.Label("Trn", className="text-muted small me-1", style={"fontSize": "10px"}),
dcc.Checklist(id={'type': 'model-toggle', 'model': model_name, 'toggle_type': 'training'}, options=[{"label": "", "value": True}], value=[True] if model_data.get('training_enabled', True) else [], className="form-check-input", style={"transform": "scale(0.7)"})
], className="d-flex align-items-center me-2"),
html.Div([
html.Label("Route", className="text-muted small me-1", style={"fontSize": "10px"}),
dcc.Checklist(id={'type': 'model-toggle', 'model': model_name, 'toggle_type': 'routing'}, options=[{"label": "", "value": True}], value=[True] if model_data.get('routing_enabled', True) else [], className="form-check-input", style={"transform": "scale(0.7)"})
], className="d-flex align-items-center"),
], className="d-flex")
], className="d-flex align-items-center mb-2"),
html.Div([
html.Div([
html.Span("Last: ", className="text-muted small"),
html.Span(f"{pred_action}", className=f"small fw-bold {'text-success' if pred_action == 'BUY' else 'text-danger' if pred_action == 'SELL' else 'text-warning'}"),
html.Span(f" ({pred_confidence:.1f}%)" if isinstance(pred_confidence, (int, float)) else "", className="text-muted small"),
html.Span(f" @ {pred_time}", className="text-muted small")
], className="mb-1"),
html.Div([
html.Span("Loss: ", className="text-muted small"),
html.Span(f"{current_loss:.4f}" if isinstance(current_loss, (int, float)) else "", className=f"small fw-bold {loss_class}"),
*([html.Span(" | Best: ", className="text-muted small"), html.Span(f"{best_loss:.4f}", className="text-success small")] if isinstance(best_loss, (int, float)) else [])
], className="mb-1"),
html.Div([
html.Span("Rate: ", className="text-muted small"),
html.Span(f"{rate:.2f}/s" if isinstance(rate, (int, float)) else "", className="text-info small")
], className="mb-1"),
html.Div([
html.Span("Last Inf: ", className="text-muted small"),
html.Span(f"{timing.get('last_inference', 'N/A')}", className="text-info small"),
html.Span(" | Train: ", className="text-muted small"),
html.Span(f"{timing.get('last_training', 'N/A')}", className="text-warning small")
], className="mb-1"),
])
], className="border rounded p-2 mb-2")
def _create_training_status_section(self, status: Dict[str, Any]) -> html.Div:
if not status or status.get("status") == "error":
return html.Div([
html.Hr(),
html.H6([html.I(className="fas fa-exclamation-triangle me-2 text-danger"), "Training Status Error"], className="mb-2"),
html.P(f"Error: {status.get('error', 'Unknown')}", className="text-danger small")
])
is_enabled = status.get("training_enabled", False)
return html.Div([
html.Hr(),
html.H6([html.I(className="fas fa-brain me-2 text-secondary"), "Training Status"], className="mb-2"),
html.Div([
html.Span("Enabled: ", className="text-muted small"),
html.Span("ON" if is_enabled else "OFF", className=f"small fw-bold {'text-success' if is_enabled else 'text-warning'}"),
], className="mb-1"),
])
def _create_system_metrics_section(self, metrics: Dict[str, Any]) -> html.Div:
return html.Div([
html.Hr(),
html.H6([html.I(className="fas fa-chart-line me-2 text-primary"), "System Performance"], className="mb-2"),
html.Div([
html.Span("Decision Fusion: ", className="text-muted small"),
html.Span("ON" if metrics.get("decision_fusion_active") else "OFF", className=f"small {'text-success' if metrics.get('decision_fusion_active') else 'text-muted'}"),
html.Span(" | COB: ", className="text-muted small"),
html.Span("ON" if metrics.get("cob_integration_active") else "OFF", className=f"small {'text-success' if metrics.get('cob_integration_active') else 'text-muted'}"),
], className="mb-1"),
html.Div([
html.Span("Tracking: ", className="text-muted small"),
html.Span(f"{metrics.get('symbols_tracking', 0)} symbols", className="text-info small"),
], className="mb-0"),
])