dark mode. new COB style

This commit is contained in:
Dobromir Popov
2025-07-22 22:00:27 +03:00
parent 55803c4fb9
commit 8898f71832
4 changed files with 162 additions and 37 deletions

View File

@ -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:

View File

@ -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 = '''
<!DOCTYPE html>
<html>
<head>
{%metas%}
<title>{%title%}</title>
{%favicon%}
{%css%}
<style>
body {
background-color: #111827 !important;
color: #f8f9fa !important;
}
.card {
background-color: #1f2937 !important;
border: 1px solid #374151 !important;
color: #f8f9fa !important;
}
.card-header {
background-color: #374151 !important;
border-bottom: 1px solid #4b5563 !important;
color: #f8f9fa !important;
}
.table {
color: #f8f9fa !important;
}
.table-dark {
background-color: #1f2937 !important;
}
.bg-light {
background-color: #374151 !important;
}
.text-muted {
color: #9ca3af !important;
}
.border {
border-color: #4b5563 !important;
}
</style>
</head>
<body>
{%app_entry%}
<footer>
{%config%}
{%scripts%}
{%renderer%}
</footer>
</body>
</html>
'''
# Suppress Dash development mode logging
self.app.enable_dev_tools(debug=False, dev_tools_silence_routes_logging=True)

View File

@ -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"""

View File

@ -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(