From 8898f71832cea8006a1aeb32f62a446f5dbd2ebd Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 22 Jul 2025 22:00:27 +0300 Subject: [PATCH] dark mode. new COB style --- core/multi_exchange_cob_provider.py | 31 ++++++++- web/clean_dashboard.py | 54 +++++++++++++++- web/component_manager.py | 99 +++++++++++++++++++---------- web/layout_manager.py | 15 ++++- 4 files changed, 162 insertions(+), 37 deletions(-) diff --git a/core/multi_exchange_cob_provider.py b/core/multi_exchange_cob_provider.py index 93064b5..d77af8b 100644 --- a/core/multi_exchange_cob_provider.py +++ b/core/multi_exchange_cob_provider.py @@ -46,12 +46,17 @@ import aiohttp.resolver logger = logging.getLogger(__name__) +# goal: use top 10 exchanges +# https://www.coingecko.com/en/exchanges + class ExchangeType(Enum): BINANCE = "binance" COINBASE = "coinbase" KRAKEN = "kraken" HUOBI = "huobi" BITFINEX = "bitfinex" + BYBIT = "bybit" + BITGET = "bitget" @dataclass class ExchangeOrderBookLevel: @@ -288,6 +293,24 @@ class MultiExchangeCOBProvider: rate_limits={'requests_per_minute': 1000} ) + # Bybit configuration + configs[ExchangeType.BYBIT.value] = ExchangeConfig( + exchange_type=ExchangeType.BYBIT, + weight=0.18, + websocket_url="wss://stream.bybit.com/v5/public/spot", + rest_api_url="https://api.bybit.com", + symbols_mapping={'BTC/USDT': 'BTCUSDT', 'ETH/USDT': 'ETHUSDT'}, + rate_limits={'requests_per_minute': 1200} + ) + # Bitget configuration + configs[ExchangeType.BITGET.value] = ExchangeConfig( + exchange_type=ExchangeType.BITGET, + weight=0.12, + websocket_url="wss://ws.bitget.com/spot/v1/stream", + rest_api_url="https://api.bitget.com", + symbols_mapping={'BTC/USDT': 'BTCUSDT_SPBL', 'ETH/USDT': 'ETHUSDT_SPBL'}, + rate_limits={'requests_per_minute': 1200} + ) return configs async def start_streaming(self): @@ -459,6 +482,10 @@ class MultiExchangeCOBProvider: await self._stream_huobi_orderbook(symbol, config) elif exchange_name == ExchangeType.BITFINEX.value: await self._stream_bitfinex_orderbook(symbol, config) + elif exchange_name == ExchangeType.BYBIT.value: + await self._stream_bybit_orderbook(symbol, config) + elif exchange_name == ExchangeType.BITGET.value: + await self._stream_bitget_orderbook(symbol, config) except Exception as e: logger.error(f"Error streaming {exchange_name} for {symbol}: {e}") @@ -467,7 +494,9 @@ class MultiExchangeCOBProvider: async def _stream_binance_orderbook(self, symbol: str, config: ExchangeConfig): """Stream order book data from Binance""" try: - ws_url = f"{config.websocket_url}{config.symbols_mapping[symbol].lower()}@depth@1000ms" + # Use partial book depth stream with maximum levels - Binance format + # @depth20@100ms gives us 20 levels at 100ms, but we also have REST API for full depth + ws_url = f"{config.websocket_url}{config.symbols_mapping[symbol].lower()}@depth20@100ms" logger.info(f"Connecting to Binance WebSocket: {ws_url}") if websockets is None or websockets_connect is None: diff --git a/web/clean_dashboard.py b/web/clean_dashboard.py index cbc64f5..83e0f72 100644 --- a/web/clean_dashboard.py +++ b/web/clean_dashboard.py @@ -174,12 +174,64 @@ class CleanTradingDashboard: timezone_name = self.config.get('system', {}).get('timezone', 'Europe/Sofia') self.timezone = pytz.timezone(timezone_name) - # Create Dash app + # Create Dash app with dark theme self.app = Dash(__name__, external_stylesheets=[ 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css' ]) + # Add custom dark theme CSS + self.app.index_string = ''' + + + + {%metas%} + {%title%} + {%favicon%} + {%css%} + + + + {%app_entry%} + + + + ''' + # Suppress Dash development mode logging self.app.enable_dev_tools(debug=False, dev_tools_silence_routes_logging=True) diff --git a/web/component_manager.py b/web/component_manager.py index a86e75c..a9009c0 100644 --- a/web/component_manager.py +++ b/web/component_manager.py @@ -438,10 +438,19 @@ class DashboardComponentManager: all_usd_volumes = [b['usd_volume'] for b in bid_buckets.values()] + [a['usd_volume'] for a in ask_buckets.values()] max_volume = max(all_usd_volumes) if all_usd_volumes else 1 - # Create price levels around mid price + # Create price levels around mid price - expanded range for more bars 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)] + + # Debug: Log how many orders we have to work with + print(f"DEBUG COB: {symbol} - Processing {len(bids)} bids, {len(asks)} asks") + print(f"DEBUG COB: Mid price: ${mid_price:.2f}, Bucket size: ${bucket_size}") + print(f"DEBUG COB: Bid buckets: {len(bid_buckets)}, Ask buckets: {len(ask_buckets)}") + if bid_buckets: + print(f"DEBUG COB: Bid price range: ${min(bid_buckets.keys()):.2f} - ${max(bid_buckets.keys()):.2f}") + if ask_buckets: + print(f"DEBUG COB: Ask price range: ${min(ask_buckets.keys()):.2f} - ${max(ask_buckets.keys()):.2f}") def create_bookmap_row(price, bid_data, ask_data, max_vol): """Create a Bookmap-style row with horizontal bars extending from center""" @@ -472,32 +481,40 @@ class DashboardComponentManager: html.Div([ html.Div( bid_vol_str, - className="text-end text-success small fw-bold px-1", + className="text-end small fw-bold px-2", style={ - "background": "rgba(40, 167, 69, 0.8)" if bid_volume > 0 else "transparent", + "background": "linear-gradient(90deg, rgba(34, 197, 94, 0.3), rgba(34, 197, 94, 0.9))" if bid_volume > 0 else "transparent", + "color": "#ffffff" if bid_volume > 0 else "transparent", "width": f"{bid_width}%", - "minHeight": "18px", + "minHeight": "22px", "display": "flex", "alignItems": "center", "justifyContent": "flex-end", - "marginLeft": "auto" + "marginLeft": "auto", + "border": "1px solid rgba(34, 197, 94, 0.5)" if bid_volume > 0 else "none", + "borderRadius": "2px", + "textShadow": "1px 1px 2px rgba(0,0,0,0.8)", + "fontWeight": "600" } ) - ], style={"width": "40%", "display": "flex", "justifyContent": "flex-end"}), + ], style={"width": "40%", "display": "flex", "justifyContent": "flex-end", "padding": "1px"}), # Price in center html.Div( f"{price:,.0f}", - className="text-center small fw-bold text-light px-2", + className="text-center small fw-bold px-2", style={ "width": "20%", - "minHeight": "18px", + "minHeight": "22px", "display": "flex", "alignItems": "center", "justifyContent": "center", - "background": "rgba(108, 117, 125, 0.8)", - "borderLeft": "1px solid rgba(255,255,255,0.2)", - "borderRight": "1px solid rgba(255,255,255,0.2)" + "background": "linear-gradient(180deg, rgba(75, 85, 99, 0.9), rgba(55, 65, 81, 0.9))", + "color": "#f8f9fa", + "borderLeft": "1px solid rgba(156, 163, 175, 0.3)", + "borderRight": "1px solid rgba(156, 163, 175, 0.3)", + "textShadow": "1px 1px 2px rgba(0,0,0,0.8)", + "fontWeight": "600" } ), @@ -505,24 +522,30 @@ class DashboardComponentManager: html.Div([ html.Div( ask_vol_str, - className="text-start text-danger small fw-bold px-1", + className="text-start small fw-bold px-2", style={ - "background": "rgba(220, 53, 69, 0.8)" if ask_volume > 0 else "transparent", + "background": "linear-gradient(270deg, rgba(239, 68, 68, 0.3), rgba(239, 68, 68, 0.9))" if ask_volume > 0 else "transparent", + "color": "#ffffff" if ask_volume > 0 else "transparent", "width": f"{ask_width}%", - "minHeight": "18px", + "minHeight": "22px", "display": "flex", "alignItems": "center", - "justifyContent": "flex-start" + "justifyContent": "flex-start", + "border": "1px solid rgba(239, 68, 68, 0.5)" if ask_volume > 0 else "none", + "borderRadius": "2px", + "textShadow": "1px 1px 2px rgba(0,0,0,0.8)", + "fontWeight": "600" } ) - ], style={"width": "40%", "display": "flex", "justifyContent": "flex-start"}) + ], style={"width": "40%", "display": "flex", "justifyContent": "flex-start", "padding": "1px"}) ], style={ "display": "flex", "alignItems": "center", - "marginBottom": "1px", - "background": "rgba(33, 37, 41, 0.9)", - "border": "1px solid rgba(255,255,255,0.1)" + "marginBottom": "2px", + "background": "rgba(17, 24, 39, 0.95)", + "border": "1px solid rgba(75, 85, 99, 0.3)", + "borderRadius": "3px" }) ]) @@ -538,29 +561,41 @@ class DashboardComponentManager: if bid_data['usd_volume'] > 0 or ask_data['usd_volume'] > 0 or abs(price - mid_price) <= bucket_size * 5: rows.append(create_bookmap_row(price, bid_data, ask_data, max_volume)) - # Add header + # Add header with improved dark theme styling header = html.Div([ - html.Div("BIDS", className="text-success text-center fw-bold small", style={"width": "40%"}), - html.Div("PRICE", className="text-light text-center fw-bold small", style={"width": "20%"}), - html.Div("ASKS", className="text-danger text-center fw-bold small", style={"width": "40%"}) + html.Div("BIDS", className="text-center fw-bold small", + style={"width": "40%", "color": "#10b981", "textShadow": "1px 1px 2px rgba(0,0,0,0.8)"}), + html.Div("PRICE", className="text-center fw-bold small", + style={"width": "20%", "color": "#f8f9fa", "textShadow": "1px 1px 2px rgba(0,0,0,0.8)"}), + html.Div("ASKS", className="text-center fw-bold small", + style={"width": "40%", "color": "#ef4444", "textShadow": "1px 1px 2px rgba(0,0,0,0.8)"}) ], style={ "display": "flex", - "marginBottom": "5px", - "padding": "5px", - "background": "rgba(52, 58, 64, 0.9)", - "border": "1px solid rgba(255,255,255,0.2)" + "marginBottom": "8px", + "padding": "8px", + "background": "linear-gradient(180deg, rgba(31, 41, 55, 0.95), rgba(17, 24, 39, 0.95))", + "border": "1px solid rgba(75, 85, 99, 0.4)", + "borderRadius": "6px", + "boxShadow": "0 2px 4px rgba(0,0,0,0.3)" }) return html.Div([ header, html.Div(rows, style={ - "maxHeight": "400px", + "maxHeight": "500px", "overflowY": "auto", - "background": "rgba(33, 37, 41, 0.95)", - "border": "1px solid rgba(255,255,255,0.2)", - "borderRadius": "4px" + "background": "linear-gradient(180deg, rgba(17, 24, 39, 0.98), rgba(31, 41, 55, 0.98))", + "border": "2px solid rgba(75, 85, 99, 0.4)", + "borderRadius": "8px", + "boxShadow": "inset 0 2px 4px rgba(0,0,0,0.3)" }) - ], style={"fontFamily": "monospace"}) + ], style={ + "fontFamily": "monospace", + "background": "rgba(17, 24, 39, 0.9)", + "padding": "8px", + "borderRadius": "8px", + "border": "1px solid rgba(75, 85, 99, 0.3)" + }) 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""" diff --git a/web/layout_manager.py b/web/layout_manager.py index dcd0bf0..8cc293e 100644 --- a/web/layout_manager.py +++ b/web/layout_manager.py @@ -15,12 +15,16 @@ class DashboardLayoutManager: self.trading_executor = trading_executor def create_main_layout(self): - """Create the main dashboard layout""" + """Create the main dashboard layout with dark theme""" return html.Div([ self._create_header(), self._create_interval_component(), self._create_main_content() - ], className="container-fluid") + ], className="container-fluid", style={ + "backgroundColor": "#111827", + "minHeight": "100vh", + "color": "#f8f9fa" + }) def _create_header(self): """Create the dashboard header""" @@ -84,7 +88,12 @@ class DashboardLayoutManager: html.H5(id=card_id, className=f"{text_class} mb-0 small"), html.P(label, className="text-muted mb-0 tiny") ], className="card-body text-center p-2") - ], className="card bg-light", style={"height": "60px"}) + ], className="card", style={ + "height": "60px", + "backgroundColor": "#1f2937", + "border": "1px solid #374151", + "color": "#f8f9fa" + }) cards.append(card) return html.Div(