Smart-Trader-EA commited on
Commit
9333e4d
ยท
1 Parent(s): 9df3483

Fix Gradio compatibility issue

Browse files
Files changed (1) hide show
  1. app.py +298 -314
app.py CHANGED
@@ -5,9 +5,15 @@ import plotly.graph_objects as go
5
  from prophet import Prophet
6
  import os
7
  import warnings
 
 
 
 
 
8
  warnings.filterwarnings('ignore')
9
 
10
- # Performance optimization for Apple Silicon
 
11
  os.environ["OMP_NUM_THREADS"] = "1"
12
  os.environ["OPENBLAS_NUM_THREADS"] = "1"
13
  os.environ["MKL_NUM_THREADS"] = "1"
@@ -17,24 +23,26 @@ RAW_DATA_DIR = "data/raw"
17
  PROCESSED_DATA_DIR = "data/processed"
18
  os.makedirs(PROCESSED_DATA_DIR, exist_ok=True)
19
 
20
- # Predefined trading pairs with expected formats
 
21
  TRADING_PAIRS = {
 
22
  "EURUSD": {
23
- "description": "Euro to US Dollar Forex Pair",
24
  "date_format": "%d.%m.%Y %H:%M:%S.%f %z",
25
  "has_timezone": True,
26
  "decimal_separator": ".",
27
  "required_columns": ["Open", "High", "Low", "Close"]
28
  },
29
  "BTCUSD": {
30
- "description": "Bitcoin to US Dollar",
31
  "date_format": "%Y-%m-%d %H:%M:%S",
32
  "has_timezone": False,
33
  "decimal_separator": ".",
34
  "required_columns": ["Open", "High", "Low", "Close"]
35
  },
36
  "AAPL": {
37
- "description": "Apple Inc. Stock",
38
  "date_format": "%Y-%m-%d",
39
  "has_timezone": False,
40
  "decimal_separator": ".",
@@ -42,35 +50,44 @@ TRADING_PAIRS = {
42
  }
43
  }
44
 
 
 
45
  def preprocess_data_file(raw_file_path, pair_name):
46
  """Preprocess raw data file to standardized format"""
47
  print(f"๐Ÿ”„ Preprocessing data for {pair_name}...")
48
 
 
 
 
 
 
 
 
 
 
49
  try:
50
- # Get pair configuration
51
- config = TRADING_PAIRS.get(pair_name, TRADING_PAIRS["EURUSD"])
52
-
53
- # Read raw data with proper encoding
54
  encodings = ['utf-8', 'latin1', 'ISO-8859-1', 'cp1252']
55
  df = None
56
 
57
  for encoding in encodings:
58
  try:
59
- df = pd.read_csv(raw_file_path, encoding=encoding)
 
60
  print(f"โœ… Successfully read {pair_name} data with {encoding} encoding")
61
  break
62
  except (UnicodeDecodeError, pd.errors.ParserError):
63
  continue
64
 
65
  if df is None:
66
- raise Exception(f"โŒ Failed to read {pair_name} data with any encoding")
67
 
68
  # Standardize column names (case-insensitive)
69
  column_mapping = {}
70
  for col in df.columns:
71
- col_lower = col.lower()
72
 
73
- if any(keyword in col_lower for keyword in ['date', 'time', 'timestamp']):
74
  column_mapping[col] = 'datetime'
75
  elif 'open' in col_lower:
76
  column_mapping[col] = 'Open'
@@ -78,9 +95,9 @@ def preprocess_data_file(raw_file_path, pair_name):
78
  column_mapping[col] = 'High'
79
  elif 'low' in col_lower:
80
  column_mapping[col] = 'Low'
81
- elif 'close' in col_lower:
82
  column_mapping[col] = 'Close'
83
- elif 'volume' in col_lower:
84
  column_mapping[col] = 'Volume'
85
 
86
  if column_mapping:
@@ -89,33 +106,30 @@ def preprocess_data_file(raw_file_path, pair_name):
89
 
90
  # Process datetime column
91
  datetime_col = None
92
- for col in ['datetime', 'date', 'time', 'timestamp']:
93
  if col in df.columns:
94
  datetime_col = col
95
  break
96
 
97
  if datetime_col is None:
98
- raise Exception("โŒ No datetime column found in data")
99
 
100
- # Handle special EURUSD format with GMT
101
  if pair_name == "EURUSD" and df[datetime_col].astype(str).str.contains('GMT').any():
102
  print("๐Ÿ•— Handling EURUSD special datetime format...")
103
- # Clean GMT format
104
- df[datetime_col] = df[datetime_col].str.replace(' GMT', '', regex=False)
105
-
106
- # Parse with specified format
107
  df[datetime_col] = pd.to_datetime(
108
  df[datetime_col],
109
- format=config['date_format'],
110
  errors='coerce',
111
  utc=True
112
  )
113
  else:
114
- # Standard datetime parsing
115
  df[datetime_col] = pd.to_datetime(
116
  df[datetime_col],
117
  errors='coerce',
118
- utc=config['has_timezone']
119
  )
120
 
121
  # Remove rows with invalid dates
@@ -127,13 +141,20 @@ def preprocess_data_file(raw_file_path, pair_name):
127
  df.set_index(datetime_col, inplace=True)
128
  df.sort_index(inplace=True)
129
 
130
- # Handle decimal separators if needed
131
  if config['decimal_separator'] != '.':
 
132
  for col in ['Open', 'High', 'Low', 'Close', 'Volume']:
133
  if col in df.columns:
134
- df[col] = df[col].astype(str).str.replace(',', '.').astype(float)
 
 
 
 
 
 
135
 
136
- # Fill missing values
137
  for col in ['Open', 'High', 'Low', 'Close']:
138
  if col in df.columns:
139
  missing_before = df[col].isna().sum()
@@ -146,13 +167,15 @@ def preprocess_data_file(raw_file_path, pair_name):
146
  df = df[~df.index.duplicated(keep='first')]
147
  print(f"๐Ÿงน Removed {before_count - len(df)} duplicate entries")
148
 
149
- # Validate required columns
150
  missing_cols = [col for col in config['required_columns'] if col not in df.columns]
151
  if missing_cols:
152
- print(f"โŒ Missing required columns: {missing_cols}")
153
- print(f"Available columns: {df.columns.tolist()}")
154
  return None
155
 
 
 
 
156
  # Save preprocessed data
157
  processed_file = os.path.join(PROCESSED_DATA_DIR, f"{pair_name}_processed.csv")
158
  df.to_csv(processed_file)
@@ -162,44 +185,47 @@ def preprocess_data_file(raw_file_path, pair_name):
162
 
163
  except Exception as e:
164
  print(f"โŒ Preprocessing error for {pair_name}: {str(e)}")
 
165
  return None
166
 
167
  def load_available_data():
168
- """Load and preprocess all available data files"""
 
169
  available_data = {}
170
 
171
- # Check if raw data directory exists
172
  if not os.path.exists(RAW_DATA_DIR):
173
- print(f"โš ๏ธ Raw data directory not found: {RAW_DATA_DIR}")
174
- # Check if data is in root directory instead
175
  if os.path.exists("data") and os.path.isdir("data"):
176
- for filename in os.listdir("data"):
177
- if filename.endswith('.csv'):
178
- # Create raw directory structure
179
- os.makedirs(RAW_DATA_DIR, exist_ok=True)
180
- # Move file to raw directory
181
- import shutil
182
- shutil.move(os.path.join("data", filename), os.path.join(RAW_DATA_DIR, filename))
183
- print(f"โœ… Moved {filename} to {RAW_DATA_DIR}")
184
-
185
- # Check again after potential move
 
 
186
  if not os.path.exists(RAW_DATA_DIR):
187
- print(f"โŒ Still cannot find raw data directory: {RAW_DATA_DIR}")
188
  return available_data
189
 
190
  print(f"๐Ÿ” Scanning for data files in {RAW_DATA_DIR}...")
191
 
192
- # Scan for CSV files in raw data directory
193
  for filename in os.listdir(RAW_DATA_DIR):
194
  if filename.endswith('.csv'):
195
- # Extract pair name from filename
196
  pair_name = filename.split('.')[0].upper()
197
 
198
- # Check if we have config for this pair, or use default
 
199
  if pair_name not in TRADING_PAIRS:
200
  TRADING_PAIRS[pair_name] = {
201
- "description": f"{pair_name} Trading Pair",
202
- "date_format": "%Y-%m-%d %H:%M:%S",
203
  "has_timezone": False,
204
  "decimal_separator": ".",
205
  "required_columns": ["Open", "High", "Low", "Close"]
@@ -208,19 +234,19 @@ def load_available_data():
208
  raw_file_path = os.path.join(RAW_DATA_DIR, filename)
209
  processed_file_path = os.path.join(PROCESSED_DATA_DIR, f"{pair_name}_processed.csv")
210
 
211
- # Check if preprocessed file exists and is newer than raw file
212
  if os.path.exists(processed_file_path):
213
  raw_mod_time = os.path.getmtime(raw_file_path)
214
  processed_mod_time = os.path.getmtime(processed_file_path)
215
 
216
  if processed_mod_time > raw_mod_time:
217
- print(f"โœ… Using existing preprocessed data for {pair_name}")
218
  try:
219
  df = pd.read_csv(processed_file_path, index_col=0, parse_dates=True)
220
  available_data[pair_name] = df
 
221
  continue
222
  except Exception as e:
223
- print(f"โš ๏ธ Error loading preprocessed file: {str(e)}")
224
 
225
  # Preprocess the file
226
  print(f"๐Ÿ”„ Processing {pair_name} data...")
@@ -229,68 +255,75 @@ def load_available_data():
229
  available_data[pair_name] = df
230
  print(f"โœ… Successfully loaded {pair_name} with {len(df)} records")
231
  else:
232
- print(f"โŒ Failed to load {pair_name} data")
233
 
234
  return available_data
235
 
236
- # Load available data at startup
237
- print("๐Ÿš€ Initializing data processing system...")
238
- available_data = load_available_data()
239
- print(f"๐Ÿ“Š Available trading pairs: {list(available_data.keys())}")
240
 
241
  def get_available_pairs():
242
- """Get list of available trading pairs with status"""
243
  if not available_data:
244
- return "โš ๏ธ No data files found. Please upload CSV files to the 'data/raw' directory."
245
 
246
  status = "โœ… Available trading pairs:\n"
247
  for pair in sorted(available_data.keys()):
248
  df = available_data[pair]
249
  records = len(df)
250
- date_range = f"{df.index.min().strftime('%Y-%m-%d')} to {df.index.max().strftime('%Y-%m-%d')}"
251
- status += f"โ€ข {pair}: {records} records ({date_range})\n"
 
 
 
252
  return status
253
 
254
- def analyze_trading_pair(pair_name):
255
- """Analyze a specific trading pair"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  try:
257
- print(f"\n๐Ÿ” Starting analysis for {pair_name}")
258
-
259
- # Check if data is available
260
- if pair_name not in available_data:
261
- # Try case-insensitive match
262
- matched_pair = None
263
- for available_pair in available_data.keys():
264
- if pair_name.upper() == available_pair.upper():
265
- matched_pair = available_pair
266
- break
267
 
268
- if matched_pair is None:
269
- available_pairs = ", ".join(available_data.keys())
270
- return (
271
- f"โŒ Data not available for '{pair_name}'\n"
272
- f"Available pairs: {available_pairs}\n"
273
- f"Upload your data to 'data/raw' directory and restart the app",
274
- None, None, None
275
- )
276
- pair_name = matched_pair
277
-
278
- # Get the data
279
- hist = available_data[pair_name].copy()
280
- print(f"๐Ÿ“ˆ Loaded {len(hist)} records for {pair_name}")
281
-
282
  # Basic data validation
283
  required_cols = ['Open', 'High', 'Low', 'Close']
284
- missing_cols = [col for col in required_cols if col not in hist.columns]
285
-
286
- if missing_cols:
287
  return (
288
- f"โŒ Missing required columns: {', '.join(missing_cols)}\n"
289
- f"Available columns: {', '.join(hist.columns)}",
290
- None, None, None
291
  )
292
 
293
- # Create candlestick chart
294
  fig = go.Figure()
295
 
296
  # Add candlestick
@@ -303,48 +336,32 @@ def analyze_trading_pair(pair_name):
303
  name='Price'
304
  ))
305
 
306
- # Add moving averages if enough data
307
  if len(hist) >= 20:
308
  hist['MA20'] = hist['Close'].rolling(window=20, min_periods=1).mean()
309
- fig.add_trace(go.Scatter(
310
- x=hist.index,
311
- y=hist['MA20'],
312
- mode='lines',
313
- name='20-period MA',
314
- line=dict(color='blue', width=1.5)
315
- ))
316
 
317
  if len(hist) >= 50:
318
  hist['MA50'] = hist['Close'].rolling(window=50, min_periods=1).mean()
319
- fig.add_trace(go.Scatter(
320
- x=hist.index,
321
- y=hist['MA50'],
322
- mode='lines',
323
- name='50-period MA',
324
- line=dict(color='orange', width=1.5)
325
- ))
326
-
327
- # Update layout
328
  fig.update_layout(
329
  title=f"{pair_name} Price Analysis",
330
  xaxis_title="Date",
331
  yaxis_title="Price",
332
  template="plotly_white",
333
  hovermode="x unified",
 
334
  height=500,
335
- margin=dict(l=50, r=50, t=50, b=50)
336
  )
337
 
338
- # Create forecast using Prophet
339
- forecast_fig = None
340
- forecast_table = None
341
- forecast_result = ""
342
 
343
  try:
344
- print("๐Ÿ”ฎ Starting forecast generation...")
345
-
346
- # Prepare data for Prophet - USE ONLY RECENT DATA TO AVOID MEMORY ISSUES
347
- # Take last 365 days (1 year) of data for forecasting
348
  prophet_df = hist[['Close']].copy().last('365D').reset_index()
349
  prophet_df.columns = ['ds', 'y']
350
  prophet_df = prophet_df.dropna()
@@ -352,172 +369,149 @@ def analyze_trading_pair(pair_name):
352
  print(f"๐Ÿ“Š Using {len(prophet_df)} data points for forecasting")
353
 
354
  if len(prophet_df) < 30:
355
- forecast_result = f"โš ๏ธ Not enough recent data points for forecasting (have {len(prophet_df)}, need at least 30)"
356
- print(forecast_result)
357
  else:
358
- # Create and fit model - WITH EXPLICIT ERROR HANDLING
359
- try:
360
- print("โš™๏ธ Creating Prophet model...")
361
- model = Prophet(
362
- daily_seasonality=True,
363
- yearly_seasonality=True,
364
- interval_width=0.95,
365
- changepoint_prior_scale=0.05,
366
- stan_backend=None # CRITICAL FIX
367
- )
368
-
369
- print("๐Ÿ”ง Fitting model to data...")
370
- model.fit(prophet_df)
371
- print("โœ… Model fitted successfully")
372
-
373
- # Create future dataframe (30 days forecast)
374
- print("๐Ÿ“… Generating 30-day forecast...")
375
- future = model.make_future_dataframe(periods=30, freq='D')
376
- forecast = model.predict(future)
377
- print(f"โœ… Generated forecast for {len(forecast)} periods")
378
-
379
- # Create forecast chart
380
- forecast_fig = go.Figure()
381
-
382
- # Historical data (only show last 90 days for clarity)
383
- hist_recent = prophet_df[prophet_df['ds'] >= (prophet_df['ds'].max() - pd.Timedelta(days=90))]
384
- forecast_fig.add_trace(go.Scatter(
385
- x=hist_recent['ds'],
386
- y=hist_recent['y'],
387
- mode='lines',
388
- name='Historical',
389
- line=dict(color='blue', width=2)
390
- ))
391
-
392
- # Forecast data
393
- forecast_recent = forecast[forecast['ds'] >= (prophet_df['ds'].max() - pd.Timedelta(days=30))]
394
- forecast_fig.add_trace(go.Scatter(
395
- x=forecast_recent['ds'],
396
- y=forecast_recent['yhat'],
397
- mode='lines',
398
- name='Forecast',
399
- line=dict(color='red', width=2, dash='dash')
400
- ))
401
-
402
- # Confidence interval
403
- forecast_fig.add_trace(go.Scatter(
404
- x=forecast_recent['ds'].tolist() + forecast_recent['ds'][::-1].tolist(),
405
- y=forecast_recent['yhat_upper'].tolist() + forecast_recent['yhat_lower'][::-1].tolist(),
406
- fill='toself',
407
- fillcolor='rgba(255,0,0,0.1)',
408
- line=dict(color='rgba(255,255,255,0)'),
409
- name='95% CI'
410
- ))
411
-
412
- forecast_fig.update_layout(
413
- title=f"{pair_name} 30-Day Price Forecast",
414
- xaxis_title="Date",
415
- yaxis_title="Price",
416
- template="plotly_white",
417
- height=500,
418
- hovermode="x unified"
419
- )
420
-
421
- # Create forecast table (next 30 days only)
422
- future_dates = forecast[forecast['ds'] > prophet_df['ds'].max()].head(30)
423
-
424
- # Format dates and prices
425
- future_dates['Date'] = future_dates['ds'].dt.strftime('%Y-%m-%d')
426
- future_dates['Predicted Price'] = future_dates['yhat'].apply(lambda x: f"{x:.5f}")
427
- future_dates['Lower Bound'] = future_dates['yhat_lower'].apply(lambda x: f"{x:.5f}")
428
- future_dates['Upper Bound'] = future_dates['yhat_upper'].apply(lambda x: f"{x:.5f}")
429
- future_dates['Trend'] = future_dates['yhat'].diff().apply(
430
- lambda x: "๐Ÿ“ˆ Rising" if x > 0 else "๐Ÿ“‰ Falling" if x < 0 else "โžก๏ธ Stable"
431
- )
432
-
433
- # Create table data
434
- table_data = future_dates[['Date', 'Predicted Price', 'Lower Bound', 'Upper Bound', 'Trend']].values.tolist()
435
- table_headers = ["Date", "Predicted Price", "Lower Bound", "Upper Bound", "Trend"]
436
-
437
- forecast_table = gr.DataFrame(
438
- headers=table_headers,
439
- value=table_data,
440
- datatype=["str", "str", "str", "str", "str"],
441
- label=f"{pair_name} 30-Day Price Forecast Table",
442
- interactive=False
443
- )
444
-
445
- # Get last forecast values
446
- last_forecast = forecast.iloc[-1]
447
- forecast_result = (
448
- f"๐Ÿ”ฎ 30-Day Forecast:\n"
449
- f"Predicted price: {last_forecast['yhat']:.5f}\n"
450
- f"Range: {last_forecast['yhat_lower']:.5f} to {last_forecast['yhat_upper']:.5f}"
451
- )
452
- print("โœ… Forecast generated successfully")
453
 
454
- except Exception as model_error:
455
- error_details = str(model_error)
456
- if "MemoryError" in error_details:
457
- forecast_result = "โš ๏ธ Memory error during forecasting. Try reducing data size."
458
- elif "ValueError" in error_details:
459
- forecast_result = f"โš ๏ธ Data format error: {error_details}"
460
- else:
461
- forecast_result = f"โš ๏ธ Model error: {error_details}"
462
- print(f"โŒ Forecasting failed: {forecast_result}")
463
- import traceback
464
- traceback.print_exc()
465
-
466
- except Exception as e:
467
- forecast_result = f"โš ๏ธ Unexpected forecasting error: {str(e)}"
468
- print(forecast_result)
469
- import traceback
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  traceback.print_exc()
471
-
472
- # Technical analysis
473
  current_price = hist['Close'].iloc[-1]
474
  signal = "๐Ÿ“Š Analyzing market conditions..."
475
 
476
- if 'MA20' in hist.columns:
 
477
  ma20 = hist['MA20'].iloc[-1]
478
- if current_price > ma20:
479
- signal = "๐Ÿ“ˆ BULLISH: Price above 20-period MA"
480
- else:
481
- signal = "๐Ÿ“‰ BEARISH: Price below 20-period MA"
482
-
483
- if 'MA50' in hist.columns:
484
  ma50 = hist['MA50'].iloc[-1]
 
485
  if current_price > ma20 and ma20 > ma50:
486
- signal = "๐Ÿš€ STRONG BULLISH: Golden Cross pattern"
487
  elif current_price < ma20 and ma20 < ma50:
488
- signal = "๐Ÿ’ฃ STRONG BEARISH: Death Cross pattern"
 
 
 
 
489
 
490
  # Calculate performance metrics
491
  start_price = hist['Close'].iloc[0]
492
  total_return = (current_price / start_price - 1) * 100
493
- volatility = hist['Close'].pct_change().std() * np.sqrt(252) * 100 # Annualized volatility
494
 
495
- # Create result text
 
 
 
 
496
  result_text = (
497
- f"๐Ÿ“Š {pair_name} Analysis Report\n"
498
- f"{'=' * 40}\n"
499
- f"๐Ÿ’ฐ Current Price: {current_price:.5f}\n"
500
- f"๐Ÿ“ˆ Total Return: {total_return:.2f}%\n"
501
- f"โšก Volatility: {volatility:.2f}%\n"
502
- f"๐ŸŽฏ Signal: {signal}\n"
503
- f"{'=' * 40}\n"
504
  f"{forecast_result}"
505
  )
506
 
507
  print(f"โœ… Analysis completed for {pair_name}")
508
  return result_text, fig, forecast_fig, forecast_table
509
-
510
  except Exception as e:
511
- error_msg = f"โŒ Analysis error: {str(e)}"
512
  print(error_msg)
513
- import traceback
514
  traceback.print_exc()
515
- return error_msg, None, None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
 
517
- # Create Gradio interface
518
  with gr.Blocks(title="Trading Pair AI Analyzer") as demo:
519
  gr.Markdown("# ๐Ÿ“ˆ Trading Pair AI Analysis System")
520
- gr.Markdown("### Analyze forex, stocks, and crypto with AI-powered insights")
521
 
522
  with gr.Row():
523
  with gr.Column(scale=2):
@@ -525,85 +519,75 @@ with gr.Blocks(title="Trading Pair AI Analyzer") as demo:
525
  label="๐Ÿ“Š Available Data",
526
  value=get_available_pairs(),
527
  interactive=False,
528
- lines=5
 
529
  )
530
 
531
  with gr.Column(scale=1):
532
  gr.Markdown("### โ„น๏ธ System Information")
533
  system_info = gr.Textbox(
534
- value=f"๐Ÿ“ˆ Trading Analysis System v2.1\n"
535
- f"๐Ÿ•’ Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
536
- f"๐Ÿงฎ Loaded pairs: {len(available_data)}",
537
- interactive=False
 
 
 
538
  )
539
 
540
  with gr.Row():
541
- with gr.Column():
542
  pair_input = gr.Textbox(
543
- label="๐Ÿ” Trading Pair",
544
- value="EURUSD",
545
  placeholder="Enter pair name (e.g., EURUSD, BTCUSD, AAPL)"
546
  )
547
- analyze_btn = gr.Button("๐Ÿš€ Analyze", variant="primary")
548
-
549
- with gr.Column():
550
- gr.Markdown("### ๐Ÿ’ก Quick Tips")
551
- gr.Markdown("""
552
- - Use pair names from the available data list
553
- - System automatically preprocesses your data
554
- - First analysis may take 30-60 seconds
555
- - Forecast table shows next 30 days of predicted prices
556
- """)
557
-
558
- result_output = gr.Textbox(label="๐Ÿ“ Analysis Results", lines=8)
559
-
560
- with gr.Tabs():
561
- with gr.TabItem("๐Ÿ“Š Charts"):
562
- with gr.Row():
563
- price_chart = gr.Plot(label="๐Ÿ“Š Price Chart & Technical Indicators")
564
- forecast_chart = gr.Plot(label="๐Ÿ”ฎ 30-Day Price Forecast")
565
 
566
- with gr.TabItem("๐Ÿ“ˆ Forecast Table"):
 
 
 
 
 
 
 
 
 
 
 
567
  forecast_table = gr.DataFrame(
568
  headers=["Date", "Predicted Price", "Lower Bound", "Upper Bound", "Trend"],
569
  value=[],
570
  datatype=["str", "str", "str", "str", "str"],
571
- label="30-Day Price Forecast Table",
572
- interactive=False
 
573
  )
574
 
575
- with gr.Accordion("๐Ÿ“ Data Upload Instructions", open=False):
576
  gr.Markdown("""
577
- ### How to Add Your Own Data
578
 
579
- 1. **Prepare your CSV file** with these columns:
580
- - Date/Time column (any format)
581
- - Open, High, Low, Close prices
582
- - Volume (optional)
583
 
584
- 2. **Upload to Hugging Face Space**:
585
- - Go to your Space Files tab
586
- - Create directories: `data/raw/`
587
- - Upload your CSV files to `data/raw/`
588
- - Example filenames: `EURUSD.csv`, `BTCUSD.csv`
589
 
590
- 3. **Restart the application**:
591
- - Go to Settings โ†’ Restart Space
592
- - Wait 2-3 minutes for rebuild
593
 
594
- 4. **Your data will be automatically preprocessed** and ready for analysis!
595
  """)
596
 
597
  # Examples for quick testing
598
- examples = gr.Examples(
599
- examples=[
600
- ["EURUSD"],
601
- ["BTCUSD"],
602
- ["AAPL"]
603
- ],
604
- inputs=pair_input,
605
- label="Try these examples:"
606
- )
607
 
608
  # Analysis function
609
  analyze_btn.click(
 
5
  from prophet import Prophet
6
  import os
7
  import warnings
8
+ import datetime
9
+ import shutil # For moving files
10
+ import traceback # For detailed error logging
11
+
12
+ # Suppress all warnings for a cleaner output
13
  warnings.filterwarnings('ignore')
14
 
15
+ # --- Configuration and Environment Setup ---
16
+ # Performance optimization for Apple Silicon/MKL based libraries
17
  os.environ["OMP_NUM_THREADS"] = "1"
18
  os.environ["OPENBLAS_NUM_THREADS"] = "1"
19
  os.environ["MKL_NUM_THREADS"] = "1"
 
23
  PROCESSED_DATA_DIR = "data/processed"
24
  os.makedirs(PROCESSED_DATA_DIR, exist_ok=True)
25
 
26
+ # Predefined trading pairs with expected formats.
27
+ # This dictionary will be dynamically updated in load_available_data.
28
  TRADING_PAIRS = {
29
+ # Default configs for known pairs
30
  "EURUSD": {
31
+ "description": "Euro to US Dollar Forex Pair (Sample)",
32
  "date_format": "%d.%m.%Y %H:%M:%S.%f %z",
33
  "has_timezone": True,
34
  "decimal_separator": ".",
35
  "required_columns": ["Open", "High", "Low", "Close"]
36
  },
37
  "BTCUSD": {
38
+ "description": "Bitcoin to US Dollar (Sample)",
39
  "date_format": "%Y-%m-%d %H:%M:%S",
40
  "has_timezone": False,
41
  "decimal_separator": ".",
42
  "required_columns": ["Open", "High", "Low", "Close"]
43
  },
44
  "AAPL": {
45
+ "description": "Apple Inc. Stock (Sample)",
46
  "date_format": "%Y-%m-%d",
47
  "has_timezone": False,
48
  "decimal_separator": ".",
 
50
  }
51
  }
52
 
53
+ # --- Data Preprocessing Functions ---
54
+
55
  def preprocess_data_file(raw_file_path, pair_name):
56
  """Preprocess raw data file to standardized format"""
57
  print(f"๐Ÿ”„ Preprocessing data for {pair_name}...")
58
 
59
+ # Get pair configuration, or use generic defaults for a new pair
60
+ config = TRADING_PAIRS.get(pair_name, {
61
+ "description": f"{pair_name} Trading Pair",
62
+ "date_format": None, # Use dynamic parsing for generic pairs
63
+ "has_timezone": False,
64
+ "decimal_separator": ".",
65
+ "required_columns": ["Open", "High", "Low", "Close"]
66
+ })
67
+
68
  try:
69
+ # Read raw data with robust encoding and generic delimiter detection
 
 
 
70
  encodings = ['utf-8', 'latin1', 'ISO-8859-1', 'cp1252']
71
  df = None
72
 
73
  for encoding in encodings:
74
  try:
75
+ # Attempt to read CSV, let pandas infer delimiter
76
+ df = pd.read_csv(raw_file_path, encoding=encoding, sep=None, engine='python')
77
  print(f"โœ… Successfully read {pair_name} data with {encoding} encoding")
78
  break
79
  except (UnicodeDecodeError, pd.errors.ParserError):
80
  continue
81
 
82
  if df is None:
83
+ raise Exception(f"โŒ Failed to read {pair_name} data with any encoding/delimiter")
84
 
85
  # Standardize column names (case-insensitive)
86
  column_mapping = {}
87
  for col in df.columns:
88
+ col_lower = col.lower().strip()
89
 
90
+ if any(keyword in col_lower for keyword in ['date', 'time', 'timestamp', 'ds']):
91
  column_mapping[col] = 'datetime'
92
  elif 'open' in col_lower:
93
  column_mapping[col] = 'Open'
 
95
  column_mapping[col] = 'High'
96
  elif 'low' in col_lower:
97
  column_mapping[col] = 'Low'
98
+ elif 'close' in col_lower or 'price' in col_lower:
99
  column_mapping[col] = 'Close'
100
+ elif 'volume' in col_lower or 'vol' in col_lower:
101
  column_mapping[col] = 'Volume'
102
 
103
  if column_mapping:
 
106
 
107
  # Process datetime column
108
  datetime_col = None
109
+ for col in ['datetime', 'ds']:
110
  if col in df.columns:
111
  datetime_col = col
112
  break
113
 
114
  if datetime_col is None:
115
+ raise Exception("โŒ No datetime column found in data after renaming")
116
 
117
+ # Handle special EURUSD format with GMT (if pair is specifically EURUSD)
118
  if pair_name == "EURUSD" and df[datetime_col].astype(str).str.contains('GMT').any():
119
  print("๐Ÿ•— Handling EURUSD special datetime format...")
120
+ df[datetime_col] = df[datetime_col].astype(str).str.replace(' GMT', '', regex=False)
 
 
 
121
  df[datetime_col] = pd.to_datetime(
122
  df[datetime_col],
123
+ format=config['date_format'], # Use specific format for EURUSD
124
  errors='coerce',
125
  utc=True
126
  )
127
  else:
128
+ # Use robust generic datetime parsing for all other cases
129
  df[datetime_col] = pd.to_datetime(
130
  df[datetime_col],
131
  errors='coerce',
132
+ utc=config.get('has_timezone', False) # Use config setting if available
133
  )
134
 
135
  # Remove rows with invalid dates
 
141
  df.set_index(datetime_col, inplace=True)
142
  df.sort_index(inplace=True)
143
 
144
+ # Handle non-standard decimal separators (e.g., European format ',')
145
  if config['decimal_separator'] != '.':
146
+ print(f"๐Ÿ› ๏ธ Fixing decimal separator from {config['decimal_separator']} to '.'")
147
  for col in ['Open', 'High', 'Low', 'Close', 'Volume']:
148
  if col in df.columns:
149
+ # Convert to string, replace comma with dot, then convert to float
150
+ df[col] = df[col].astype(str).str.replace(config['decimal_separator'], '.', regex=False).astype(float)
151
+
152
+ # Ensure price columns are numeric
153
+ for col in ['Open', 'High', 'Low', 'Close']:
154
+ if col in df.columns:
155
+ df[col] = pd.to_numeric(df[col], errors='coerce')
156
 
157
+ # Fill missing values (only after numeric conversion)
158
  for col in ['Open', 'High', 'Low', 'Close']:
159
  if col in df.columns:
160
  missing_before = df[col].isna().sum()
 
167
  df = df[~df.index.duplicated(keep='first')]
168
  print(f"๐Ÿงน Removed {before_count - len(df)} duplicate entries")
169
 
170
+ # Final validation
171
  missing_cols = [col for col in config['required_columns'] if col not in df.columns]
172
  if missing_cols:
173
+ print(f"โŒ Missing required columns: {missing_cols}. Available: {df.columns.tolist()}")
 
174
  return None
175
 
176
+ if len(df) < 2:
177
+ raise Exception("Dataset has fewer than 2 valid rows after preprocessing.")
178
+
179
  # Save preprocessed data
180
  processed_file = os.path.join(PROCESSED_DATA_DIR, f"{pair_name}_processed.csv")
181
  df.to_csv(processed_file)
 
185
 
186
  except Exception as e:
187
  print(f"โŒ Preprocessing error for {pair_name}: {str(e)}")
188
+ traceback.print_exc()
189
  return None
190
 
191
  def load_available_data():
192
+ """Load and preprocess all available data files, handling raw folder creation."""
193
+ global TRADING_PAIRS, available_data
194
  available_data = {}
195
 
196
+ # Check if data exists in a generic 'data' folder and move it to 'data/raw'
197
  if not os.path.exists(RAW_DATA_DIR):
198
+ print(f"โš ๏ธ Raw data directory not found: {RAW_DATA_DIR}. Checking 'data/'...")
 
199
  if os.path.exists("data") and os.path.isdir("data"):
200
+ csv_files = [f for f in os.listdir("data") if f.endswith('.csv')]
201
+ if csv_files:
202
+ os.makedirs(RAW_DATA_DIR, exist_ok=True)
203
+ for filename in csv_files:
204
+ try:
205
+ shutil.move(os.path.join("data", filename), os.path.join(RAW_DATA_DIR, filename))
206
+ print(f"โœ… Moved {filename} to {RAW_DATA_DIR}")
207
+ except Exception as e:
208
+ print(f"โš ๏ธ Could not move {filename}: {e}")
209
+ else:
210
+ print("No CSV files found in 'data/' to move.")
211
+
212
  if not os.path.exists(RAW_DATA_DIR):
213
+ print(f"โŒ Still cannot find raw data directory: {RAW_DATA_DIR}. Please check your file structure.")
214
  return available_data
215
 
216
  print(f"๐Ÿ” Scanning for data files in {RAW_DATA_DIR}...")
217
 
 
218
  for filename in os.listdir(RAW_DATA_DIR):
219
  if filename.endswith('.csv'):
220
+ # Extract pair name from filename (e.g., EURUSD.csv -> EURUSD)
221
  pair_name = filename.split('.')[0].upper()
222
 
223
+ # --- Generalization Improvement ---
224
+ # If pair is not in TRADING_PAIRS, add it with generic defaults
225
  if pair_name not in TRADING_PAIRS:
226
  TRADING_PAIRS[pair_name] = {
227
+ "description": f"{pair_name} Trading Pair (Generic)",
228
+ "date_format": None, # Indicates dynamic parsing
229
  "has_timezone": False,
230
  "decimal_separator": ".",
231
  "required_columns": ["Open", "High", "Low", "Close"]
 
234
  raw_file_path = os.path.join(RAW_DATA_DIR, filename)
235
  processed_file_path = os.path.join(PROCESSED_DATA_DIR, f"{pair_name}_processed.csv")
236
 
237
+ # Check for existing preprocessed file
238
  if os.path.exists(processed_file_path):
239
  raw_mod_time = os.path.getmtime(raw_file_path)
240
  processed_mod_time = os.path.getmtime(processed_file_path)
241
 
242
  if processed_mod_time > raw_mod_time:
 
243
  try:
244
  df = pd.read_csv(processed_file_path, index_col=0, parse_dates=True)
245
  available_data[pair_name] = df
246
+ print(f"โœ… Using existing preprocessed data for {pair_name} with {len(df)} records")
247
  continue
248
  except Exception as e:
249
+ print(f"โš ๏ธ Error loading preprocessed file for {pair_name}: {str(e)}. Reprocessing.")
250
 
251
  # Preprocess the file
252
  print(f"๐Ÿ”„ Processing {pair_name} data...")
 
255
  available_data[pair_name] = df
256
  print(f"โœ… Successfully loaded {pair_name} with {len(df)} records")
257
  else:
258
+ print(f"โŒ Failed to load {pair_name} data. See error above.")
259
 
260
  return available_data
261
 
262
+ # --- Analysis Functions ---
 
 
 
263
 
264
  def get_available_pairs():
265
+ """Get list of available trading pairs with status for UI"""
266
  if not available_data:
267
+ return "โš ๏ธ No data files found. Please upload CSV files to the 'data/raw' directory and restart the app."
268
 
269
  status = "โœ… Available trading pairs:\n"
270
  for pair in sorted(available_data.keys()):
271
  df = available_data[pair]
272
  records = len(df)
273
+ if records > 0:
274
+ date_range = f"{df.index.min().strftime('%Y-%m-%d')} to {df.index.max().strftime('%Y-%m-%d')}"
275
+ status += f"โ€ข {pair}: {records} records ({date_range})\n"
276
+ else:
277
+ status += f"โ€ข {pair}: 0 records (Data Error)\n"
278
  return status
279
 
280
+ def analyze_trading_pair(pair_name: str):
281
+ """
282
+ Analyzes a specific trading pair, generates candlestick chart,
283
+ calculates technical indicators, and runs a Prophet forecast.
284
+ """
285
+
286
+ pair_name = pair_name.upper().strip() # Normalize input
287
+ print(f"\n๐Ÿ” Starting analysis for {pair_name}")
288
+
289
+ # Initial return values for error case
290
+ default_error_fig = go.Figure().update_layout(title="Analysis Failed", xaxis_title="Time", yaxis_title="Price")
291
+ default_error_df = gr.DataFrame(headers=["Error"], value=[["Analysis failed"]])
292
+
293
+ # Check if data is available (case-insensitive search)
294
+ matched_pair = None
295
+ for available_pair in available_data.keys():
296
+ if pair_name == available_pair or pair_name.upper() == available_pair.upper():
297
+ matched_pair = available_pair
298
+ break
299
+
300
+ if matched_pair is None:
301
+ available_pairs = ", ".join(available_data.keys()) or "None"
302
+ return (
303
+ f"โŒ Data not available for '{pair_name}'\nAvailable pairs: {available_pairs}",
304
+ default_error_fig, default_error_fig, default_error_df
305
+ )
306
+
307
+ pair_name = matched_pair
308
+ hist = available_data[pair_name].copy()
309
+
310
  try:
311
+ if len(hist) < 5:
312
+ return (
313
+ f"โŒ Data is too short for analysis. Only {len(hist)} records.",
314
+ default_error_fig, default_error_fig, default_error_df
315
+ )
 
 
 
 
 
316
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  # Basic data validation
318
  required_cols = ['Open', 'High', 'Low', 'Close']
319
+ if not all(col in hist.columns for col in required_cols):
320
+ missing_cols = [col for col in required_cols if col not in hist.columns]
 
321
  return (
322
+ f"โŒ Missing required columns: {', '.join(missing_cols)}\nAvailable columns: {', '.join(hist.columns)}",
323
+ default_error_fig, default_error_fig, default_error_df
 
324
  )
325
 
326
+ # --- 1. Candlestick Chart with Technical Indicators (MAs) ---
327
  fig = go.Figure()
328
 
329
  # Add candlestick
 
336
  name='Price'
337
  ))
338
 
339
+ # Add moving averages
340
  if len(hist) >= 20:
341
  hist['MA20'] = hist['Close'].rolling(window=20, min_periods=1).mean()
342
+ fig.add_trace(go.Scatter(x=hist.index, y=hist['MA20'], mode='lines', name='20-period MA', line=dict(color='blue', width=1.5)))
 
 
 
 
 
 
343
 
344
  if len(hist) >= 50:
345
  hist['MA50'] = hist['Close'].rolling(window=50, min_periods=1).mean()
346
+ fig.add_trace(go.Scatter(x=hist.index, y=hist['MA50'], mode='lines', name='50-period MA', line=dict(color='orange', width=1.5)))
347
+
 
 
 
 
 
 
 
348
  fig.update_layout(
349
  title=f"{pair_name} Price Analysis",
350
  xaxis_title="Date",
351
  yaxis_title="Price",
352
  template="plotly_white",
353
  hovermode="x unified",
354
+ xaxis_rangeslider_visible=False, # Hide the bottom slider for cleaner look
355
  height=500,
 
356
  )
357
 
358
+ # --- 2. Forecasting using Prophet ---
359
+ forecast_fig = default_error_fig
360
+ forecast_table = default_error_df
361
+ forecast_result = "No forecast data available"
362
 
363
  try:
364
+ # Use last 1 year (365 days) of data for forecasting, ensures manageable size and recent relevance
 
 
 
365
  prophet_df = hist[['Close']].copy().last('365D').reset_index()
366
  prophet_df.columns = ['ds', 'y']
367
  prophet_df = prophet_df.dropna()
 
369
  print(f"๐Ÿ“Š Using {len(prophet_df)} data points for forecasting")
370
 
371
  if len(prophet_df) < 30:
372
+ forecast_result = f"โš ๏ธ Not enough recent data points for forecasting (have {len(prophet_df)}, need at least 30 historical daily points for good results)."
373
+
374
  else:
375
+ # Initialize Prophet model with CRITICAL FIX (stan_backend=None)
376
+ model = Prophet(
377
+ daily_seasonality=False, # Use False unless intra-day data is used
378
+ yearly_seasonality=True,
379
+ interval_width=0.95,
380
+ changepoint_prior_scale=0.05,
381
+ stan_backend=None # CRITICAL FIX for better compatibility
382
+ )
383
+
384
+ # Automatically add custom daily seasonality if data is less than daily resolution
385
+ if (prophet_df['ds'].diff().min().total_seconds() < 86400 * 0.9): # < 90% of a day
386
+ model.add_seasonality(name='subdaily', period=1, fourier_order=5, prior_scale=0.1)
387
+
388
+ model.fit(prophet_df)
389
+
390
+ # Create future dataframe (30 days forecast)
391
+ future = model.make_future_dataframe(periods=30, freq='D')
392
+ forecast = model.predict(future)
393
+
394
+ # Create forecast chart
395
+ forecast_fig = go.Figure()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
 
397
+ # Historical data (only show last 90 days for clarity)
398
+ hist_recent = prophet_df[prophet_df['ds'] >= (prophet_df['ds'].max() - pd.Timedelta(days=90))]
399
+ forecast_fig.add_trace(go.Scatter(x=hist_recent['ds'], y=hist_recent['y'], mode='lines', name='Historical', line=dict(color='blue', width=2)))
400
+
401
+ # Forecast data
402
+ # Show forecast from last 30 days of historical data + future
403
+ forecast_recent = forecast[forecast['ds'] >= (prophet_df['ds'].max() - pd.Timedelta(days=30))]
404
+ forecast_fig.add_trace(go.Scatter(x=forecast_recent['ds'], y=forecast_recent['yhat'], mode='lines', name='Forecast', line=dict(color='red', width=2, dash='dash')))
405
+
406
+ # Confidence interval
407
+ forecast_fig.add_trace(go.Scatter(
408
+ x=forecast_recent['ds'].tolist() + forecast_recent['ds'][::-1].tolist(),
409
+ y=forecast_recent['yhat_upper'].tolist() + forecast_recent['yhat_lower'][::-1].tolist(),
410
+ fill='toself', fillcolor='rgba(255,0,0,0.1)', line=dict(color='rgba(255,255,255,0)'), name='95% CI'
411
+ ))
412
+
413
+ forecast_fig.update_layout(
414
+ title=f"{pair_name} 30-Day Price Forecast", xaxis_title="Date", yaxis_title="Price",
415
+ template="plotly_white", height=500, hovermode="x unified"
416
+ )
417
+
418
+ # Create forecast table
419
+ future_dates = forecast[forecast['ds'] > prophet_df['ds'].max()].head(30)
420
+
421
+ # Calculate trend based on yhat difference
422
+ future_dates['Trend_Value'] = future_dates['yhat'].diff()
423
+ future_dates.iloc[0, future_dates.columns.get_loc('Trend_Value')] = future_dates.iloc[0]['yhat'] - prophet_df.iloc[-1]['y']
424
+
425
+ future_dates['Date'] = future_dates['ds'].dt.strftime('%Y-%m-%d')
426
+ future_dates['Predicted Price'] = future_dates['yhat'].apply(lambda x: f"{x:.5f}")
427
+ future_dates['Lower Bound'] = future_dates['yhat_lower'].apply(lambda x: f"{x:.5f}")
428
+ future_dates['Upper Bound'] = future_dates['yhat_upper'].apply(lambda x: f"{x:.5f}")
429
+ future_dates['Trend'] = future_dates['Trend_Value'].apply(
430
+ lambda x: "๐Ÿ“ˆ Rising" if x > 0 else "๐Ÿ“‰ Falling" if x < 0 else "โžก๏ธ Stable"
431
+ )
432
+
433
+ table_data = future_dates[['Date', 'Predicted Price', 'Lower Bound', 'Upper Bound', 'Trend']].values.tolist()
434
+ table_headers = ["Date", "Predicted Price", "Lower Bound", "Upper Bound", "Trend"]
435
+
436
+ forecast_table = gr.DataFrame(headers=table_headers, value=table_data)
437
+
438
+ last_forecast = forecast.iloc[-1]
439
+ forecast_result = (
440
+ f"๐Ÿ”ฎ 30-Day Forecast:\n"
441
+ f"Predicted price for {last_forecast['ds'].strftime('%Y-%m-%d')}: **{last_forecast['yhat']:.5f}**\n"
442
+ f"95% Confidence Range: {last_forecast['yhat_lower']:.5f} to {last_forecast['yhat_upper']:.5f}"
443
+ )
444
+ print("โœ… Forecast generated successfully")
445
+
446
+ except Exception as model_error:
447
+ forecast_result = f"โš ๏ธ Forecasting failed. Error: {str(model_error)}"
448
+ print(f"โŒ Forecasting failed: {forecast_result}")
449
  traceback.print_exc()
450
+
451
+ # --- 3. Technical Analysis Signal & Metrics ---
452
  current_price = hist['Close'].iloc[-1]
453
  signal = "๐Ÿ“Š Analyzing market conditions..."
454
 
455
+ # Signal based on MAs
456
+ if 'MA50' in hist.columns and 'MA20' in hist.columns:
457
  ma20 = hist['MA20'].iloc[-1]
 
 
 
 
 
 
458
  ma50 = hist['MA50'].iloc[-1]
459
+
460
  if current_price > ma20 and ma20 > ma50:
461
+ signal = "๐Ÿš€ **STRONG BULLISH**: Price above 20MA, and 20MA > 50MA (Golden Cross potential)"
462
  elif current_price < ma20 and ma20 < ma50:
463
+ signal = "๐Ÿ’ฃ **STRONG BEARISH**: Price below 20MA, and 20MA < 50MA (Death Cross potential)"
464
+ elif current_price > ma20:
465
+ signal = "๐Ÿ“ˆ **BULLISH**: Price above 20-period MA"
466
+ elif current_price < ma20:
467
+ signal = "๐Ÿ“‰ **BEARISH**: Price below 20-period MA"
468
 
469
  # Calculate performance metrics
470
  start_price = hist['Close'].iloc[0]
471
  total_return = (current_price / start_price - 1) * 100
 
472
 
473
+ # Annualized volatility: assumes daily data, adjusts for time period if needed (simple)
474
+ # Use a more robust check for non-daily data, e.g., daily returns if frequency is higher than daily
475
+ volatility = hist['Close'].pct_change().std() * np.sqrt(252) * 100
476
+
477
+ # Create final result text
478
  result_text = (
479
+ f"๐Ÿ“Š **{pair_name} Analysis Report**\n"
480
+ f"{'=' * 50}\n"
481
+ f"๐Ÿ’ฐ **Current Price**: {current_price:.5f}\n"
482
+ f"๐Ÿ“ˆ **Total Return (full period)**: {total_return:.2f}%\n"
483
+ f"โšก **Annualized Volatility**: {volatility:.2f}%\n"
484
+ f"๐ŸŽฏ **Technical Signal**: {signal}\n"
485
+ f"{'=' * 50}\n"
486
  f"{forecast_result}"
487
  )
488
 
489
  print(f"โœ… Analysis completed for {pair_name}")
490
  return result_text, fig, forecast_fig, forecast_table
491
+
492
  except Exception as e:
493
+ error_msg = f"โŒ Major Analysis Error for {pair_name}: {str(e)}"
494
  print(error_msg)
 
495
  traceback.print_exc()
496
+
497
+ return (
498
+ error_msg,
499
+ default_error_fig,
500
+ default_error_fig,
501
+ default_error_df
502
+ )
503
+
504
+ # --- Application Initialization ---
505
+
506
+ print("๐Ÿš€ Initializing data processing system...")
507
+ available_data = load_available_data()
508
+ print(f"๐Ÿ“Š Available trading pairs: {list(available_data.keys())}")
509
+
510
+ # --- Gradio Interface ---
511
 
 
512
  with gr.Blocks(title="Trading Pair AI Analyzer") as demo:
513
  gr.Markdown("# ๐Ÿ“ˆ Trading Pair AI Analysis System")
514
+ gr.Markdown("### Analyze financial instruments with interactive charts and 30-day AI-powered forecasts (Prophet)")
515
 
516
  with gr.Row():
517
  with gr.Column(scale=2):
 
519
  label="๐Ÿ“Š Available Data",
520
  value=get_available_pairs(),
521
  interactive=False,
522
+ lines=5,
523
+ autoscroll=True
524
  )
525
 
526
  with gr.Column(scale=1):
527
  gr.Markdown("### โ„น๏ธ System Information")
528
  system_info = gr.Textbox(
529
+ value=(
530
+ f"๐Ÿ“ˆ Trading Analysis System v2.3\n"
531
+ f"๐Ÿ•’ Last updated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
532
+ f"๐Ÿงฎ Loaded pairs: {len(available_data)}"
533
+ ),
534
+ interactive=False,
535
+ lines=3
536
  )
537
 
538
  with gr.Row():
539
+ with gr.Column(scale=2):
540
  pair_input = gr.Textbox(
541
+ label="๐Ÿ” Trading Pair to Analyze",
542
+ value=list(available_data.keys())[0] if available_data else "EURUSD", # Set default to first available pair
543
  placeholder="Enter pair name (e.g., EURUSD, BTCUSD, AAPL)"
544
  )
545
+ analyze_btn = gr.Button("๐Ÿš€ Analyze Pair", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
546
 
547
+ with gr.Column(scale=3):
548
+ result_output = gr.Textbox(label="๐Ÿ“ Analysis & Forecast Summary", lines=6, max_lines=6)
549
+
550
+ # Tabs for Visual Output
551
+ with gr.Tabs():
552
+ with gr.TabItem("๐Ÿ“ˆ Price Chart & Indicators"):
553
+ price_chart = gr.Plot(label="Candlestick Chart with 20/50-period Moving Averages")
554
+
555
+ with gr.TabItem("๐Ÿ”ฎ Price Forecast Chart"):
556
+ forecast_chart = gr.Plot(label="30-Day Price Forecast (Prophet Model)")
557
+
558
+ with gr.TabItem("๐Ÿ“‹ Forecast Table"):
559
  forecast_table = gr.DataFrame(
560
  headers=["Date", "Predicted Price", "Lower Bound", "Upper Bound", "Trend"],
561
  value=[],
562
  datatype=["str", "str", "str", "str", "str"],
563
+ label="30-Day Predicted Prices Table",
564
+ interactive=False,
565
+ wrap=True
566
  )
567
 
568
+ with gr.Accordion("๐Ÿ“ Data & Usage Instructions", open=False):
569
  gr.Markdown("""
570
+ ### How to Add Your Own Data (General Instrument Handling)
571
 
572
+ 1. **Prepare your CSV file** with at least these columns:
573
+ - **Date/Time** column (any reasonable format)
574
+ - **Open, High, Low, Close** prices (case-insensitive column names are handled).
 
575
 
576
+ 2. **Upload to Hugging Face Space**: Upload your CSV file(s) to the designated folder: `data/raw/`
 
 
 
 
577
 
578
+ 3. **Restart the application**: Go to the Space Settings and select 'Restart Space'.
 
 
579
 
580
+ 4. **Automatic Processing**: Your data will be automatically loaded, cleaned, and a new pair entry will appear in the 'Available Data' section, ready for analysis. The system is designed to generalize to *any* instrument/pair name you upload (e.g., `TSLA.csv`, `GBPCHF.csv`).
581
  """)
582
 
583
  # Examples for quick testing
584
+ examples_list = [pair for pair in available_data.keys() if pair in ["EURUSD", "BTCUSD", "AAPL"]]
585
+ if examples_list:
586
+ gr.Examples(
587
+ examples=[[pair] for pair in examples_list],
588
+ inputs=pair_input,
589
+ label="Try these examples:"
590
+ )
 
 
591
 
592
  # Analysis function
593
  analyze_btn.click(