diff --git a/realtime.py b/realtime.py index c6744f1..6aa908c 100644 --- a/realtime.py +++ b/realtime.py @@ -1321,6 +1321,16 @@ class RealTimeChart: total_volume = sum(volumes) avg_volume = total_volume / len(volumes) if volumes else 0 + # Calculate additional volume stats + max_volume = max(volumes) if volumes else 0 + min_volume = min(volumes) if volumes else 0 + median_volume = sorted(volumes)[len(volumes)//2] if volumes else 0 + + # Calculate trade value in USDT + trade_values = [p * v for p, v in zip(prices, volumes)] + total_value = sum(trade_values) + avg_trade_value = total_value / len(trade_values) if trade_values else 0 + first_timestamp = filtered_ticks[0]['timestamp'] last_timestamp = filtered_ticks[-1]['timestamp'] time_span_ms = last_timestamp - first_timestamp @@ -1383,6 +1393,18 @@ class RealTimeChart: html.Div("Total Volume", style=label_style), html.Div(f"{total_volume:.8f}", style=value_style), html.Div(f"Avg: {avg_volume:.8f}", style=label_style) + ], style=card_style), + + html.Div([ + html.Div("Max Volume", style=label_style), + html.Div(f"{max_volume:.8f}", style=value_style), + html.Div("Median: {:.8f}".format(median_volume), style=label_style) + ], style=card_style), + + html.Div([ + html.Div("Total Value", style=label_style), + html.Div(f"{total_value:.2f}", style=value_style), + html.Div(f"Avg: {avg_trade_value:.2f}", style=label_style) ], style=card_style) ] @@ -1499,8 +1521,14 @@ class RealTimeChart: # Get filtered ticks filtered_ticks = self.tick_storage.get_ticks_from_time(start_time_ms=start_time) - # Create figure - fig = go.Figure() + # Create figure with 2 subplots - price and volume + fig = make_subplots( + rows=2, cols=1, + shared_xaxes=True, + vertical_spacing=0.03, + row_heights=[0.7, 0.3], + subplot_titles=(f"Price Movement (Last {window_seconds // 60} minutes)", "Volume") + ) if not filtered_ticks: fig.add_annotation( @@ -1516,31 +1544,101 @@ class RealTimeChart: prices = [tick['price'] for tick in filtered_ticks] volumes = [tick.get('volume', 0) for tick in filtered_ticks] - # Scale volumes for better visibility - max_volume = max(volumes) if volumes else 1 - scaled_volumes = [vol * (max(prices) - min(prices)) / max_volume * 0.2 + min(prices) for vol in volumes] + # Add price scatter plot + fig.add_trace( + go.Scatter( + x=timestamps, + y=prices, + mode='lines', + name='Price', + line=dict(color='#4CAF50', width=1.5) + ), + row=1, col=1 + ) - # Add price line - fig.add_trace(go.Scatter( - x=timestamps, - y=prices, - mode='lines', - name='Price', - line=dict(color='#4CAF50', width=1.5) - )) + # Create a volume profile on the right side of the price chart + if len(prices) > 5: # Only create profile if we have enough data + # Group prices into bins + price_min = min(prices) + price_max = max(prices) + # Create approximately 20 bins based on price range + bin_size = max(0.01, (price_max - price_min) / 20) + + # Create a dictionary to hold volume by price level + volume_by_price = {} + + # Group volumes by price bins + for p, v in zip(prices, volumes): + bin_key = round(p / bin_size) * bin_size + if bin_key in volume_by_price: + volume_by_price[bin_key] += v + else: + volume_by_price[bin_key] = v + + # Sort by price level + sorted_bins = sorted(volume_by_price.items()) + profile_prices = [p for p, _ in sorted_bins] + profile_volumes = [v for _, v in sorted_bins] + + # Add separate volume profile trace + fig.add_trace( + go.Bar( + y=profile_prices, + x=profile_volumes, + orientation='h', + name='Volume Profile', + marker=dict( + color=['#33CC33' if p <= latest_price else '#FF4136' for p in profile_prices], + opacity=0.5 + ), + showlegend=True, + hovertemplate='Price: %{y:.2f}
Volume: %{x:.8f}' + ), + row=1, col=1 + ) + + # Add a line marking the latest price + fig.add_shape( + type="line", + y0=latest_price, y1=latest_price, + x0=0, x1=max(profile_volumes) * 1.1, + line=dict(color="yellow", width=1, dash="dash"), + row=1, col=1 + ) - # Add volume bars - fig.add_trace(go.Bar( - x=timestamps, - y=scaled_volumes, - name='Volume', - marker=dict(color='rgba(128, 128, 255, 0.3)'), - opacity=0.5, - yaxis='y2' - )) + # Add volume bars in separate subplot + # Color volume bars green for price increases, red for decreases + if len(timestamps) > 1: + # Compare each price with the previous to determine color + colors = [] + for i in range(len(prices)): + if i == 0: + colors.append('#33CC33') # Default to green for first tick + else: + if prices[i] >= prices[i-1]: + colors.append('#33CC33') # Green for price increase/same + else: + colors.append('#FF4136') # Red for price decrease + else: + colors = ['#33CC33'] # Default green if only one tick + + fig.add_trace( + go.Bar( + x=timestamps, + y=volumes, + name='Volume', + marker=dict(color=colors) + ), + row=2, col=1 + ) + + # Compute stats for annotations + latest_price = prices[-1] if prices else 0 + total_volume = sum(volumes) + max_volume = max(volumes) if volumes else 0 + avg_volume = total_volume / len(volumes) if volumes else 0 # Add annotations for latest price - latest_price = prices[-1] if prices else 0 fig.add_annotation( x=timestamps[-1] if timestamps else 0, y=latest_price, @@ -1551,41 +1649,63 @@ class RealTimeChart: arrowwidth=2, arrowcolor="#4CAF50", font=dict(size=12, color="#4CAF50"), - xshift=50 + xshift=50, + row=1, col=1 + ) + + # Add annotations for volume stats + fig.add_annotation( + x=timestamps[-1] if timestamps else 0, + y=max_volume, + text=f"Max: {max_volume:.8f}", + showarrow=False, + font=dict(size=10, color="rgba(128, 128, 255, 1)"), + xshift=50, + row=2, col=1 ) # Update layout fig.update_layout( - title=f"{self.symbol} Price Movement (Last {window_seconds // 60} minutes)", + title_text=f"{self.symbol} Tick Data", title_x=0.5, - xaxis=dict( - title="Time", - showgrid=True, - gridcolor='rgba(128,128,128,0.2)' - ), - yaxis=dict( - title="Price (USDT)", - showgrid=True, - gridcolor='rgba(128,128,128,0.2)' - ), - yaxis2=dict( - title="Volume", - overlaying='y', - side='right', - showgrid=False, - showticklabels=False - ), template='plotly_dark', paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(25,25,50,1)', - height=500, - margin=dict(l=40, r=40, t=50, b=40), + height=600, # Increased height for better visualization + margin=dict(l=40, r=40, t=80, b=40), legend=dict( - yanchor="top", - y=0.99, - xanchor="left", - x=0.01 - ) + orientation="h", + yanchor="bottom", + y=1.02, + xanchor="right", + x=1 + ), + hovermode="x unified" # Show all data points at the same x-coordinate + ) + + # Update x-axis to be shared + fig.update_xaxes( + showgrid=True, + gridwidth=1, + gridcolor='rgba(128,128,128,0.2)', + rangeslider_visible=False + ) + + # Update y-axes + fig.update_yaxes( + title_text="Price (USDT)", + showgrid=True, + gridwidth=1, + gridcolor='rgba(128,128,128,0.2)', + row=1, col=1 + ) + + fig.update_yaxes( + title_text="Volume", + showgrid=True, + gridwidth=1, + gridcolor='rgba(128,128,128,0.2)', + row=2, col=1 ) return fig