COB cummilative stats
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -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,85 +269,145 @@ class DashboardComponentManager:
|
||||
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"
|
||||
# --- Left Panel: Overview and Stats ---
|
||||
overview_panel = self._create_cob_overview_panel(symbol, stats, cumulative_imbalance_stats)
|
||||
|
||||
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")
|
||||
# --- Right Panel: Compact Ladder ---
|
||||
ladder_panel = self._create_cob_ladder_panel(bids, asks, mid_price)
|
||||
|
||||
# --- 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")
|
||||
]))
|
||||
|
||||
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")
|
||||
]))
|
||||
|
||||
# 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])
|
||||
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 COB data ladder: {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"
|
||||
|
||||
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([
|
||||
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"),
|
||||
|
||||
html.Div([
|
||||
html.Strong("Snapshot Imbalance: ", className="small"),
|
||||
html.Span(imbalance_text, className=f"fw-bold small {imbalance_color}")
|
||||
]),
|
||||
|
||||
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)
|
||||
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
|
||||
return buckets
|
||||
|
||||
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
|
||||
|
||||
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, 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"
|
||||
|
||||
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_row = html.Tr([
|
||||
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", 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-borderless cob-ladder-table-compact m-0 p-0") # Compact classes
|
||||
|
||||
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"""
|
||||
try:
|
||||
|
Reference in New Issue
Block a user