diff --git a/core/data_provider.py b/core/data_provider.py index 953ea4d..78991a5 100644 --- a/core/data_provider.py +++ b/core/data_provider.py @@ -4386,11 +4386,11 @@ class DataProvider: # Ensure symbol keys exist in the dictionary with proper deque initialization for sym in ['ETH/USDT', 'BTC/USDT']: if sym not in self.cob_raw_ticks: - # Use deque with maxlen for automatic size management (15 min at ~100 ticks/sec) - self.cob_raw_ticks[sym] = deque(maxlen=90000) + # Use deque with maxlen for automatic size management (30 min at ~100 ticks/sec) + self.cob_raw_ticks[sym] = deque(maxlen=180000) if sym not in self.cob_1s_aggregated: - # 1s aggregated: 15 minutes = 900 seconds - self.cob_1s_aggregated[sym] = deque(maxlen=900) + # 1s aggregated: 30 minutes = 1800 seconds + self.cob_1s_aggregated[sym] = deque(maxlen=1800) # Add to raw ticks - deque automatically handles size limit with maxlen self.cob_raw_ticks[symbol].append(cob_data) @@ -4452,7 +4452,7 @@ class DataProvider: elif not isinstance(self.cob_data_cache[symbol], (list, deque)): self.cob_data_cache[symbol] = [] self.cob_data_cache[symbol].append(standard_cob_data) - if len(self.cob_data_cache[symbol]) > 300: # Keep 5 minutes + if len(self.cob_data_cache[symbol]) > 1800: # Keep 30 minutes self.cob_data_cache[symbol].pop(0) # Notify subscribers diff --git a/utils/audit_plotter.py b/utils/audit_plotter.py index e8570f5..5b64561 100644 --- a/utils/audit_plotter.py +++ b/utils/audit_plotter.py @@ -341,10 +341,31 @@ def save_inference_audit_image(base_data, model_name: str, symbol: str, out_root ax6 = fig.add_subplot(gs[1, 2]) _plot_data_summary(ax6, base_data, symbol) - # COB data (bottom, spanning all columns) + # COB data (bottom, spanning all columns) with optional heatmap overlay above it ax7 = fig.add_subplot(gs[2, :]) _plot_cob_data(ax7, prices, bid_v, ask_v, imb, current_price, symbol) + # Optional: append a small heatmap figure to the side if available + try: + heat_times = getattr(base_data, 'cob_heatmap_times', []) + heat_prices = getattr(base_data, 'cob_heatmap_prices', []) + heat_vals = getattr(base_data, 'cob_heatmap_values', []) + if heat_times and heat_prices and heat_vals: + import numpy as np + # Create an inset axes on ax7 for compact heatmap + from mpl_toolkits.axes_grid1.inset_locator import inset_axes + inset_ax = inset_axes(ax7, width="25%", height="100%", loc='upper right', borderpad=1) + z = np.array(heat_vals, dtype=float) + if z.size > 0: + col_max = np.maximum(z.max(axis=0), 1e-9) + zn = (z / col_max).T + inset_ax.imshow(zn, aspect='auto', origin='lower', cmap='turbo') + inset_ax.set_title('COB Heatmap', fontsize=8) + inset_ax.set_xticks([]) + inset_ax.set_yticks([]) + except Exception as _hm_ex: + logger.debug(f"Audit heatmap overlay skipped: {_hm_ex}") + # Add overall title with model and timestamp info fig.suptitle(f"{model_name} - {safe_symbol} - {datetime.utcnow().strftime('%H:%M:%S')}", fontsize=14, fontweight='bold') diff --git a/web/clean_dashboard.py b/web/clean_dashboard.py index 6083c4a..6170a5b 100644 --- a/web/clean_dashboard.py +++ b/web/clean_dashboard.py @@ -1487,6 +1487,70 @@ class CleanTradingDashboard: fig.update_layout(margin=dict(l=10, r=10, t=20, b=10)) return fig + @self.app.callback( + Output('cob-heatmap-btc', 'figure'), + [Input('interval-component', 'n_intervals')] + ) + def update_cob_heatmap_btc(n): + try: + times, prices, matrix = [], [], [] + if hasattr(self.data_provider, 'get_cob_heatmap_matrix'): + times, prices, matrix, mids = self.data_provider.get_cob_heatmap_matrix( + 'BTC/USDT', seconds=300, bucket_radius=10, metric='liquidity' + ) + if not times or not prices or not matrix: + fig = go.Figure() + fig.add_annotation(text="No COB heatmap data", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False) + fig.update_layout(margin=dict(l=10, r=10, t=20, b=10)) + return fig + z = np.array(matrix, dtype=float) + col_max = np.maximum(z.max(axis=0), 1e-9) + zn = z / col_max + fig = go.Figure(data=go.Heatmap( + z=zn.T, + x=[t.strftime('%H:%M:%S') for t in times], + y=[f"{p:.2f}" for p in prices], + colorscale='Turbo', + colorbar=dict(title='Norm'), + zmin=0.0, + zmax=1.0 + )) + try: + bucket_size = abs(prices[1] - prices[0]) if len(prices) > 1 else 1.0 + price_line = mids if 'mids' in locals() and mids else [] + if price_line: + y_vals = [] + y_labels = [float(p) for p in prices] + for m in price_line: + if m and bucket_size > 0: + idx = int(round((m - y_labels[0]) / bucket_size)) + idx = max(0, min(len(y_labels) - 1, idx)) + y_vals.append(y_labels[idx]) + else: + y_vals.append(None) + fig.add_trace(go.Scatter( + x=[t.strftime('%H:%M:%S') for t in times], + y=[f"{yv:.2f}" if yv is not None else None for yv in y_vals], + mode='lines', + line=dict(color='white', width=1.5), + name='Mid Price' + )) + except Exception as _line_ex: + logger.debug(f"Price line overlay skipped: {_line_ex}") + fig.update_layout( + title="BTC COB Heatmap (liquidity, per-bucket normalized)", + xaxis_title="Time", + yaxis_title="Price", + margin=dict(l=10, r=10, t=30, b=10) + ) + return fig + except Exception as e: + logger.error(f"Error rendering BTC COB heatmap: {e}") + fig = go.Figure() + fig.add_annotation(text=f"Heatmap Error: {str(e)}", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False) + fig.update_layout(margin=dict(l=10, r=10, t=20, b=10)) + return fig + # Original training metrics callback - temporarily disabled for testing # @self.app.callback( # Output('training-metrics', 'children'), diff --git a/web/layout_manager.py b/web/layout_manager.py index b6d6c08..999ffcb 100644 --- a/web/layout_manager.py +++ b/web/layout_manager.py @@ -410,15 +410,18 @@ class DashboardLayoutManager: ], style={"width": "38%", "marginLeft": "2%"}), ], className="d-flex mb-3"), - # COB Heatmap (ETH) + # Mini COB Heatmaps (ETH and BTC) html.Div([ html.Div([ - html.H6([ - html.I(className="fas fa-fire me-2"), - "ETH/USDT COB Heatmap (last 5m, ±10 buckets)" - ], className="card-title mb-2"), - dcc.Graph(id='cob-heatmap-eth', config={'displayModeBar': False}, style={"height": "300px"}) - ], className="card-body p-2") + html.Div([ + html.H6([html.I(className="fas fa-fire me-2"), "ETH Heatmap"], className="card-title mb-2"), + dcc.Graph(id='cob-heatmap-eth', config={'displayModeBar': False}, style={"height": "220px"}) + ], className="card-body p-2", style={"flex": "1"}), + html.Div([ + html.H6([html.I(className="fas fa-fire me-2"), "BTC Heatmap"], className="card-title mb-2"), + dcc.Graph(id='cob-heatmap-btc', config={'displayModeBar': False}, style={"height": "220px"}) + ], className="card-body p-2", style={"flex": "1", "marginLeft": "1rem"}) + ], className="d-flex") ], className="card"), # Second row: Pending Orders (left) and Closed Trades (right)