Merge commit 'd49a473ed6f4aef55bfdd47d6370e53582be6b7b' into cleanup
This commit is contained in:
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user