import gradio as gr import pandas as pd import numpy as np import plotly.graph_objects as go import os import warnings import datetime import traceback import shutil import tempfile # Disable Gradio queueing system (FIXES KeyError: 1 errors) gr.queue = False # Suppress warnings for cleaner output warnings.filterwarnings('ignore') # Performance optimization os.environ["OMP_NUM_THREADS"] = "1" os.environ["OPENBLAS_NUM_THREADS"] = "1" os.environ["MKL_NUM_THREADS"] = "1" # Define data directories RAW_DATA_DIR = "data/raw" PROCESSED_DATA_DIR = "data/processed" os.makedirs(PROCESSED_DATA_DIR, exist_ok=True) # Predefined trading pairs TRADING_PAIRS = { "EURUSD": { "description": "Euro to US Dollar Forex Pair", "date_format": "%d.%m.%Y %H:%M:%S.%f %z", "has_timezone": True, "decimal_separator": ".", "required_columns": ["Open", "High", "Low", "Close"] } } def preprocess_data_file(raw_file_path, pair_name): """Preprocess raw data file to standardized format""" print(f"๐Ÿ”„ Preprocessing data for {pair_name}...") try: # Read raw data df = pd.read_csv(raw_file_path, encoding='utf-8') print(f"โœ… Successfully read {pair_name} data with utf-8 encoding") # Standardize column names column_mapping = {} for col in df.columns: col_lower = col.lower().strip() if any(keyword in col_lower for keyword in ['date', 'time', 'timestamp']): column_mapping[col] = 'datetime' elif 'open' in col_lower: column_mapping[col] = 'Open' elif 'high' in col_lower: column_mapping[col] = 'High' elif 'low' in col_lower: column_mapping[col] = 'Low' elif 'close' in col_lower: column_mapping[col] = 'Close' elif 'volume' in col_lower: column_mapping[col] = 'Volume' if column_mapping: df.rename(columns=column_mapping, inplace=True) print(f"๐Ÿท๏ธ Standardized columns: {list(column_mapping.keys())} โ†’ {list(column_mapping.values())}") # Process datetime column datetime_col = None for col in ['datetime', 'date', 'time', 'timestamp']: if col in df.columns: datetime_col = col break if datetime_col is None: raise Exception("โŒ No datetime column found in data") # Handle EURUSD special format if pair_name == "EURUSD" and df[datetime_col].astype(str).str.contains('GMT').any(): print("๐Ÿ•— Handling EURUSD special datetime format...") df[datetime_col] = df[datetime_col].str.replace(' GMT', '', regex=False) df[datetime_col] = pd.to_datetime( df[datetime_col], format="%d.%m.%Y %H:%M:%S.%f %z", errors='coerce', utc=True ) else: df[datetime_col] = pd.to_datetime( df[datetime_col], errors='coerce', utc=True ) # Clean data before_count = len(df) df = df.dropna(subset=[datetime_col]) print(f"๐Ÿงน Removed {before_count - len(df)} rows with invalid dates") # Set datetime as index df.set_index(datetime_col, inplace=True) df.sort_index(inplace=True) # Fill missing values for col in ['Open', 'High', 'Low', 'Close']: if col in df.columns: missing_before = df[col].isna().sum() if missing_before > 0: df[col] = df[col].fillna(method='ffill').fillna(method='bfill') print(f" ๐Ÿ”„ Filled {missing_before} missing values in {col}") # Remove duplicates before_count = len(df) df = df[~df.index.duplicated(keep='first')] print(f"๐Ÿงน Removed {before_count - len(df)} duplicate entries") # Save preprocessed data processed_file = os.path.join(PROCESSED_DATA_DIR, f"{pair_name}_processed.csv") df.to_csv(processed_file) print(f"โœ… Saved preprocessed data to {processed_file}") return df except Exception as e: print(f"โŒ Preprocessing error for {pair_name}: {str(e)}") traceback.print_exc() return None def load_available_data(): """Load and preprocess all available data files""" global available_data available_data = {} # Check if raw data directory exists if not os.path.exists(RAW_DATA_DIR): print(f"โš ๏ธ Raw data directory not found: {RAW_DATA_DIR}") # Check if data is in root directory instead if os.path.exists("data") and os.path.isdir("data"): for filename in os.listdir("data"): if filename.endswith('.csv'): os.makedirs(RAW_DATA_DIR, exist_ok=True) shutil.move(os.path.join("data", filename), os.path.join(RAW_DATA_DIR, filename)) print(f"โœ… Moved {filename} to {RAW_DATA_DIR}") if not os.path.exists(RAW_DATA_DIR): print(f"โŒ Still cannot find raw data directory: {RAW_DATA_DIR}") return available_data print(f"๐Ÿ” Scanning for data files in {RAW_DATA_DIR}...") for filename in os.listdir(RAW_DATA_DIR): if filename.endswith('.csv'): pair_name = filename.split('.')[0].upper() if pair_name not in TRADING_PAIRS: TRADING_PAIRS[pair_name] = { "description": f"{pair_name} Trading Pair", "date_format": "%Y-%m-%d %H:%M:%S", "has_timezone": False, "decimal_separator": ".", "required_columns": ["Open", "High", "Low", "Close"] } raw_file_path = os.path.join(RAW_DATA_DIR, filename) processed_file_path = os.path.join(PROCESSED_DATA_DIR, f"{pair_name}_processed.csv") # Check for existing preprocessed file if os.path.exists(processed_file_path): try: df = pd.read_csv(processed_file_path, index_col=0, parse_dates=True) available_data[pair_name] = df print(f"โœ… Using existing preprocessed data for {pair_name} with {len(df)} records") continue except Exception as e: print(f"โš ๏ธ Error loading preprocessed file: {str(e)}. Reprocessing.") # Preprocess the file print(f"๐Ÿ”„ Processing {pair_name} data...") df = preprocess_data_file(raw_file_path, pair_name) if df is not None: available_data[pair_name] = df print(f"โœ… Successfully loaded {pair_name} with {len(df)} records") return available_data def get_available_pairs(): """Get list of available trading pairs with status""" if not available_data: return "โš ๏ธ No data files found. Please upload CSV files to the 'data/raw' directory." status = "โœ… Available trading pairs:\n" for pair in sorted(available_data.keys()): df = available_data[pair] records = len(df) if records > 0: date_range = f"{df.index.min().strftime('%Y-%m-%d')} to {df.index.max().strftime('%Y-%m-%d')}" status += f"โ€ข {pair}: {records} records ({date_range})\n" else: status += f"โ€ข {pair}: 0 records (Data Error)\n" return status def analyze_trading_pair(pair_name: str): """Analyze a specific trading pair""" pair_name = pair_name.upper().strip() print(f"\n๐Ÿ” Starting analysis for {pair_name}") # Error fallbacks default_error_fig = go.Figure().update_layout( title="Analysis Failed", xaxis_title="Date", yaxis_title="Price", template="plotly_white", height=500 ) default_error_df = gr.DataFrame( headers=["Error"], value=[["Analysis failed - check logs for details"]], interactive=False ) # Check if data is available if pair_name not in available_data: available_pairs = ", ".join(available_data.keys()) or "None" return ( f"โŒ Data not available for '{pair_name}'\nAvailable pairs: {available_pairs}", default_error_fig, default_error_fig, default_error_df ) try: hist = available_data[pair_name].copy() # Basic data validation required_cols = ['Open', 'High', 'Low', 'Close'] if not all(col in hist.columns for col in required_cols): missing_cols = [col for col in required_cols if col not in hist.columns] return ( f"โŒ Missing required columns: {', '.join(missing_cols)}\nAvailable columns: {', '.join(hist.columns)}", default_error_fig, default_error_fig, default_error_df ) # --- 1. Candlestick Chart with Technical Indicators (MAs) --- fig = go.Figure() # Add candlestick fig.add_trace(go.Candlestick( x=hist.index, open=hist['Open'], high=hist['High'], low=hist['Low'], close=hist['Close'], name='Price' )) # Add moving averages if len(hist) >= 20: hist['MA20'] = hist['Close'].rolling(window=20, min_periods=1).mean() fig.add_trace(go.Scatter( x=hist.index, y=hist['MA20'], mode='lines', name='20-period MA', line=dict(color='blue', width=1.5) )) if len(hist) >= 50: hist['MA50'] = hist['Close'].rolling(window=50, min_periods=1).mean() fig.add_trace(go.Scatter( x=hist.index, y=hist['MA50'], mode='lines', name='50-period MA', line=dict(color='orange', width=1.5) )) fig.update_layout( title=f"{pair_name} Price Analysis", xaxis_title="Date", yaxis_title="Price", template="plotly_white", hovermode="x unified", height=500, margin=dict(l=50, r=50, t=50, b=50) ) # --- 2. Simple Forecast (without Prophet to avoid import issues) --- forecast_fig = default_error_fig forecast_table = default_error_df forecast_result = "Forecast functionality will be available soon." try: # Simple linear forecast as fallback if len(hist) >= 30: # Take last 30 days recent_data = hist['Close'].tail(30) dates = recent_data.index # Create simple trend line x = np.arange(len(recent_data)) y = recent_data.values slope, intercept = np.polyfit(x, y, 1) # Create forecast data future_dates = [dates[-1] + datetime.timedelta(days=i) for i in range(1, 31)] future_values = [slope * (len(x) + i) + intercept for i in range(30)] # Create forecast chart forecast_fig = go.Figure() forecast_fig.add_trace(go.Scatter( x=dates, y=recent_data.values, mode='lines', name='Historical', line=dict(color='blue', width=2) )) forecast_fig.add_trace(go.Scatter( x=future_dates, y=future_values, mode='lines', name='Forecast', line=dict(color='red', width=2, dash='dash') )) forecast_fig.update_layout( title=f"{pair_name} 30-Day Price Forecast (Simple Trend)", xaxis_title="Date", yaxis_title="Price", template="plotly_white", height=500, hovermode="x unified" ) # Create forecast table table_data = [] for i, (date, value) in enumerate(zip(future_dates, future_values)): trend = "๐Ÿ“ˆ Rising" if slope > 0 else "๐Ÿ“‰ Falling" table_data.append([ date.strftime('%Y-%m-%d'), f"{value:.5f}", f"{value * 0.98:.5f}", f"{value * 1.02:.5f}", trend ]) forecast_table = gr.DataFrame( headers=["Date", "Predicted Price", "Lower Bound", "Upper Bound", "Trend"], value=table_data, datatype=["str", "str", "str", "str", "str"], label=f"{pair_name} 30-Day Price Forecast Table", interactive=False ) forecast_result = ( f"๐Ÿ”ฎ 30-Day Forecast (Simple Trend):\n" f"Projected price range based on recent trend" ) except Exception as e: print(f"โš ๏ธ Forecasting error: {str(e)}") forecast_result = f"โš ๏ธ Forecasting error: {str(e)}" # Technical analysis current_price = hist['Close'].iloc[-1] signal = "๐Ÿ“Š Analyzing market conditions..." if 'MA20' in hist.columns and 'MA50' in hist.columns: ma20 = hist['MA20'].iloc[-1] ma50 = hist['MA50'].iloc[-1] if current_price > ma20 > ma50: signal = "๐Ÿš€ STRONG BULLISH: Golden Cross pattern" elif current_price < ma20 < ma50: signal = "๐Ÿ’ฃ STRONG BEARISH: Death Cross pattern" elif current_price > ma20: signal = "๐Ÿ“ˆ BULLISH: Price above 20-period MA" else: signal = "๐Ÿ“‰ BEARISH: Price below 20-period MA" # Calculate performance metrics start_price = hist['Close'].iloc[0] total_return = (current_price / start_price - 1) * 100 volatility = hist['Close'].pct_change().std() * np.sqrt(252) * 100 # Create result text result_text = ( f"๐Ÿ“Š {pair_name} Analysis Report\n" f"{'=' * 40}\n" f"๐Ÿ’ฐ Current Price: {current_price:.5f}\n" f"๐Ÿ“ˆ Total Return: {total_return:.2f}%\n" f"โšก Volatility: {volatility:.2f}%\n" f"๐ŸŽฏ Signal: {signal}\n" f"{'=' * 40}\n" f"{forecast_result}" ) print(f"โœ… Analysis completed for {pair_name}") return result_text, fig, forecast_fig, forecast_table except Exception as e: error_msg = f"โŒ Analysis error: {str(e)}" print(error_msg) traceback.print_exc() return error_msg, default_error_fig, default_error_fig, default_error_df def export_forecast(pair_name): """Export forecast data to CSV file""" try: # Create a simple export file temp_dir = tempfile.mkdtemp() export_path = os.path.join(temp_dir, f"{pair_name}_forecast.csv") # Create dummy data for now dates = [datetime.datetime.now() + datetime.timedelta(days=i) for i in range(30)] prices = [1.0800 + i*0.0005 for i in range(30)] pd.DataFrame({ 'Date': [d.strftime('%Y-%m-%d') for d in dates], 'Predicted_Price': prices, 'Lower_Bound': [p * 0.998 for p in prices], 'Upper_Bound': [p * 1.002 for p in prices], 'Trend': ['Rising' if prices[i] > prices[i-1] else 'Falling' for i in range(30)] }).to_csv(export_path, index=False) return export_path except Exception as e: print(f"โŒ Export error: {str(e)}") return None def refresh_data(): """Refresh available data""" global available_data print("๐Ÿ”„ Refreshing data...") available_data = load_available_data() return get_available_pairs(), f"๐Ÿ“ˆ Trading Analysis System v2.4\n๐Ÿ•’ Last updated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}\n๐Ÿงฎ Loaded pairs: {len(available_data)}" # Load available data at startup print("๐Ÿš€ Initializing data processing system...") available_data = load_available_data() print(f"๐Ÿ“Š Available trading pairs: {list(available_data.keys())}") # Create Gradio interface with gr.Blocks(title="Trading Pair AI Analyzer") as demo: gr.Markdown("# ๐Ÿ“ˆ Trading Pair AI Analysis System") gr.Markdown("### Analyze forex data with interactive charts and forecasts") with gr.Row(): with gr.Column(scale=2): data_status = gr.Textbox( label="๐Ÿ“Š Available Data", value=get_available_pairs(), interactive=False, lines=5 ) with gr.Column(scale=1): gr.Markdown("### โ„น๏ธ System Information") system_info = gr.Textbox( value=f"๐Ÿ“ˆ Trading Analysis System v2.4\n๐Ÿ•’ Last updated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}\n๐Ÿงฎ Loaded pairs: {len(available_data)}", interactive=False, lines=3 ) refresh_btn = gr.Button("๐Ÿ”„ Refresh Data", variant="secondary") with gr.Row(): with gr.Column(scale=2): pair_input = gr.Textbox( label="๐Ÿ” Trading Pair to Analyze", value=list(available_data.keys())[0] if available_data else "EURUSD", placeholder="Enter pair name (e.g., EURUSD)" ) analyze_btn = gr.Button("๐Ÿš€ Analyze Pair", variant="primary") with gr.Column(scale=1): export_btn = gr.Button("๐Ÿ“ฅ Export Forecast Data", variant="secondary") export_output = gr.File(label="Download Forecast CSV", visible=False) result_output = gr.Textbox(label="๐Ÿ“ Analysis Results", lines=8) with gr.Tabs(): with gr.TabItem("๐Ÿ“ˆ Price Chart & Indicators"): price_chart = gr.Plot(label="Candlestick Chart with Moving Averages") with gr.TabItem("๐Ÿ”ฎ Price Forecast Chart"): forecast_chart = gr.Plot(label="30-Day Price Forecast") with gr.TabItem("๐Ÿ“‹ Forecast Table"): forecast_table = gr.DataFrame( headers=["Date", "Predicted Price", "Lower Bound", "Upper Bound", "Trend"], value=[], datatype=["str", "str", "str", "str", "str"], label="30-Day Price Forecast Table", interactive=False ) with gr.Accordion("๐Ÿ“ Data Upload Instructions", open=False): gr.Markdown(""" ### How to Add Your Own Data 1. **Prepare your CSV file** with these columns: - Date/Time column (any format) - Open, High, Low, Close prices - Volume (optional) 2. **Upload to Hugging Face Space**: - Go to your Space Files tab - Create directories: `data/raw/` - Upload your CSV files to `data/raw/` - Example filenames: `EURUSD.csv` 3. **Refresh the application**: - Click the "๐Ÿ”„ Refresh Data" button - Wait for data to load 4. **Your data will be automatically preprocessed** and ready for analysis! """) # Event handlers analyze_btn.click( fn=analyze_trading_pair, inputs=pair_input, outputs=[result_output, price_chart, forecast_chart, forecast_table] ) refresh_btn.click( fn=refresh_data, inputs=[], outputs=[data_status, system_info] ) export_btn.click( fn=export_forecast, inputs=pair_input, outputs=export_output ).then( fn=lambda: gr.update(visible=True), inputs=None, outputs=export_output ) # Launch the app if __name__ == "__main__": demo.launch( server_name="0.0.0.0", server_port=7860, share=False )