COB working
This commit is contained in:
@ -1809,13 +1809,7 @@ class CleanTradingDashboard:
|
|||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.consolidated_bids = data.get('bids', [])
|
self.consolidated_bids = data.get('bids', [])
|
||||||
self.consolidated_asks = data.get('asks', [])
|
self.consolidated_asks = data.get('asks', [])
|
||||||
stats = data.get('stats', {})
|
self.stats = data.get('stats', {})
|
||||||
self.spread_bps = stats.get('spread_bps', 0)
|
|
||||||
self.volume_weighted_mid = stats.get('mid_price', 0)
|
|
||||||
self.liquidity_imbalance = stats.get('imbalance', 0)
|
|
||||||
self.total_bid_liquidity = stats.get('total_bid_liquidity', 0)
|
|
||||||
self.total_ask_liquidity = stats.get('total_ask_liquidity', 0)
|
|
||||||
self.exchanges_active = stats.get('exchanges_active', [])
|
|
||||||
|
|
||||||
return COBSnapshot(cob_data)
|
return COBSnapshot(cob_data)
|
||||||
else:
|
else:
|
||||||
|
@ -4,8 +4,10 @@ Manages the formatting and creation of dashboard components
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dash import html, dcc
|
from dash import html, dcc
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -245,139 +247,108 @@ class DashboardComponentManager:
|
|||||||
logger.error(f"Error formatting system status: {e}")
|
logger.error(f"Error formatting system status: {e}")
|
||||||
return [html.P(f"Error: {str(e)}", className="text-danger small")]
|
return [html.P(f"Error: {str(e)}", className="text-danger small")]
|
||||||
|
|
||||||
def _format_cnn_pivot_prediction(self, model_info):
|
|
||||||
"""Format CNN pivot prediction for display"""
|
|
||||||
try:
|
|
||||||
pivot_prediction = model_info.get('pivot_prediction')
|
|
||||||
if not pivot_prediction:
|
|
||||||
return html.Div()
|
|
||||||
|
|
||||||
pivot_type = pivot_prediction.get('pivot_type', 'UNKNOWN')
|
|
||||||
predicted_price = pivot_prediction.get('predicted_price', 0)
|
|
||||||
confidence = pivot_prediction.get('confidence', 0)
|
|
||||||
time_horizon = pivot_prediction.get('time_horizon_minutes', 0)
|
|
||||||
|
|
||||||
# Color coding for pivot types
|
|
||||||
if 'RESISTANCE' in pivot_type:
|
|
||||||
pivot_color = "text-danger"
|
|
||||||
pivot_icon = "fas fa-arrow-up"
|
|
||||||
elif 'SUPPORT' in pivot_type:
|
|
||||||
pivot_color = "text-success"
|
|
||||||
pivot_icon = "fas fa-arrow-down"
|
|
||||||
else:
|
|
||||||
pivot_color = "text-warning"
|
|
||||||
pivot_icon = "fas fa-arrows-alt-h"
|
|
||||||
|
|
||||||
return html.Div([
|
|
||||||
html.Div([
|
|
||||||
html.I(className=f"{pivot_icon} me-1 {pivot_color}"),
|
|
||||||
html.Span("Next Pivot: ", className="text-muted small"),
|
|
||||||
html.Span(f"${predicted_price:.2f}", className=f"small fw-bold {pivot_color}")
|
|
||||||
], className="mb-1"),
|
|
||||||
html.Div([
|
|
||||||
html.Span(f"{pivot_type.replace('_', ' ')}", className=f"small {pivot_color}"),
|
|
||||||
html.Span(f" ({confidence:.0%}) in ~{time_horizon}m", className="text-muted small")
|
|
||||||
])
|
|
||||||
], className="mt-1 p-1", style={"backgroundColor": "rgba(255,255,255,0.02)", "borderRadius": "3px"})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Error formatting CNN pivot prediction: {e}")
|
|
||||||
return html.Div()
|
|
||||||
|
|
||||||
def format_cob_data(self, cob_snapshot, symbol):
|
def format_cob_data(self, cob_snapshot, symbol):
|
||||||
"""Format COB data for display"""
|
"""Format COB data into a ladder display with volume bars"""
|
||||||
try:
|
try:
|
||||||
if not cob_snapshot:
|
if not cob_snapshot or not hasattr(cob_snapshot, 'stats'):
|
||||||
return [html.P("No COB data", className="text-muted small")]
|
return html.Div([
|
||||||
|
html.H6(f"{symbol} COB", className="mb-2"),
|
||||||
|
html.P("No COB data available", className="text-muted small")
|
||||||
|
])
|
||||||
|
|
||||||
# Real COB data display
|
stats = cob_snapshot.stats if hasattr(cob_snapshot, 'stats') else {}
|
||||||
cob_info = []
|
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', [])
|
||||||
|
|
||||||
|
if mid_price == 0 or not bids or not asks:
|
||||||
|
return html.Div([
|
||||||
|
html.H6(f"{symbol} COB", className="mb-2"),
|
||||||
|
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"
|
||||||
|
|
||||||
# Symbol header
|
header = html.Div([
|
||||||
cob_info.append(html.Div([
|
html.H6(f"{symbol} - COB Ladder", className="mb-1"),
|
||||||
html.Strong(f"{symbol}", className="text-info"),
|
html.Div([
|
||||||
html.Span(" - COB Snapshot", className="small text-muted")
|
html.Span(f"Mid: ${mid_price:,.2f}", className="me-3"),
|
||||||
], className="mb-2"))
|
html.Span(f"Spread: {spread_bps:.1f} bps", className="me-3"),
|
||||||
|
html.Span(f"Imbalance: ", className="small"),
|
||||||
# Check if we have a real COB snapshot object
|
html.Span(imbalance_text, className=f"fw-bold {imbalance_color}")
|
||||||
if hasattr(cob_snapshot, 'volume_weighted_mid'):
|
], className="small text-muted")
|
||||||
# Real COB snapshot data
|
], className="mb-2")
|
||||||
mid_price = getattr(cob_snapshot, 'volume_weighted_mid', 0)
|
|
||||||
spread_bps = getattr(cob_snapshot, 'spread_bps', 0)
|
# --- Ladder Creation ---
|
||||||
bid_liquidity = getattr(cob_snapshot, 'total_bid_liquidity', 0)
|
bucket_size = 10 # $10 price buckets
|
||||||
ask_liquidity = getattr(cob_snapshot, 'total_ask_liquidity', 0)
|
num_levels = 5 # 5 levels above and below
|
||||||
imbalance = getattr(cob_snapshot, 'liquidity_imbalance', 0)
|
|
||||||
bid_levels = len(getattr(cob_snapshot, 'consolidated_bids', []))
|
# Aggregate bids and asks into buckets
|
||||||
ask_levels = len(getattr(cob_snapshot, 'consolidated_asks', []))
|
def aggregate_buckets(orders, mid_price, bucket_size):
|
||||||
|
buckets = {}
|
||||||
# Price and spread
|
for order in orders:
|
||||||
cob_info.append(html.Div([
|
price = order.get('price', 0)
|
||||||
html.Div([
|
size = order.get('size', 0)
|
||||||
html.I(className="fas fa-dollar-sign text-success me-2"),
|
if price > 0:
|
||||||
html.Span(f"Mid: ${mid_price:.2f}", className="small fw-bold")
|
bucket_key = round(price / bucket_size) * bucket_size
|
||||||
], className="mb-1"),
|
if bucket_key not in buckets:
|
||||||
html.Div([
|
buckets[bucket_key] = 0
|
||||||
html.I(className="fas fa-arrows-alt-h text-warning me-2"),
|
buckets[bucket_key] += size * price # Volume in quote currency (USD)
|
||||||
html.Span(f"Spread: {spread_bps:.1f} bps", className="small")
|
return buckets
|
||||||
], className="mb-1")
|
|
||||||
|
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")
|
||||||
]))
|
]))
|
||||||
|
|
||||||
# Liquidity info
|
bid_rows = []
|
||||||
total_liquidity = bid_liquidity + ask_liquidity
|
for price in sorted(bid_levels, reverse=True):
|
||||||
bid_pct = (bid_liquidity / total_liquidity * 100) if total_liquidity > 0 else 0
|
volume = bid_buckets.get(price, 0)
|
||||||
ask_pct = (ask_liquidity / total_liquidity * 100) if total_liquidity > 0 else 0
|
progress = (volume / max_volume) * 100
|
||||||
|
bid_rows.append(html.Tr([
|
||||||
cob_info.append(html.Div([
|
html.Td(f"${price:,.2f}", className="text-success price-level"),
|
||||||
html.Div([
|
html.Td(f"${volume:,.0f}", className="volume-level"),
|
||||||
html.I(className="fas fa-layer-group text-info me-2"),
|
html.Td(dbc.Progress(value=progress, color="success", className="vh-25"), className="progress-cell")
|
||||||
html.Span(f"Liquidity: ${total_liquidity:,.0f}", className="small")
|
|
||||||
], className="mb-1"),
|
|
||||||
html.Div([
|
|
||||||
html.Span(f"Bids: {bid_pct:.0f}% ", className="small text-success"),
|
|
||||||
html.Span(f"Asks: {ask_pct:.0f}%", className="small text-danger")
|
|
||||||
], className="mb-1")
|
|
||||||
]))
|
|
||||||
|
|
||||||
# Order book depth
|
|
||||||
cob_info.append(html.Div([
|
|
||||||
html.Div([
|
|
||||||
html.I(className="fas fa-list text-secondary me-2"),
|
|
||||||
html.Span(f"Levels: {bid_levels} bids, {ask_levels} asks", className="small")
|
|
||||||
], className="mb-1")
|
|
||||||
]))
|
|
||||||
|
|
||||||
# Imbalance indicator
|
|
||||||
imbalance_color = "text-success" if imbalance > 0.1 else "text-danger" if imbalance < -0.1 else "text-muted"
|
|
||||||
imbalance_text = "Bid Heavy" if imbalance > 0.1 else "Ask Heavy" if imbalance < -0.1 else "Balanced"
|
|
||||||
|
|
||||||
cob_info.append(html.Div([
|
|
||||||
html.I(className="fas fa-balance-scale me-2"),
|
|
||||||
html.Span(f"Imbalance: ", className="small text-muted"),
|
|
||||||
html.Span(f"{imbalance_text} ({imbalance:.3f})", className=f"small {imbalance_color}")
|
|
||||||
], className="mb-1"))
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Fallback display for other data formats
|
|
||||||
cob_info.append(html.Div([
|
|
||||||
html.Div([
|
|
||||||
html.I(className="fas fa-chart-bar text-success me-2"),
|
|
||||||
html.Span("Order Book: Active", className="small")
|
|
||||||
], className="mb-1"),
|
|
||||||
html.Div([
|
|
||||||
html.I(className="fas fa-coins text-warning me-2"),
|
|
||||||
html.Span("Liquidity: Good", className="small")
|
|
||||||
], className="mb-1"),
|
|
||||||
html.Div([
|
|
||||||
html.I(className="fas fa-balance-scale text-info me-2"),
|
|
||||||
html.Span("Imbalance: Neutral", className="small")
|
|
||||||
])
|
|
||||||
]))
|
]))
|
||||||
|
|
||||||
return cob_info
|
# 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])
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error formatting COB data: {e}")
|
logger.error(f"Error formatting COB data ladder: {e}")
|
||||||
return [html.P(f"Error: {str(e)}", className="text-danger small")]
|
return html.P(f"Error: {str(e)}", className="text-danger small")
|
||||||
|
|
||||||
def format_cob_data_with_buckets(self, cob_snapshot, symbol, price_buckets, memory_stats, bucket_size=1.0):
|
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"""
|
"""Format COB data with price buckets for high-frequency display"""
|
||||||
@ -692,4 +663,43 @@ class DashboardComponentManager:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error formatting training metrics: {e}")
|
logger.error(f"Error formatting training metrics: {e}")
|
||||||
return [html.P(f"Error: {str(e)}", className="text-danger small")]
|
return [html.P(f"Error: {str(e)}", className="text-danger small")]
|
||||||
|
|
||||||
|
def _format_cnn_pivot_prediction(self, model_info):
|
||||||
|
"""Format CNN pivot prediction for display"""
|
||||||
|
try:
|
||||||
|
pivot_prediction = model_info.get('pivot_prediction')
|
||||||
|
if not pivot_prediction:
|
||||||
|
return html.Div()
|
||||||
|
|
||||||
|
pivot_type = pivot_prediction.get('pivot_type', 'UNKNOWN')
|
||||||
|
predicted_price = pivot_prediction.get('predicted_price', 0)
|
||||||
|
confidence = pivot_prediction.get('confidence', 0)
|
||||||
|
time_horizon = pivot_prediction.get('time_horizon_minutes', 0)
|
||||||
|
|
||||||
|
# Color coding for pivot types
|
||||||
|
if 'RESISTANCE' in pivot_type:
|
||||||
|
pivot_color = "text-danger"
|
||||||
|
pivot_icon = "fas fa-arrow-up"
|
||||||
|
elif 'SUPPORT' in pivot_type:
|
||||||
|
pivot_color = "text-success"
|
||||||
|
pivot_icon = "fas fa-arrow-down"
|
||||||
|
else:
|
||||||
|
pivot_color = "text-warning"
|
||||||
|
pivot_icon = "fas fa-arrows-alt-h"
|
||||||
|
|
||||||
|
return html.Div([
|
||||||
|
html.Div([
|
||||||
|
html.I(className=f"{pivot_icon} me-1 {pivot_color}"),
|
||||||
|
html.Span("Next Pivot: ", className="text-muted small"),
|
||||||
|
html.Span(f"${predicted_price:.2f}", className=f"small fw-bold {pivot_color}")
|
||||||
|
], className="mb-1"),
|
||||||
|
html.Div([
|
||||||
|
html.Span(f"{pivot_type.replace('_', ' ')}", className=f"small {pivot_color}"),
|
||||||
|
html.Span(f" ({confidence:.0%}) in ~{time_horizon}m", className="text-muted small")
|
||||||
|
])
|
||||||
|
], className="mt-1 p-1", style={"backgroundColor": "rgba(255,255,255,0.02)", "borderRadius": "3px"})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Error formatting CNN pivot prediction: {e}")
|
||||||
|
return html.Div()
|
@ -51,7 +51,7 @@ class DashboardLayoutManager:
|
|||||||
return html.Div([
|
return html.Div([
|
||||||
self._create_metrics_and_signals_row(),
|
self._create_metrics_and_signals_row(),
|
||||||
self._create_charts_row(),
|
self._create_charts_row(),
|
||||||
self._create_analytics_and_performance_row()
|
self._create_cob_and_trades_row()
|
||||||
])
|
])
|
||||||
|
|
||||||
def _create_metrics_and_signals_row(self):
|
def _create_metrics_and_signals_row(self):
|
||||||
@ -199,6 +199,100 @@ class DashboardLayoutManager:
|
|||||||
], className="card")
|
], className="card")
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def _create_cob_and_trades_row(self):
|
||||||
|
"""Creates the row for COB ladders, closed trades, and model status."""
|
||||||
|
return html.Div(
|
||||||
|
[
|
||||||
|
# Left side: COB Ladders (60% width)
|
||||||
|
html.Div(
|
||||||
|
[
|
||||||
|
html.Div(
|
||||||
|
[
|
||||||
|
# ETH/USDT COB
|
||||||
|
html.Div(
|
||||||
|
[
|
||||||
|
html.Div(
|
||||||
|
id="eth-cob-content",
|
||||||
|
className="card-body p-2",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
className="card",
|
||||||
|
style={"flex": "1"},
|
||||||
|
),
|
||||||
|
# BTC/USDT COB
|
||||||
|
html.Div(
|
||||||
|
[
|
||||||
|
html.Div(
|
||||||
|
id="btc-cob-content",
|
||||||
|
className="card-body p-2",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
className="card",
|
||||||
|
style={"flex": "1", "marginLeft": "1rem"},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
className="d-flex",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
style={"width": "60%"},
|
||||||
|
),
|
||||||
|
# Right side: Trades and Model Status (40% width)
|
||||||
|
html.Div(
|
||||||
|
[
|
||||||
|
# Closed Trades
|
||||||
|
html.Div(
|
||||||
|
[
|
||||||
|
html.Div(
|
||||||
|
[
|
||||||
|
html.H6(
|
||||||
|
[
|
||||||
|
html.I(className="fas fa-history me-2"),
|
||||||
|
"Closed Trades",
|
||||||
|
],
|
||||||
|
className="card-title mb-2",
|
||||||
|
),
|
||||||
|
html.Div(
|
||||||
|
id="closed-trades-table",
|
||||||
|
style={"height": "250px", "overflowY": "auto"},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
className="card-body p-2",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
className="card mb-3",
|
||||||
|
),
|
||||||
|
# Model Status
|
||||||
|
html.Div(
|
||||||
|
[
|
||||||
|
html.Div(
|
||||||
|
[
|
||||||
|
html.H6(
|
||||||
|
[
|
||||||
|
html.I(className="fas fa-brain me-2"),
|
||||||
|
"Models & Training Progress",
|
||||||
|
],
|
||||||
|
className="card-title mb-2",
|
||||||
|
),
|
||||||
|
html.Div(
|
||||||
|
id="training-metrics",
|
||||||
|
style={
|
||||||
|
"height": "250px",
|
||||||
|
"overflowY": "auto",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
className="card-body p-2",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
className="card",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
style={"width": "38%", "marginLeft": "2%"},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
className="d-flex mb-3",
|
||||||
|
)
|
||||||
|
|
||||||
def _create_analytics_and_performance_row(self):
|
def _create_analytics_and_performance_row(self):
|
||||||
"""Create the combined analytics and performance row with COB data, trades, and training progress"""
|
"""Create the combined analytics and performance row with COB data, trades, and training progress"""
|
||||||
return html.Div([
|
return html.Div([
|
||||||
|
Reference in New Issue
Block a user