cleanup new COB ladder

This commit is contained in:
Dobromir Popov
2025-07-22 21:39:36 +03:00
parent 153ebe6ec2
commit 55803c4fb9
33 changed files with 125 additions and 90096 deletions

View File

@ -405,24 +405,21 @@ class DashboardComponentManager:
], className="text-center")
def _create_cob_ladder_panel(self, bids, asks, mid_price, symbol=""):
"""Creates the right panel with the compact COB ladder."""
"""Creates Bookmap-style COB display with horizontal bars extending from center price."""
# Use symbol-specific bucket sizes: ETH = $1, BTC = $10
bucket_size = 1.0 if "ETH" in symbol else 10.0
num_levels = 5
num_levels = 20 # Show 20 levels each side
def aggregate_buckets(orders):
buckets = {}
for order in orders:
# Handle both dictionary format and ConsolidatedOrderBookLevel objects
if hasattr(order, 'price'):
# ConsolidatedOrderBookLevel object
price = order.price
size = order.total_size
volume_usd = order.total_volume_usd
else:
# Dictionary format (legacy)
price = order.get('price', 0)
# Handle both old format (size) and new format (total_size)
size = order.get('total_size', order.get('size', 0))
volume_usd = order.get('total_volume_usd', size * price)
@ -437,68 +434,133 @@ class DashboardComponentManager:
bid_buckets = aggregate_buckets(bids)
ask_buckets = aggregate_buckets(asks)
# Calculate max volume for scaling
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
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)]
def create_ladder_row(price, bucket_data, max_vol, row_type):
usd_volume = bucket_data.get('usd_volume', 0)
crypto_volume = bucket_data.get('crypto_volume', 0)
def create_bookmap_row(price, bid_data, ask_data, max_vol):
"""Create a Bookmap-style row with horizontal bars extending from center"""
bid_volume = bid_data.get('usd_volume', 0)
ask_volume = ask_data.get('usd_volume', 0)
progress = (usd_volume / max_vol) * 100 if max_vol > 0 else 0
color = "danger" if row_type == 'ask' else "success"
text_color = "text-danger" if row_type == 'ask' else "text-success"
# Calculate bar widths (0-100%)
bid_width = (bid_volume / max_vol) * 100 if max_vol > 0 else 0
ask_width = (ask_volume / max_vol) * 100 if max_vol > 0 else 0
# Format USD volume (no $ symbol)
if usd_volume > 1e6:
usd_str = f"{usd_volume/1e6:.1f}M"
elif usd_volume > 1e3:
usd_str = f"{usd_volume/1e3:.0f}K"
else:
usd_str = f"{usd_volume:,.0f}"
# Format volumes
def format_volume(vol):
if vol > 1e6:
return f"{vol/1e6:.1f}M"
elif vol > 1e3:
return f"{vol/1e3:.0f}K"
elif vol > 0:
return f"{vol:,.0f}"
return ""
# Format crypto volume (no unit symbol)
if crypto_volume > 1000:
crypto_str = f"{crypto_volume/1000:.1f}K"
elif crypto_volume > 1:
crypto_str = f"{crypto_volume:.1f}"
else:
crypto_str = f"{crypto_volume:.3f}"
bid_vol_str = format_volume(bid_volume)
ask_vol_str = format_volume(ask_volume)
return html.Div([
# Price level row
html.Div([
# Bid side (left) - green bar extending right
html.Div([
html.Div(
bid_vol_str,
className="text-end text-success small fw-bold px-1",
style={
"background": "rgba(40, 167, 69, 0.8)" if bid_volume > 0 else "transparent",
"width": f"{bid_width}%",
"minHeight": "18px",
"display": "flex",
"alignItems": "center",
"justifyContent": "flex-end",
"marginLeft": "auto"
}
)
], style={"width": "40%", "display": "flex", "justifyContent": "flex-end"}),
# Price in center
html.Div(
f"{price:,.0f}",
className="text-center small fw-bold text-light px-2",
style={
"width": "20%",
"minHeight": "18px",
"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)"
}
),
# Ask side (right) - red bar extending left
html.Div([
html.Div(
ask_vol_str,
className="text-start text-danger small fw-bold px-1",
style={
"background": "rgba(220, 53, 69, 0.8)" if ask_volume > 0 else "transparent",
"width": f"{ask_width}%",
"minHeight": "18px",
"display": "flex",
"alignItems": "center",
"justifyContent": "flex-start"
}
)
], style={"width": "40%", "display": "flex", "justifyContent": "flex-start"})
], style={
"display": "flex",
"alignItems": "center",
"marginBottom": "1px",
"background": "rgba(33, 37, 41, 0.9)",
"border": "1px solid rgba(255,255,255,0.1)"
})
])
return html.Tr([
html.Td(f"${price:,.0f}", className=f"{text_color} price-level small"),
html.Td(
dbc.Progress(value=progress, color=color, className="vh-25 compact-progress"),
className="progress-cell p-0"
),
html.Td(usd_str, className="volume-level text-end fw-bold small p-0 pe-1"),
html.Td(crypto_str, className="volume-level text-start small text-muted p-0 ps-1")
], className="compact-ladder-row p-0")
# Create all price levels
all_levels = sorted(set(ask_levels + bid_levels + [center_bucket]), reverse=True)
rows = []
for price in all_levels:
bid_data = bid_buckets.get(price, {'usd_volume': 0})
ask_data = ask_buckets.get(price, {'usd_volume': 0})
# Only show rows with some volume or near mid price
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))
def get_bucket_data(buckets, price):
return buckets.get(price, {'usd_volume': 0, 'crypto_volume': 0})
# Add header
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%"})
], style={
"display": "flex",
"marginBottom": "5px",
"padding": "5px",
"background": "rgba(52, 58, 64, 0.9)",
"border": "1px solid rgba(255,255,255,0.2)"
})
ask_rows = [create_ladder_row(p, get_bucket_data(ask_buckets, p), max_volume, 'ask') for p in sorted(ask_levels, reverse=True)]
bid_rows = [create_ladder_row(p, get_bucket_data(bid_buckets, p), max_volume, 'bid') for p in sorted(bid_levels, reverse=True)]
mid_row = html.Tr([
html.Td(f"${mid_price:,.0f}", colSpan=4, className="text-center fw-bold small mid-price-row p-0")
])
ladder_table = html.Table([
html.Thead(html.Tr([
html.Th("Price", className="small p-0"),
html.Th("Volume", className="small p-0"),
html.Th("USD", className="small text-end p-0 pe-1"),
html.Th("Crypto", className="small text-start p-0 ps-1")
])),
html.Tbody(ask_rows + [mid_row] + bid_rows)
], className="table table-sm table-borderless cob-ladder-table-compact m-0 p-0") # Compact classes
return ladder_table
return html.Div([
header,
html.Div(rows, style={
"maxHeight": "400px",
"overflowY": "auto",
"background": "rgba(33, 37, 41, 0.95)",
"border": "1px solid rgba(255,255,255,0.2)",
"borderRadius": "4px"
})
], style={"fontFamily": "monospace"})
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"""