diff --git a/realtime.py b/realtime.py index c60fc3a..7d55211 100644 --- a/realtime.py +++ b/realtime.py @@ -17,6 +17,8 @@ from threading import Thread import requests import os from datetime import datetime, timedelta +import pytz +import tzlocal # Configure logging with more detailed format logging.basicConfig( @@ -29,6 +31,50 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) +# Try to get local timezone, default to Sofia/EET if not available +try: + local_timezone = tzlocal.get_localzone() + # Get timezone name safely + try: + tz_name = str(local_timezone) + # Handle case where it might be zoneinfo.ZoneInfo object instead of pytz timezone + if hasattr(local_timezone, 'zone'): + tz_name = local_timezone.zone + elif hasattr(local_timezone, 'key'): + tz_name = local_timezone.key + else: + tz_name = str(local_timezone) + except: + tz_name = "Local" + logger.info(f"Detected local timezone: {local_timezone} ({tz_name})") +except Exception as e: + logger.warning(f"Could not detect local timezone: {str(e)}. Defaulting to Sofia/EET") + local_timezone = pytz.timezone('Europe/Sofia') + tz_name = "Europe/Sofia" + +def convert_to_local_time(timestamp): + """Convert timestamp to local timezone""" + try: + if isinstance(timestamp, pd.Timestamp): + dt = timestamp.to_pydatetime() + elif isinstance(timestamp, np.datetime64): + dt = pd.Timestamp(timestamp).to_pydatetime() + elif isinstance(timestamp, str): + dt = pd.to_datetime(timestamp).to_pydatetime() + else: + dt = timestamp + + # If datetime is naive (no timezone), assume it's UTC + if dt.tzinfo is None: + dt = dt.replace(tzinfo=pytz.UTC) + + # Convert to local timezone + local_dt = dt.astimezone(local_timezone) + return local_dt + except Exception as e: + logger.error(f"Error converting timestamp to local time: {str(e)}") + return timestamp + class TradeTickStorage: """Store and manage raw trade ticks for display and candle formation""" def __init__(self, max_age_seconds: int = 1800): # 30 minutes by default @@ -1110,10 +1156,19 @@ class RealTimeChart: logger.debug(f"Displaying {len(display_df)} candles in main chart") + # Convert timestamps to local time for display + display_df = display_df.copy() + try: + display_df['local_time'] = display_df['timestamp'].apply(convert_to_local_time) + logger.debug(f"Converted timestamps to local time ({local_timezone})") + except Exception as e: + logger.error(f"Error converting timestamps to local time: {str(e)}") + display_df['local_time'] = display_df['timestamp'] + # Add candlestick chart fig.add_trace( go.Candlestick( - x=display_df['timestamp'], + x=display_df['local_time'], # Use local time for display open=display_df['open'], high=display_df['high'], low=display_df['low'], @@ -1131,7 +1186,7 @@ class RealTimeChart: fig.add_trace( go.Bar( - x=display_df['timestamp'], + x=display_df['local_time'], # Use local time for display y=display_df['volume'], name='Volume', marker_color=colors @@ -1141,21 +1196,30 @@ class RealTimeChart: # Add latest price line from the candlestick data latest_price = display_df['close'].iloc[-1] + latest_time = display_df['local_time'].iloc[-1] + earliest_time = display_df['local_time'].iloc[0] + fig.add_shape( type="line", - x0=display_df['timestamp'].min(), + x0=earliest_time, y0=latest_price, - x1=display_df['timestamp'].max(), + x1=latest_time, y1=latest_price, line=dict(color="yellow", width=1, dash="dash"), row=1, col=1 ) + # Format the last update time in local timezone + try: + last_update_time = latest_time.strftime('%Y-%m-%d %H:%M:%S') + except: + last_update_time = datetime.now().astimezone(local_timezone).strftime('%Y-%m-%d %H:%M:%S') + # Annotation for last candle close price fig.add_annotation( - x=display_df['timestamp'].max(), + x=latest_time, y=latest_price, - text=f"{latest_price:.2f}", + text=f"{latest_price:.2f} ({last_update_time})", showarrow=False, font=dict(size=14, color="yellow"), xshift=50, @@ -1167,19 +1231,22 @@ class RealTimeChart: # Add current price line fig.add_shape( type="line", - x0=display_df['timestamp'].min(), + x0=earliest_time, y0=current_price, - x1=display_df['timestamp'].max(), + x1=latest_time, y1=current_price, line=dict(color="cyan", width=1, dash="dot"), row=1, col=1 ) + # Format current time in local timezone + current_time_str = datetime.now().astimezone(local_timezone).strftime('%Y-%m-%d %H:%M:%S') + # Add current price annotation fig.add_annotation( - x=display_df['timestamp'].max(), + x=latest_time, y=current_price, - text=f"Current: {current_price:.2f}", + text=f"Current: {current_price:.2f} ({current_time_str})", showarrow=False, font=dict(size=14, color="cyan"), xshift=50, @@ -1221,13 +1288,21 @@ class RealTimeChart: if len(ohlcv_df) > 100: ohlcv_df = ohlcv_df.tail(100) + # Convert to local time + ohlcv_display_df = ohlcv_df.copy() + try: + ohlcv_display_df['local_time'] = ohlcv_df['timestamp'].apply(convert_to_local_time) + except Exception as e: + logger.error(f"Error converting {interval_key} timestamps to local time: {str(e)}") + ohlcv_display_df['local_time'] = ohlcv_df['timestamp'] + fig.add_trace( go.Candlestick( - x=ohlcv_df['timestamp'], - open=ohlcv_df['open'], - high=ohlcv_df['high'], - low=ohlcv_df['low'], - close=ohlcv_df['close'], + x=ohlcv_display_df['local_time'], + open=ohlcv_display_df['open'], + high=ohlcv_display_df['high'], + low=ohlcv_display_df['low'], + close=ohlcv_display_df['close'], name=f'{label}', increasing_line_color='#33CC33', decreasing_line_color='#FF4136', @@ -1237,23 +1312,33 @@ class RealTimeChart: ) # Add latest price line - latest_timeframe_price = ohlcv_df['close'].iloc[-1] if len(ohlcv_df) > 0 else None + latest_timeframe_price = ohlcv_display_df['close'].iloc[-1] if len(ohlcv_display_df) > 0 else None if latest_timeframe_price: + # Get first and last timestamps + earliest_tf_time = ohlcv_display_df['local_time'].iloc[0] + latest_tf_time = ohlcv_display_df['local_time'].iloc[-1] + fig.add_shape( type="line", - x0=ohlcv_df['timestamp'].min(), + x0=earliest_tf_time, y0=latest_timeframe_price, - x1=ohlcv_df['timestamp'].max(), + x1=latest_tf_time, y1=latest_timeframe_price, line=dict(color="yellow", width=1, dash="dash"), row=row_idx, col=1 ) + # Get formatted time + try: + update_time = latest_tf_time.strftime('%Y-%m-%d %H:%M') + except: + update_time = "" + # Add price annotation fig.add_annotation( - x=ohlcv_df['timestamp'].max(), + x=latest_tf_time, y=latest_timeframe_price, - text=f"{latest_timeframe_price:.2f}", + text=f"{latest_timeframe_price:.2f} ({update_time})", showarrow=False, font=dict(size=12, color="yellow"), xshift=50, @@ -1279,13 +1364,21 @@ class RealTimeChart: if len(ohlcv_df) > 100: ohlcv_df = ohlcv_df.tail(100) + # Convert to local time + ohlcv_display_df = ohlcv_df.copy() + try: + ohlcv_display_df['local_time'] = ohlcv_df['timestamp'].apply(convert_to_local_time) + except Exception as e: + logger.error(f"Error converting {interval_key} timestamps to local time: {str(e)}") + ohlcv_display_df['local_time'] = ohlcv_df['timestamp'] + fig.add_trace( go.Candlestick( - x=ohlcv_df['timestamp'], - open=ohlcv_df['open'], - high=ohlcv_df['high'], - low=ohlcv_df['low'], - close=ohlcv_df['close'], + x=ohlcv_display_df['local_time'], + open=ohlcv_display_df['open'], + high=ohlcv_display_df['high'], + low=ohlcv_display_df['low'], + close=ohlcv_display_df['close'], name=f'{label}', increasing_line_color='#33CC33', decreasing_line_color='#FF4136', @@ -1293,6 +1386,40 @@ class RealTimeChart: ), row=row_idx, col=1 ) + + # Add latest price line + latest_timeframe_price = ohlcv_display_df['close'].iloc[-1] if len(ohlcv_display_df) > 0 else None + if latest_timeframe_price: + # Get first and last timestamps + earliest_tf_time = ohlcv_display_df['local_time'].iloc[0] + latest_tf_time = ohlcv_display_df['local_time'].iloc[-1] + + fig.add_shape( + type="line", + x0=earliest_tf_time, + y0=latest_timeframe_price, + x1=latest_tf_time, + y1=latest_timeframe_price, + line=dict(color="yellow", width=1, dash="dash"), + row=row_idx, col=1 + ) + + # Get formatted time + try: + update_time = latest_tf_time.strftime('%Y-%m-%d %H:%M') + except: + update_time = "" + + # Add price annotation + fig.add_annotation( + x=latest_tf_time, + y=latest_timeframe_price, + text=f"{latest_timeframe_price:.2f} ({update_time})", + showarrow=False, + font=dict(size=12, color="yellow"), + xshift=50, + row=row_idx, col=1 + ) # Add a message to the empty main chart fig.add_annotation( @@ -1317,6 +1444,10 @@ class RealTimeChart: # Build info box text with all the statistics info_lines = [f"{self.symbol}"] + # Add current time in local timezone + current_local_time = datetime.now().astimezone(local_timezone) + info_lines.append(f"Time: {current_local_time.strftime('%Y-%m-%d %H:%M:%S')} ({tz_name})") + # Add current price if available if current_price: info_lines.append(f"Current: {current_price:.2f} USDT")