wip COB data source - not ready

This commit is contained in:
Dobromir Popov
2025-08-08 00:49:13 +03:00
parent 0ce6e2691b
commit bd15bdc87d
7 changed files with 327 additions and 166 deletions

View File

@ -1300,7 +1300,7 @@ class CleanTradingDashboard:
"""Update COB data displays with real order book ladders and cumulative stats"""
try:
# COB data is critical for trading - keep at 2s interval
import time
eth_snapshot = self._get_cob_snapshot('ETH/USDT')
btc_snapshot = self._get_cob_snapshot('BTC/USDT')
@ -1352,8 +1352,47 @@ class CleanTradingDashboard:
if isinstance(btc_snapshot, list):
btc_snapshot = None
eth_components = self.component_manager.format_cob_data(eth_snapshot, 'ETH/USDT', eth_imbalance_stats, cob_mode)
btc_components = self.component_manager.format_cob_data(btc_snapshot, 'BTC/USDT', btc_imbalance_stats, cob_mode)
# Compute and display COB update rate and include recent aggregated views
def _calc_update_rate(symbol):
if not hasattr(self, 'cob_last_update'):
return "n/a"
last_ts = self.cob_last_update.get(symbol)
if not last_ts:
return "n/a"
age = time.time() - last_ts
if age <= 0:
return "n/a"
hz = 1.0 / age if age > 0 else 0
return f"{hz:.1f} Hz"
# Fetch aggregated 1s COB and recent ~0.2s ticks
def _recent_ticks(symbol):
if hasattr(self.data_provider, 'get_cob_raw_ticks'):
ticks = self.data_provider.get_cob_raw_ticks(symbol, count=25)
return ticks[-5:] if ticks else []
return []
eth_rate = _calc_update_rate('ETH/USDT')
btc_rate = _calc_update_rate('BTC/USDT')
eth_agg_1s = self.data_provider.get_cob_1s_aggregated('ETH/USDT') if hasattr(self.data_provider, 'get_cob_1s_aggregated') else []
btc_agg_1s = self.data_provider.get_cob_1s_aggregated('BTC/USDT') if hasattr(self.data_provider, 'get_cob_1s_aggregated') else []
eth_recent = _recent_ticks('ETH/USDT')
btc_recent = _recent_ticks('BTC/USDT')
eth_components = self.component_manager.format_cob_data(
eth_snapshot,
'ETH/USDT',
eth_imbalance_stats,
cob_mode,
update_info={'update_rate': eth_rate, 'aggregated_1s': eth_agg_1s[-5:], 'recent_ticks': eth_recent}
)
btc_components = self.component_manager.format_cob_data(
btc_snapshot,
'BTC/USDT',
btc_imbalance_stats,
cob_mode,
update_info={'update_rate': btc_rate, 'aggregated_1s': btc_agg_1s[-5:], 'recent_ticks': btc_recent}
)
return eth_components, btc_components
@ -3427,23 +3466,28 @@ class CleanTradingDashboard:
def _get_cob_mode(self) -> str:
"""Get current COB data collection mode"""
try:
# Check if data provider has WebSocket COB integration
if self.data_provider and hasattr(self.data_provider, 'cob_websocket'):
# Check WebSocket status
if hasattr(self.data_provider.cob_websocket, 'status'):
eth_status = self.data_provider.cob_websocket.status.get('ETH/USDT')
if eth_status and eth_status.connected:
return "WS" # WebSocket mode
# Check if we have recent WebSocket data
if hasattr(self.data_provider, 'cob_raw_ticks'):
eth_ticks = self.data_provider.cob_raw_ticks.get('ETH/USDT', [])
if eth_ticks:
import time
latest_tick = eth_ticks[-1]
tick_time = latest_tick.get('timestamp', 0)
if isinstance(tick_time, (int, float)) and (time.time() - tick_time) < 10:
return "WS" # Recent WebSocket data
# Determine WS mode using provider's enhanced websocket or raw tick recency
if self.data_provider:
# Preferred: enhanced websocket status summary
if hasattr(self.data_provider, 'get_cob_websocket_status'):
try:
status = self.data_provider.get_cob_websocket_status()
overall = status.get('overall_status') or status.get('status')
if overall in ("active", "ok", "ready"):
return "WS"
except Exception:
pass
# Fallback: raw ticks recency
if hasattr(self.data_provider, 'get_cob_raw_ticks'):
try:
ticks = self.data_provider.get_cob_raw_ticks('ETH/USDT', count=1)
if ticks:
import time
t = ticks[-1].get('timestamp', 0)
if isinstance(t, (int, float)) and (time.time() - t) < 5:
return "WS"
except Exception:
pass
# Check if we have any COB data (REST fallback)
if hasattr(self, 'latest_cob_data') and 'ETH/USDT' in self.latest_cob_data:
@ -7193,9 +7237,8 @@ class CleanTradingDashboard:
if hasattr(self.data_provider, 'get_base_data_input'):
return self.data_provider.get_base_data_input(symbol)
# Fallback: create BaseDataInput from available data
from core.data_models import BaseDataInput, OHLCVBar, COBData
import random
# Fallback: create BaseDataInput from available data (no synthetic data)
from core.data_models import BaseDataInput
# Get OHLCV data for different timeframes - ensure we have enough data
ohlcv_1s = self._get_ohlcv_bars(symbol, '1s', 300)
@ -7206,53 +7249,11 @@ class CleanTradingDashboard:
# Get BTC reference data
btc_ohlcv_1s = self._get_ohlcv_bars('BTC/USDT', '1s', 300)
# Ensure we have minimum required data (pad if necessary)
def pad_ohlcv_data(bars, target_count=300):
if len(bars) < target_count:
# Pad with realistic variation instead of identical bars
if len(bars) > 0:
last_bar = bars[-1]
# Add small random variation to prevent identical data
for i in range(target_count - len(bars)):
# Create slight variations of the last bar
variation = random.uniform(-0.001, 0.001) # 0.1% variation
new_bar = OHLCVBar(
symbol=last_bar.symbol,
timestamp=last_bar.timestamp + timedelta(seconds=i),
open=last_bar.open * (1 + variation),
high=last_bar.high * (1 + variation),
low=last_bar.low * (1 + variation),
close=last_bar.close * (1 + variation),
volume=last_bar.volume * (1 + random.uniform(-0.1, 0.1)),
timeframe=last_bar.timeframe
)
bars.append(new_bar)
else:
# Create realistic dummy bars with variation
base_price = 3500.0
for i in range(target_count):
# Add realistic price movement
price_change = random.uniform(-0.02, 0.02) # 2% max change
current_price = base_price * (1 + price_change)
dummy_bar = OHLCVBar(
symbol=symbol,
timestamp=datetime.now() - timedelta(seconds=target_count-i),
open=current_price * random.uniform(0.998, 1.002),
high=current_price * random.uniform(1.000, 1.005),
low=current_price * random.uniform(0.995, 1.000),
close=current_price,
volume=random.uniform(500.0, 2000.0),
timeframe="1s"
)
bars.append(dummy_bar)
return bars[:target_count] # Ensure exactly target_count
# Pad all data to required length
ohlcv_1s = pad_ohlcv_data(ohlcv_1s, 300)
ohlcv_1m = pad_ohlcv_data(ohlcv_1m, 300)
ohlcv_1h = pad_ohlcv_data(ohlcv_1h, 300)
ohlcv_1d = pad_ohlcv_data(ohlcv_1d, 300)
btc_ohlcv_1s = pad_ohlcv_data(btc_ohlcv_1s, 300)
# Strictly require sufficient real data; otherwise return None
datasets = [ohlcv_1s, ohlcv_1m, ohlcv_1h, ohlcv_1d, btc_ohlcv_1s]
if any(len(ds) < 100 for ds in datasets):
logger.warning(f"Insufficient real OHLCV data for {symbol}; skipping BaseDataInput fallback")
return None
logger.debug(f"OHLCV data lengths: 1s={len(ohlcv_1s)}, 1m={len(ohlcv_1m)}, 1h={len(ohlcv_1h)}, 1d={len(ohlcv_1d)}, BTC={len(btc_ohlcv_1s)}")

View File

@ -296,14 +296,17 @@ 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, cumulative_imbalance_stats=None, cob_mode="Unknown"):
"""Format COB data into a split view with summary, imbalance stats, and a compact ladder."""
def format_cob_data(self, cob_snapshot, symbol, cumulative_imbalance_stats=None, cob_mode="Unknown", update_info: dict = None):
"""Format COB data into a split view with summary, imbalance stats, and a compact ladder.
update_info can include keys: 'update_rate', 'aggregated_1s', 'recent_ticks'.
"""
try:
if not cob_snapshot:
return html.Div([
html.H6(f"{symbol} COB", className="mb-2"),
html.P("No COB data available", className="text-muted small"),
html.P(f"Mode: {cob_mode}", className="text-muted small")
html.P(f"Mode: {cob_mode}", className="text-muted small"),
html.P(f"Update: {(update_info or {}).get('update_rate', 'n/a')}", className="text-muted small")
])
# Defensive: If cob_snapshot is a list, log and return error
@ -312,7 +315,8 @@ class DashboardComponentManager:
return html.Div([
html.H6(f"{symbol} COB", className="mb-2"),
html.P("Invalid COB data format (list)", className="text-danger small"),
html.P(f"Mode: {cob_mode}", className="text-muted small")
html.P(f"Mode: {cob_mode}", className="text-muted small"),
html.P(f"Update: {(update_info or {}).get('update_rate', 'n/a')}", className="text-muted small")
])
# Debug: Log the type and structure of cob_snapshot
@ -347,7 +351,9 @@ class DashboardComponentManager:
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")
html.P("Awaiting valid order book data...", className="text-muted small"),
html.P(f"Mode: {cob_mode}", className="text-muted small"),
html.P(f"Update: {(update_info or {}).get('update_rate', 'n/a')}", className="text-muted small")
])
# Create stats dict for compatibility with existing code
@ -362,15 +368,38 @@ class DashboardComponentManager:
}
# --- Left Panel: Overview and Stats ---
# Prepend update info to overview
overview_panel = self._create_cob_overview_panel(symbol, stats, cumulative_imbalance_stats, cob_mode)
if update_info and update_info.get('update_rate'):
# Wrap with a small header line for update rate
overview_panel = html.Div([
html.Div(html.Small(f"Update: {update_info['update_rate']}", className="text-muted"), className="mb-1"),
overview_panel
])
# --- Right Panel: Compact Ladder ---
ladder_panel = self._create_cob_ladder_panel(bids, asks, mid_price, symbol)
# Append small extras line from aggregated_1s and recent_ticks
extras = []
if update_info:
agg = (update_info.get('aggregated_1s') or [])
if agg and isinstance(agg[-1], dict):
last = agg[-1]
avg_spread = last.get('spread', {}).get('average_bps', 0)
avg_imb = last.get('imbalance', {}).get('average', 0)
tick_count = last.get('tick_count', 0)
extras.append(html.Small(f"1s agg: {tick_count} ticks, spread {avg_spread:.1f} bps, imb {avg_imb:.2f}", className="text-muted"))
recent = (update_info.get('recent_ticks') or [])
if recent:
extras.append(html.Small(f"Recent ticks: {len(recent)}", className="text-muted ms-2"))
extras_div = html.Div(extras, className="mb-1") if extras else None
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
children = [dbc.Col(overview_panel, width=5, className="pe-1")]
right_children = [ladder_panel]
if extras_div:
right_children.insert(0, extras_div)
children.append(dbc.Col(html.Div(right_children), width=7, className="ps-1"))
return dbc.Row(children, className="g-0") # g-0 removes gutters
except Exception as e:
logger.error(f"Error formatting split COB data: {e}")