From 296e1be4226052592f2466eb4b9f1ae9fb3a3ef9 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Mon, 30 Jun 2025 02:39:37 +0300 Subject: [PATCH] COB working --- web/clean_dashboard.py | 8 +- web/component_manager.py | 262 ++++++++++++++++++++------------------- web/layout_manager.py | 96 +++++++++++++- 3 files changed, 232 insertions(+), 134 deletions(-) diff --git a/web/clean_dashboard.py b/web/clean_dashboard.py index 7461355..a080365 100644 --- a/web/clean_dashboard.py +++ b/web/clean_dashboard.py @@ -1809,13 +1809,7 @@ class CleanTradingDashboard: def __init__(self, data): self.consolidated_bids = data.get('bids', []) self.consolidated_asks = data.get('asks', []) - stats = data.get('stats', {}) - self.spread_bps = stats.get('spread_bps', 0) - self.volume_weighted_mid = stats.get('mid_price', 0) - self.liquidity_imbalance = stats.get('imbalance', 0) - self.total_bid_liquidity = stats.get('total_bid_liquidity', 0) - self.total_ask_liquidity = stats.get('total_ask_liquidity', 0) - self.exchanges_active = stats.get('exchanges_active', []) + self.stats = data.get('stats', {}) return COBSnapshot(cob_data) else: diff --git a/web/component_manager.py b/web/component_manager.py index edfa5be..dd5e8b1 100644 --- a/web/component_manager.py +++ b/web/component_manager.py @@ -4,8 +4,10 @@ Manages the formatting and creation of dashboard components """ from dash import html, dcc +import dash_bootstrap_components as dbc from datetime import datetime import logging +import numpy as np logger = logging.getLogger(__name__) @@ -245,139 +247,108 @@ class DashboardComponentManager: logger.error(f"Error formatting system status: {e}") return [html.P(f"Error: {str(e)}", className="text-danger small")] - def _format_cnn_pivot_prediction(self, model_info): - """Format CNN pivot prediction for display""" - try: - pivot_prediction = model_info.get('pivot_prediction') - if not pivot_prediction: - return html.Div() - - pivot_type = pivot_prediction.get('pivot_type', 'UNKNOWN') - predicted_price = pivot_prediction.get('predicted_price', 0) - confidence = pivot_prediction.get('confidence', 0) - time_horizon = pivot_prediction.get('time_horizon_minutes', 0) - - # Color coding for pivot types - if 'RESISTANCE' in pivot_type: - pivot_color = "text-danger" - pivot_icon = "fas fa-arrow-up" - elif 'SUPPORT' in pivot_type: - pivot_color = "text-success" - pivot_icon = "fas fa-arrow-down" - else: - pivot_color = "text-warning" - pivot_icon = "fas fa-arrows-alt-h" - - return html.Div([ - html.Div([ - html.I(className=f"{pivot_icon} me-1 {pivot_color}"), - html.Span("Next Pivot: ", className="text-muted small"), - html.Span(f"${predicted_price:.2f}", className=f"small fw-bold {pivot_color}") - ], className="mb-1"), - html.Div([ - html.Span(f"{pivot_type.replace('_', ' ')}", className=f"small {pivot_color}"), - html.Span(f" ({confidence:.0%}) in ~{time_horizon}m", className="text-muted small") - ]) - ], className="mt-1 p-1", style={"backgroundColor": "rgba(255,255,255,0.02)", "borderRadius": "3px"}) - - except Exception as e: - logger.debug(f"Error formatting CNN pivot prediction: {e}") - return html.Div() - def format_cob_data(self, cob_snapshot, symbol): - """Format COB data for display""" + """Format COB data into a ladder display with volume bars""" try: - if not cob_snapshot: - return [html.P("No COB data", className="text-muted small")] + if not cob_snapshot or not hasattr(cob_snapshot, 'stats'): + return html.Div([ + html.H6(f"{symbol} COB", className="mb-2"), + html.P("No COB data available", className="text-muted small") + ]) - # Real COB data display - cob_info = [] + stats = cob_snapshot.stats if hasattr(cob_snapshot, 'stats') else {} + mid_price = stats.get('mid_price', 0) + spread_bps = stats.get('spread_bps', 0) + total_bid_liquidity = stats.get('total_bid_liquidity', 0) + total_ask_liquidity = stats.get('total_ask_liquidity', 0) + imbalance = stats.get('imbalance', 0) + bids = getattr(cob_snapshot, 'consolidated_bids', []) + asks = getattr(cob_snapshot, 'consolidated_asks', []) + + 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") + ]) + + # Header with summary stats + imbalance_text = f"Bid Heavy ({imbalance:.3f})" if imbalance > 0 else f"Ask Heavy ({imbalance:.3f})" + imbalance_color = "text-success" if imbalance > 0 else "text-danger" - # Symbol header - cob_info.append(html.Div([ - html.Strong(f"{symbol}", className="text-info"), - html.Span(" - COB Snapshot", className="small text-muted") - ], className="mb-2")) - - # Check if we have a real COB snapshot object - if hasattr(cob_snapshot, 'volume_weighted_mid'): - # Real COB snapshot data - mid_price = getattr(cob_snapshot, 'volume_weighted_mid', 0) - spread_bps = getattr(cob_snapshot, 'spread_bps', 0) - bid_liquidity = getattr(cob_snapshot, 'total_bid_liquidity', 0) - ask_liquidity = getattr(cob_snapshot, 'total_ask_liquidity', 0) - imbalance = getattr(cob_snapshot, 'liquidity_imbalance', 0) - bid_levels = len(getattr(cob_snapshot, 'consolidated_bids', [])) - ask_levels = len(getattr(cob_snapshot, 'consolidated_asks', [])) - - # Price and spread - cob_info.append(html.Div([ - html.Div([ - html.I(className="fas fa-dollar-sign text-success me-2"), - html.Span(f"Mid: ${mid_price:.2f}", className="small fw-bold") - ], className="mb-1"), - html.Div([ - html.I(className="fas fa-arrows-alt-h text-warning me-2"), - html.Span(f"Spread: {spread_bps:.1f} bps", className="small") - ], className="mb-1") + header = html.Div([ + html.H6(f"{symbol} - COB Ladder", className="mb-1"), + html.Div([ + html.Span(f"Mid: ${mid_price:,.2f}", className="me-3"), + html.Span(f"Spread: {spread_bps:.1f} bps", className="me-3"), + html.Span(f"Imbalance: ", className="small"), + html.Span(imbalance_text, className=f"fw-bold {imbalance_color}") + ], className="small text-muted") + ], className="mb-2") + + # --- Ladder Creation --- + bucket_size = 10 # $10 price buckets + num_levels = 5 # 5 levels above and below + + # Aggregate bids and asks into buckets + def aggregate_buckets(orders, mid_price, bucket_size): + buckets = {} + for order in orders: + price = order.get('price', 0) + size = order.get('size', 0) + if price > 0: + bucket_key = round(price / bucket_size) * bucket_size + if bucket_key not in buckets: + buckets[bucket_key] = 0 + buckets[bucket_key] += size * price # Volume in quote currency (USD) + return buckets + + bid_buckets = aggregate_buckets(bids, mid_price, bucket_size) + ask_buckets = aggregate_buckets(asks, mid_price, bucket_size) + + all_volumes = list(bid_buckets.values()) + list(ask_buckets.values()) + max_volume = max(all_volumes) if all_volumes else 1 + + # Determine ladder price levels + center_bucket = round(mid_price / bucket_size) * bucket_size + ask_levels = [center_bucket + i * bucket_size for i in range(1, num_levels + 1)] + bid_levels = [center_bucket - i * bucket_size for i in range(num_levels)] + + # Create ladder rows + ask_rows = [] + for price in sorted(ask_levels, reverse=True): + volume = ask_buckets.get(price, 0) + progress = (volume / max_volume) * 100 + ask_rows.append(html.Tr([ + html.Td(f"${price:,.2f}", className="text-danger price-level"), + html.Td(f"${volume:,.0f}", className="volume-level"), + html.Td(dbc.Progress(value=progress, color="danger", className="vh-25"), className="progress-cell") ])) - - # Liquidity info - total_liquidity = bid_liquidity + ask_liquidity - bid_pct = (bid_liquidity / total_liquidity * 100) if total_liquidity > 0 else 0 - ask_pct = (ask_liquidity / total_liquidity * 100) if total_liquidity > 0 else 0 - - cob_info.append(html.Div([ - html.Div([ - html.I(className="fas fa-layer-group text-info me-2"), - html.Span(f"Liquidity: ${total_liquidity:,.0f}", className="small") - ], className="mb-1"), - html.Div([ - html.Span(f"Bids: {bid_pct:.0f}% ", className="small text-success"), - html.Span(f"Asks: {ask_pct:.0f}%", className="small text-danger") - ], className="mb-1") - ])) - - # Order book depth - cob_info.append(html.Div([ - html.Div([ - html.I(className="fas fa-list text-secondary me-2"), - html.Span(f"Levels: {bid_levels} bids, {ask_levels} asks", className="small") - ], className="mb-1") - ])) - - # Imbalance indicator - imbalance_color = "text-success" if imbalance > 0.1 else "text-danger" if imbalance < -0.1 else "text-muted" - imbalance_text = "Bid Heavy" if imbalance > 0.1 else "Ask Heavy" if imbalance < -0.1 else "Balanced" - - cob_info.append(html.Div([ - html.I(className="fas fa-balance-scale me-2"), - html.Span(f"Imbalance: ", className="small text-muted"), - html.Span(f"{imbalance_text} ({imbalance:.3f})", className=f"small {imbalance_color}") - ], className="mb-1")) - - else: - # Fallback display for other data formats - cob_info.append(html.Div([ - html.Div([ - html.I(className="fas fa-chart-bar text-success me-2"), - html.Span("Order Book: Active", className="small") - ], className="mb-1"), - html.Div([ - html.I(className="fas fa-coins text-warning me-2"), - html.Span("Liquidity: Good", className="small") - ], className="mb-1"), - html.Div([ - html.I(className="fas fa-balance-scale text-info me-2"), - html.Span("Imbalance: Neutral", className="small") - ]) + + bid_rows = [] + for price in sorted(bid_levels, reverse=True): + volume = bid_buckets.get(price, 0) + progress = (volume / max_volume) * 100 + bid_rows.append(html.Tr([ + html.Td(f"${price:,.2f}", className="text-success price-level"), + html.Td(f"${volume:,.0f}", className="volume-level"), + html.Td(dbc.Progress(value=progress, color="success", className="vh-25"), className="progress-cell") ])) - return cob_info - + # Mid-price separator + mid_row = html.Tr([ + html.Td(f"${mid_price:,.2f}", colSpan=3, className="text-center fw-bold text-white bg-secondary") + ]) + + ladder_table = html.Table([ + html.Thead(html.Tr([html.Th("Price (USD)"), html.Th("Volume (USD)"), html.Th("Total")])), + html.Tbody(ask_rows + [mid_row] + bid_rows) + ], className="table table-sm table-dark cob-ladder-table") + + return html.Div([header, ladder_table]) + except Exception as e: - logger.error(f"Error formatting COB data: {e}") - return [html.P(f"Error: {str(e)}", className="text-danger small")] + logger.error(f"Error formatting COB data ladder: {e}") + return html.P(f"Error: {str(e)}", className="text-danger small") def format_cob_data_with_buckets(self, cob_snapshot, symbol, price_buckets, memory_stats, bucket_size=1.0): """Format COB data with price buckets for high-frequency display""" @@ -692,4 +663,43 @@ class DashboardComponentManager: except Exception as e: logger.error(f"Error formatting training metrics: {e}") - return [html.P(f"Error: {str(e)}", className="text-danger small")] \ No newline at end of file + return [html.P(f"Error: {str(e)}", className="text-danger small")] + + def _format_cnn_pivot_prediction(self, model_info): + """Format CNN pivot prediction for display""" + try: + pivot_prediction = model_info.get('pivot_prediction') + if not pivot_prediction: + return html.Div() + + pivot_type = pivot_prediction.get('pivot_type', 'UNKNOWN') + predicted_price = pivot_prediction.get('predicted_price', 0) + confidence = pivot_prediction.get('confidence', 0) + time_horizon = pivot_prediction.get('time_horizon_minutes', 0) + + # Color coding for pivot types + if 'RESISTANCE' in pivot_type: + pivot_color = "text-danger" + pivot_icon = "fas fa-arrow-up" + elif 'SUPPORT' in pivot_type: + pivot_color = "text-success" + pivot_icon = "fas fa-arrow-down" + else: + pivot_color = "text-warning" + pivot_icon = "fas fa-arrows-alt-h" + + return html.Div([ + html.Div([ + html.I(className=f"{pivot_icon} me-1 {pivot_color}"), + html.Span("Next Pivot: ", className="text-muted small"), + html.Span(f"${predicted_price:.2f}", className=f"small fw-bold {pivot_color}") + ], className="mb-1"), + html.Div([ + html.Span(f"{pivot_type.replace('_', ' ')}", className=f"small {pivot_color}"), + html.Span(f" ({confidence:.0%}) in ~{time_horizon}m", className="text-muted small") + ]) + ], className="mt-1 p-1", style={"backgroundColor": "rgba(255,255,255,0.02)", "borderRadius": "3px"}) + + except Exception as e: + logger.debug(f"Error formatting CNN pivot prediction: {e}") + return html.Div() \ No newline at end of file diff --git a/web/layout_manager.py b/web/layout_manager.py index 6855bfd..c0e3e33 100644 --- a/web/layout_manager.py +++ b/web/layout_manager.py @@ -51,7 +51,7 @@ class DashboardLayoutManager: return html.Div([ self._create_metrics_and_signals_row(), self._create_charts_row(), - self._create_analytics_and_performance_row() + self._create_cob_and_trades_row() ]) def _create_metrics_and_signals_row(self): @@ -199,6 +199,100 @@ class DashboardLayoutManager: ], className="card") ]) + def _create_cob_and_trades_row(self): + """Creates the row for COB ladders, closed trades, and model status.""" + return html.Div( + [ + # Left side: COB Ladders (60% width) + html.Div( + [ + html.Div( + [ + # ETH/USDT COB + html.Div( + [ + html.Div( + id="eth-cob-content", + className="card-body p-2", + ) + ], + className="card", + style={"flex": "1"}, + ), + # BTC/USDT COB + html.Div( + [ + html.Div( + id="btc-cob-content", + className="card-body p-2", + ) + ], + className="card", + style={"flex": "1", "marginLeft": "1rem"}, + ), + ], + className="d-flex", + ) + ], + style={"width": "60%"}, + ), + # Right side: Trades and Model Status (40% width) + html.Div( + [ + # Closed Trades + html.Div( + [ + html.Div( + [ + html.H6( + [ + html.I(className="fas fa-history me-2"), + "Closed Trades", + ], + className="card-title mb-2", + ), + html.Div( + id="closed-trades-table", + style={"height": "250px", "overflowY": "auto"}, + ), + ], + className="card-body p-2", + ) + ], + className="card mb-3", + ), + # Model Status + html.Div( + [ + html.Div( + [ + html.H6( + [ + html.I(className="fas fa-brain me-2"), + "Models & Training Progress", + ], + className="card-title mb-2", + ), + html.Div( + id="training-metrics", + style={ + "height": "250px", + "overflowY": "auto", + }, + ), + ], + className="card-body p-2", + ) + ], + className="card", + ), + ], + style={"width": "38%", "marginLeft": "2%"}, + ), + ], + className="d-flex mb-3", + ) + def _create_analytics_and_performance_row(self): """Create the combined analytics and performance row with COB data, trades, and training progress""" return html.Div([