diff --git a/components/charts/utils.py b/components/charts/utils.py index a4d4996..2dd2ee2 100644 --- a/components/charts/utils.py +++ b/components/charts/utils.py @@ -9,6 +9,7 @@ import pandas as pd from datetime import datetime, timezone from typing import List, Dict, Any, Optional, Union from decimal import Decimal +from tzlocal import get_localzone from utils.logger import get_logger @@ -114,10 +115,21 @@ def prepare_chart_data(candles: List[Dict[str, Any]]) -> pd.DataFrame: # Convert to DataFrame df = pd.DataFrame(candles) - # Ensure timestamp is datetime + # Ensure timestamp is datetime and localized to system time if 'timestamp' in df.columns: df['timestamp'] = pd.to_datetime(df['timestamp']) - + local_tz = get_localzone() + + # Check if the timestamps are already timezone-aware + if df['timestamp'].dt.tz is not None: + # If they are, just convert to the local timezone + df['timestamp'] = df['timestamp'].dt.tz_convert(local_tz) + logger.debug(f"Converted timezone-aware timestamps to local timezone: {local_tz}") + else: + # If they are naive, localize to UTC first, then convert + df['timestamp'] = df['timestamp'].dt.tz_localize('UTC').dt.tz_convert(local_tz) + logger.debug(f"Localized naive timestamps to UTC and converted to local timezone: {local_tz}") + # Convert OHLCV columns to numeric numeric_columns = ['open', 'high', 'low', 'close'] if 'volume' in df.columns: diff --git a/dashboard/callbacks/charts.py b/dashboard/callbacks/charts.py index e6c3f09..14a5889 100644 --- a/dashboard/callbacks/charts.py +++ b/dashboard/callbacks/charts.py @@ -103,6 +103,11 @@ def register_chart_callbacks(app): triggered_id = ctx.triggered_id logger.debug(f"Update_price_chart triggered by: {triggered_id}") + # If the update is from the interval and the chart is locked, do nothing. + if triggered_id == 'interval-component' and analysis_mode == 'locked': + logger.debug("Analysis mode is 'locked'. Skipping interval-based chart update.") + return no_update, no_update + days_back, status_message = calculate_time_range( time_range_quick, custom_start_date, custom_end_date, analysis_mode, n_intervals ) @@ -208,6 +213,21 @@ def register_chart_callbacks(app): error_status = f"❌ Error: {str(e)}" return error_fig, error_status + @app.callback( + Output('analysis-mode-toggle', 'value'), + Input('price-chart', 'relayoutData'), + State('analysis-mode-toggle', 'value'), + prevent_initial_call=True + ) + def auto_lock_chart_on_interaction(relayout_data, current_mode): + """Automatically switch to 'locked' mode when the user zooms or pans.""" + # relayout_data is triggered by zoom/pan actions. + if relayout_data and 'xaxis.range' in relayout_data: + if current_mode != 'locked': + logger.debug("User chart interaction detected (zoom/pan). Switching to 'locked' analysis mode.") + return 'locked' + return no_update + # Strategy selection callback - automatically load strategy indicators @app.callback( [Output('overlay-indicators-checklist', 'value'),