COB cummilative stats

This commit is contained in:
Dobromir Popov
2025-06-30 02:52:27 +03:00
parent 296e1be422
commit 88614bfd19
2 changed files with 470 additions and 741 deletions

File diff suppressed because it is too large Load Diff

View File

@ -247,8 +247,8 @@ class DashboardComponentManager:
logger.error(f"Error formatting system status: {e}")
return [html.P(f"Error: {str(e)}", className="text-danger small")]
def format_cob_data(self, cob_snapshot, symbol):
"""Format COB data into a ladder display with volume bars"""
def format_cob_data(self, cob_snapshot, symbol, cumulative_imbalance_stats=None):
"""Format COB data into a split view with summary, imbalance stats, and a compact ladder."""
try:
if not cob_snapshot or not hasattr(cob_snapshot, 'stats'):
return html.Div([
@ -259,8 +259,6 @@ class DashboardComponentManager:
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', [])
@ -271,26 +269,90 @@ class DashboardComponentManager:
html.P("Awaiting valid order book data...", className="text-muted small")
])
# Header with summary stats
# --- Left Panel: Overview and Stats ---
overview_panel = self._create_cob_overview_panel(symbol, stats, cumulative_imbalance_stats)
# --- Right Panel: Compact Ladder ---
ladder_panel = self._create_cob_ladder_panel(bids, asks, mid_price)
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
except Exception as e:
logger.error(f"Error formatting split COB data: {e}")
return html.P(f"Error: {str(e)}", className="text-danger small")
def _create_cob_overview_panel(self, symbol, stats, cumulative_imbalance_stats):
"""Creates the left panel with summary and imbalance stats."""
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)
bid_levels = stats.get('bid_levels', 0)
ask_levels = stats.get('ask_levels', 0)
imbalance = stats.get('imbalance', 0)
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"
header = html.Div([
html.H6(f"{symbol} - COB Ladder", className="mb-1"),
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))
return html.Div([
html.H6(f"{symbol} - COB Overview", className="mb-2"),
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")
self._create_stat_card("Mid Price", f"${mid_price:,.2f}", "fas fa-dollar-sign"),
self._create_stat_card("Spread", f"{spread_bps:.1f} bps", "fas fa-arrows-alt-h")
], className="d-flex justify-content-between mb-2"),
# --- Ladder Creation ---
bucket_size = 10 # $10 price buckets
num_levels = 5 # 5 levels above and below
html.Div([
html.Strong("Snapshot Imbalance: ", className="small"),
html.Span(imbalance_text, className=f"fw-bold small {imbalance_color}")
]),
# Aggregate bids and asks into buckets
def aggregate_buckets(orders, mid_price, bucket_size):
html.Div(imbalance_stats_display),
html.Hr(className="my-2"),
html.Table([
html.Tbody([
html.Tr([html.Td("Bid Liq.", className="small text-muted"), html.Td(f"${total_bid_liquidity/1e6:.2f}M", className="text-end small")]),
html.Tr([html.Td("Ask Liq.", className="small text-muted"), html.Td(f"${total_ask_liquidity/1e6:.2f}M", className="text-end small")]),
html.Tr([html.Td("Bid Levels", className="small text-muted"), html.Td(f"{bid_levels}", className="text-end small")]),
html.Tr([html.Td("Ask Levels", className="small text-muted"), html.Td(f"{ask_levels}", className="text-end small")])
])
], className="table table-sm table-borderless")
], className="p-2 border rounded", style={"backgroundColor": "rgba(255,255,255,0.03)"})
def _create_imbalance_stat_row(self, period, value):
"""Helper to format a single row of cumulative imbalance."""
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"
return html.Div([
html.Span(f"{period}:", className="small text-muted", style={"width": "35px", "display": "inline-block"}),
html.Span([
html.I(className=f"{icon} me-1 {color}"),
html.Span(f"{value:+.3f}", className=f"fw-bold small {color}")
])
], className="d-flex align-items-center mb-1")
def _create_stat_card(self, title, value, icon):
"""Helper for creating small stat cards."""
return html.Div([
html.Div(title, className="small text-muted"),
html.Div(value, className="fw-bold")
], className="text-center")
def _create_cob_ladder_panel(self, bids, asks, mid_price):
"""Creates the right panel with the compact COB ladder."""
bucket_size = 10
num_levels = 5
def aggregate_buckets(orders):
buckets = {}
for order in orders:
price = order.get('price', 0)
@ -299,56 +361,52 @@ class DashboardComponentManager:
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)
buckets[bucket_key] += size * price
return buckets
bid_buckets = aggregate_buckets(bids, mid_price, bucket_size)
ask_buckets = aggregate_buckets(asks, mid_price, bucket_size)
bid_buckets = aggregate_buckets(bids)
ask_buckets = aggregate_buckets(asks)
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")
]))
def create_ladder_row(price, volume, max_vol, row_type):
progress = (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"
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")
]))
vol_str = f"${volume/1e3:.0f}K" if volume > 1e3 else f"${volume:,.0f}"
return html.Tr([
html.Td(f"${price:,.2f}", className=f"{text_color} price-level"),
html.Td(
dbc.Progress(value=progress, color=color, className="vh-25 compact-progress"),
className="progress-cell"
),
html.Td(vol_str, className="volume-level text-end")
], className="compact-ladder-row")
ask_rows = [create_ladder_row(p, ask_buckets.get(p, 0), max_volume, 'ask') for p in sorted(ask_levels, reverse=True)]
bid_rows = [create_ladder_row(p, bid_buckets.get(p, 0), max_volume, 'bid') for p in sorted(bid_levels, reverse=True)]
# Mid-price separator
mid_row = html.Tr([
html.Td(f"${mid_price:,.2f}", colSpan=3, className="text-center fw-bold text-white bg-secondary")
html.Td(f"${mid_price:,.2f}", colSpan=3, className="text-center fw-bold small mid-price-row")
])
ladder_table = html.Table([
html.Thead(html.Tr([html.Th("Price (USD)"), html.Th("Volume (USD)"), html.Th("Total")])),
html.Thead(html.Tr([
html.Th("Price", className="small"),
html.Th("Volume", className="small"),
html.Th("Total", className="small text-end")
])),
html.Tbody(ask_rows + [mid_row] + bid_rows)
], className="table table-sm table-dark cob-ladder-table")
], className="table table-sm table-borderless cob-ladder-table-compact m-0 p-0") # Compact classes
return html.Div([header, ladder_table])
except Exception as e:
logger.error(f"Error formatting COB data ladder: {e}")
return html.P(f"Error: {str(e)}", className="text-danger small")
return ladder_table
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"""