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

Fix Gradio compatibility issue

Browse files
Files changed (1) hide show
  1. app.py +83 -251
app.py CHANGED
@@ -6,8 +6,8 @@ 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')
@@ -26,7 +26,6 @@ os.makedirs(PROCESSED_DATA_DIR, exist_ok=True)
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",
@@ -154,7 +153,7 @@ def preprocess_data_file(raw_file_path, pair_name):
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()
@@ -189,7 +188,7 @@ def preprocess_data_file(raw_file_path, pair_name):
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
 
@@ -210,22 +209,20 @@ def load_available_data():
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,7 +231,7 @@ def load_available_data():
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)
@@ -243,28 +240,27 @@ def load_available_data():
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...")
253
  df = preprocess_data_file(raw_file_path, pair_name)
254
  if df is not None:
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()):
@@ -273,24 +269,17 @@ def get_available_pairs():
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():
@@ -298,308 +287,151 @@ def analyze_trading_pair(pair_name: str):
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
330
  fig.add_trace(go.Candlestick(
331
- x=hist.index,
332
- open=hist['Open'],
333
- high=hist['High'],
334
- low=hist['Low'],
335
- close=hist['Close'],
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()
368
 
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):
518
- data_status = gr.Textbox(
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(
594
- fn=analyze_trading_pair,
595
- inputs=pair_input,
596
- outputs=[result_output, price_chart, forecast_chart, forecast_table]
597
- )
598
 
599
- # Launch the app
600
  if __name__ == "__main__":
601
  demo.launch(
602
  server_name="0.0.0.0",
603
  server_port=7860,
604
- share=False
 
605
  )
 
6
  import os
7
  import warnings
8
  import datetime
9
+ import shutil
10
+ import traceback
11
 
12
  # Suppress all warnings for a cleaner output
13
  warnings.filterwarnings('ignore')
 
26
  # Predefined trading pairs with expected formats.
27
  # This dictionary will be dynamically updated in load_available_data.
28
  TRADING_PAIRS = {
 
29
  "EURUSD": {
30
  "description": "Euro to US Dollar Forex Pair (Sample)",
31
  "date_format": "%d.%m.%Y %H:%M:%S.%f %z",
 
153
  if col in df.columns:
154
  df[col] = pd.to_numeric(df[col], errors='coerce')
155
 
156
+ # Fill missing values
157
  for col in ['Open', 'High', 'Low', 'Close']:
158
  if col in df.columns:
159
  missing_before = df[col].isna().sum()
 
188
  return None
189
 
190
  def load_available_data():
191
+ """Load and preprocess all available data files."""
192
  global TRADING_PAIRS, available_data
193
  available_data = {}
194
 
 
209
  print("No CSV files found in 'data/' to move.")
210
 
211
  if not os.path.exists(RAW_DATA_DIR):
212
+ print(f"โŒ Still cannot find raw data directory: {RAW_DATA_DIR}.")
213
  return available_data
214
 
215
  print(f"๐Ÿ” Scanning for data files in {RAW_DATA_DIR}...")
216
 
217
  for filename in os.listdir(RAW_DATA_DIR):
218
  if filename.endswith('.csv'):
 
219
  pair_name = filename.split('.')[0].upper()
220
 
221
+ # Generalization: Add generic config if not exists
 
222
  if pair_name not in TRADING_PAIRS:
223
  TRADING_PAIRS[pair_name] = {
224
  "description": f"{pair_name} Trading Pair (Generic)",
225
+ "date_format": None,
226
  "has_timezone": False,
227
  "decimal_separator": ".",
228
  "required_columns": ["Open", "High", "Low", "Close"]
 
231
  raw_file_path = os.path.join(RAW_DATA_DIR, filename)
232
  processed_file_path = os.path.join(PROCESSED_DATA_DIR, f"{pair_name}_processed.csv")
233
 
234
+ # Check existing processed file
235
  if os.path.exists(processed_file_path):
236
  raw_mod_time = os.path.getmtime(raw_file_path)
237
  processed_mod_time = os.path.getmtime(processed_file_path)
 
240
  try:
241
  df = pd.read_csv(processed_file_path, index_col=0, parse_dates=True)
242
  available_data[pair_name] = df
243
+ print(f"โœ… Using existing preprocessed data for {pair_name}")
244
  continue
245
  except Exception as e:
246
+ print(f"โš ๏ธ Error loading preprocessed file for {pair_name}. Reprocessing.")
247
 
248
+ # Preprocess
249
  print(f"๐Ÿ”„ Processing {pair_name} data...")
250
  df = preprocess_data_file(raw_file_path, pair_name)
251
  if df is not None:
252
  available_data[pair_name] = df
253
+ print(f"โœ… Successfully loaded {pair_name}")
254
  else:
255
+ print(f"โŒ Failed to load {pair_name} data.")
256
 
257
  return available_data
258
 
259
  # --- Analysis Functions ---
260
 
261
  def get_available_pairs():
 
262
  if not available_data:
263
+ return "โš ๏ธ No data files found. Please upload CSV files to 'data/raw'."
264
 
265
  status = "โœ… Available trading pairs:\n"
266
  for pair in sorted(available_data.keys()):
 
269
  if records > 0:
270
  date_range = f"{df.index.min().strftime('%Y-%m-%d')} to {df.index.max().strftime('%Y-%m-%d')}"
271
  status += f"โ€ข {pair}: {records} records ({date_range})\n"
 
 
272
  return status
273
 
274
  def analyze_trading_pair(pair_name: str):
275
+ pair_name = pair_name.upper().strip()
 
 
 
 
 
276
  print(f"\n๐Ÿ” Starting analysis for {pair_name}")
277
 
278
+ # Defaults for error case
279
+ default_error_fig = go.Figure().update_layout(title="Analysis Failed")
280
  default_error_df = gr.DataFrame(headers=["Error"], value=[["Analysis failed"]])
281
 
282
+ # Match pair name
283
  matched_pair = None
284
  for available_pair in available_data.keys():
285
  if pair_name == available_pair or pair_name.upper() == available_pair.upper():
 
287
  break
288
 
289
  if matched_pair is None:
290
+ return f"โŒ Data not available for '{pair_name}'", default_error_fig, default_error_fig, default_error_df
 
 
 
 
291
 
292
  pair_name = matched_pair
293
  hist = available_data[pair_name].copy()
294
 
295
  try:
296
  if len(hist) < 5:
297
+ return f"โŒ Data too short ({len(hist)} records).", default_error_fig, default_error_fig, default_error_df
 
 
 
 
 
 
 
 
 
 
 
 
298
 
299
+ # 1. Candlestick Chart
300
  fig = go.Figure()
 
 
301
  fig.add_trace(go.Candlestick(
302
+ x=hist.index, open=hist['Open'], high=hist['High'],
303
+ low=hist['Low'], close=hist['Close'], name='Price'
 
 
 
 
304
  ))
305
 
 
306
  if len(hist) >= 20:
307
+ hist['MA20'] = hist['Close'].rolling(window=20).mean()
308
+ fig.add_trace(go.Scatter(x=hist.index, y=hist['MA20'], mode='lines', name='20 MA', line=dict(color='blue', width=1.5)))
309
 
310
  if len(hist) >= 50:
311
+ hist['MA50'] = hist['Close'].rolling(window=50).mean()
312
+ fig.add_trace(go.Scatter(x=hist.index, y=hist['MA50'], mode='lines', name='50 MA', line=dict(color='orange', width=1.5)))
313
 
314
  fig.update_layout(
315
+ title=f"{pair_name} Price Analysis", xaxis_title="Date", yaxis_title="Price",
316
+ template="plotly_white", hovermode="x unified", xaxis_rangeslider_visible=False, height=500
 
 
 
 
 
317
  )
318
 
319
+ # 2. Prophet Forecast
320
  forecast_fig = default_error_fig
321
  forecast_table = default_error_df
322
  forecast_result = "No forecast data available"
323
 
324
  try:
 
325
  prophet_df = hist[['Close']].copy().last('365D').reset_index()
326
  prophet_df.columns = ['ds', 'y']
327
  prophet_df = prophet_df.dropna()
328
 
329
+ if len(prophet_df) >= 30:
 
 
 
 
 
 
330
  model = Prophet(
331
+ daily_seasonality=False, yearly_seasonality=True,
332
+ interval_width=0.95, changepoint_prior_scale=0.05,
333
+ stan_backend=None # Critical Fix
 
 
334
  )
335
 
336
+ if (prophet_df['ds'].diff().min().total_seconds() < 86400 * 0.9):
 
337
  model.add_seasonality(name='subdaily', period=1, fourier_order=5, prior_scale=0.1)
338
 
339
  model.fit(prophet_df)
 
 
340
  future = model.make_future_dataframe(periods=30, freq='D')
341
  forecast = model.predict(future)
342
 
343
+ # Forecast Chart
344
  forecast_fig = go.Figure()
 
 
345
  hist_recent = prophet_df[prophet_df['ds'] >= (prophet_df['ds'].max() - pd.Timedelta(days=90))]
346
+ forecast_fig.add_trace(go.Scatter(x=hist_recent['ds'], y=hist_recent['y'], mode='lines', name='History', line=dict(color='blue')))
347
 
 
 
348
  forecast_recent = forecast[forecast['ds'] >= (prophet_df['ds'].max() - pd.Timedelta(days=30))]
349
+ forecast_fig.add_trace(go.Scatter(x=forecast_recent['ds'], y=forecast_recent['yhat'], mode='lines', name='Forecast', line=dict(color='red', dash='dash')))
350
 
 
351
  forecast_fig.add_trace(go.Scatter(
352
  x=forecast_recent['ds'].tolist() + forecast_recent['ds'][::-1].tolist(),
353
  y=forecast_recent['yhat_upper'].tolist() + forecast_recent['yhat_lower'][::-1].tolist(),
354
  fill='toself', fillcolor='rgba(255,0,0,0.1)', line=dict(color='rgba(255,255,255,0)'), name='95% CI'
355
  ))
356
 
357
+ forecast_fig.update_layout(title=f"{pair_name} 30-Day Forecast", template="plotly_white", height=500)
 
 
 
358
 
359
+ # Forecast Table
360
  future_dates = forecast[forecast['ds'] > prophet_df['ds'].max()].head(30)
 
 
361
  future_dates['Trend_Value'] = future_dates['yhat'].diff()
 
 
 
 
 
 
 
 
 
362
 
363
+ # Fix first trend value NaN by comparing to last historical
364
+ if not future_dates.empty:
365
+ future_dates.iloc[0, future_dates.columns.get_loc('Trend_Value')] = future_dates.iloc[0]['yhat'] - prophet_df.iloc[-1]['y']
366
 
367
+ future_dates['Date'] = future_dates['ds'].dt.strftime('%Y-%m-%d')
368
+ future_dates['Price'] = future_dates['yhat'].apply(lambda x: f"{x:.5f}")
369
+ future_dates['Trend'] = future_dates['Trend_Value'].apply(lambda x: "๐Ÿ“ˆ Up" if x > 0 else "๐Ÿ“‰ Down" if x < 0 else "โžก๏ธ Flat")
370
 
371
+ forecast_table = gr.DataFrame(
372
+ headers=["Date", "Price", "Trend"],
373
+ value=future_dates[['Date', 'Price', 'Trend']].values.tolist()
 
 
374
  )
 
375
 
376
+ last_f = forecast.iloc[-1]
377
+ forecast_result = f"๐Ÿ”ฎ Forecast (Day 30): {last_f['yhat']:.5f} (Range: {last_f['yhat_lower']:.5f} - {last_f['yhat_upper']:.5f})"
378
+
379
+ except Exception as e:
380
+ forecast_result = f"โš ๏ธ Forecast Error: {str(e)}"
381
  traceback.print_exc()
382
 
383
+ # 3. Signals
384
  current_price = hist['Close'].iloc[-1]
385
+ signal = "Neutral"
386
+ if 'MA50' in hist.columns:
 
 
387
  ma20 = hist['MA20'].iloc[-1]
388
  ma50 = hist['MA50'].iloc[-1]
389
+ if current_price > ma20 and ma20 > ma50: signal = "๐Ÿš€ STRONG BULLISH"
390
+ elif current_price < ma20 and ma20 < ma50: signal = "๐Ÿ’ฃ STRONG BEARISH"
391
+ elif current_price > ma20: signal = "๐Ÿ“ˆ BULLISH"
392
+ elif current_price < ma20: signal = "๐Ÿ“‰ BEARISH"
393
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
  result_text = (
395
+ f"๐Ÿ“Š **{pair_name} Report**\n{'='*30}\n"
396
+ f"๐Ÿ’ฐ Price: {current_price:.5f}\n"
397
+ f"๐ŸŽฏ Signal: {signal}\n"
 
 
 
 
398
  f"{forecast_result}"
399
  )
400
 
 
401
  return result_text, fig, forecast_fig, forecast_table
402
 
403
  except Exception as e:
404
+ return f"โŒ Error: {str(e)}", default_error_fig, default_error_fig, default_error_df
 
 
 
 
 
 
 
 
 
405
 
406
+ # --- Gradio App ---
407
 
408
+ print("๐Ÿš€ Initializing...")
409
  available_data = load_available_data()
 
410
 
411
+ with gr.Blocks(title="Trading AI") as demo:
412
+ gr.Markdown("# ๐Ÿ“ˆ Trading Pair AI Analysis")
 
 
 
413
 
414
  with gr.Row():
415
+ data_status = gr.Textbox(label="Available Data", value=get_available_pairs(), lines=4)
416
+ pair_input = gr.Textbox(label="Input Pair", value=list(available_data.keys())[0] if available_data else "EURUSD")
417
+ analyze_btn = gr.Button("๐Ÿš€ Analyze", variant="primary")
418
+
419
+ result_output = gr.Textbox(label="Summary", lines=4)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  with gr.Tabs():
422
+ with gr.TabItem("Charts"):
423
+ with gr.Row():
424
+ price_chart = gr.Plot()
425
+ forecast_chart = gr.Plot()
426
+ with gr.TabItem("Table"):
427
+ forecast_table = gr.DataFrame()
428
 
429
+ analyze_btn.click(analyze_trading_pair, inputs=pair_input, outputs=[result_output, price_chart, forecast_chart, forecast_table])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
 
 
431
  if __name__ == "__main__":
432
  demo.launch(
433
  server_name="0.0.0.0",
434
  server_port=7860,
435
+ share=False,
436
+ ssr_mode=False # <--- CRITICAL FIX: Disables SSR to prevent KeyError: 1
437
  )