JayLacoma commited on
Commit
cd97129
·
verified ·
1 Parent(s): 3863d58

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +37 -15
app.py CHANGED
@@ -82,7 +82,7 @@ class CacheManager:
82
  df[col] = pd.to_datetime(df[col])
83
  # If the first column is the index, set it
84
  if 'Date' in df.columns and df.columns[0] == 'Date':
85
- df.set_index('Date', inplace=True)
86
  return df
87
  except Exception:
88
  return None
@@ -121,7 +121,7 @@ class StockAnalyzer:
121
  cache_file = f"{self.ticker}_sentiment.csv"
122
  df_daily = CacheManager.load_df(cache_file)
123
  if df_daily is not None and not self.force_refresh:
124
- return df_daily, None # Detailed news_df not needed from cache
125
 
126
  end_date = datetime.now()
127
  start_date = end_date - timedelta(days=Config.SENTIMENT_DAYS)
@@ -219,6 +219,8 @@ class StockAnalyzer:
219
  @staticmethod
220
  def _calculate_indicators(df: pd.DataFrame) -> pd.DataFrame:
221
  """Calculates a suite of technical indicators."""
 
 
222
  # RSI
223
  delta = df['Close'].diff()
224
  gain = (delta.where(delta > 0, 0)).rolling(14).mean()
@@ -238,9 +240,20 @@ class StockAnalyzer:
238
  df['SlowK'] = ((df['Close'] - ll) / (hh - ll)) * 100
239
  df['SlowD'] = df['SlowK'].rolling(3).mean()
240
 
241
- # Chaikin Money Flow (CMF)
242
- mfv = ((df['Close'] - df['Low']) - (df['High'] - df['Close'])) / (df['High'] - df['Low']) * df['Volume']
243
- df['CMF'] = mfv.rolling(20).sum() / df['Volume'].rolling(20).sum()
 
 
 
 
 
 
 
 
 
 
 
244
 
245
  # Signals (using stricter thresholds)
246
  df['RSI_Signal'] = np.where(df['RSI'] < 20, 1, np.where(df['RSI'] > 80, -1, 0))
@@ -248,6 +261,7 @@ class StockAnalyzer:
248
  df['Stochastic_Signal'] = np.where((df['SlowK'] < 15) & (df['SlowD'] < 15), 1, np.where((df['SlowK'] > 85) & (df['SlowD'] > 85), -1, 0))
249
  df['CMF_Signal'] = np.where(df['CMF'] < -0.25, 1, np.where(df['CMF'] > 0.25, -1, 0))
250
  df['Technical_Score'] = df[['RSI_Signal', 'BB_Signal', 'Stochastic_Signal', 'CMF_Signal']].sum(axis=1)
 
251
  return df
252
 
253
  # ============================================================================
@@ -342,9 +356,9 @@ class Plotter:
342
  # ============================================================================
343
  def run_full_analysis(tickers_str: str, time_range: str, show_bollinger: bool, force_refresh: bool, progress=gr.Progress()):
344
  """Main function triggered by the Gradio button."""
345
- tickers = [t.strip().upper() for t in tickers_str.split(',') if t.strip()][:5] # Limit to 5 tickers
346
  if not tickers:
347
- return "Please enter at least one ticker.", None, gr.Accordion(visible=False)
348
 
349
  progress(0, desc="Starting analysis...")
350
  all_results = {}
@@ -353,14 +367,14 @@ def run_full_analysis(tickers_str: str, time_range: str, show_bollinger: bool, f
353
  try:
354
  analyzer = StockAnalyzer(ticker, force_refresh)
355
  if analyzer.tech_df.empty:
356
- continue # Skip if no data
357
  all_results[ticker] = analyzer
358
  except Exception as e:
359
  print(f"Error analyzing {ticker}: {e}")
360
  continue
361
 
362
  if not all_results:
363
- return "Could not retrieve data for the entered tickers.", None, gr.Accordion(visible=False)
364
 
365
  # 1. Create the comparative multi-ticker plot
366
  multi_plot = Plotter.create_multi_ticker_plot(
@@ -372,11 +386,13 @@ def run_full_analysis(tickers_str: str, time_range: str, show_bollinger: bool, f
372
  accordion_items = []
373
  for ticker, analyzer in all_results.items():
374
  # Summary Tab Content
 
 
375
  summary_md = f"""
376
  ### 🎯 Decision: **{analyzer.decision}** (Score: {analyzer.total_score}/6)
377
- - **Current Price:** `${analyzer.tech_df['Close'].iloc[-1]:.2f}`
378
  - **Technical Score:** `{analyzer.scores['Technical']}`
379
- - **Sentiment Score:** `{analyzer.scores['Sentiment']}` (Avg: {analyzer.sentiment_daily['avg_sentiment'].mean():.2f})
380
  - **Forecast Score:** `{analyzer.scores['Forecast']}` ({analyzer.forecast_pct:.1f}% change to `${analyzer.forecast_price:.2f}`)
381
  """
382
  gauge_plot = Plotter.create_decision_gauge(analyzer.decision, analyzer.total_score)
@@ -394,16 +410,22 @@ def run_full_analysis(tickers_str: str, time_range: str, show_bollinger: bool, f
394
  # Assemble the accordion item
395
  ticker_accordion = gr.Accordion(
396
  label=f"📊 {ticker} Analysis",
397
- open=ticker == tickers[0] # Open the first one by default
398
  )
399
  with ticker_accordion:
400
  with gr.Tabs():
401
  with gr.TabItem("📈 Summary & Decision"):
402
  summary_col.render()
403
  with gr.TabItem("😊 Sentiment Analysis"):
404
- gr.Plot(sentiment_plot).render() if isinstance(sentiment_plot, go.Figure) else sentiment_plot.render()
 
 
 
405
  with gr.TabItem("🔮 Forecast"):
406
- gr.Plot(forecast_plot).render() if isinstance(forecast_plot, go.Figure) else forecast_plot.render()
 
 
 
407
  accordion_items.append(ticker_accordion)
408
 
409
  progress(1, "Analysis complete!")
@@ -411,7 +433,7 @@ def run_full_analysis(tickers_str: str, time_range: str, show_bollinger: bool, f
411
 
412
 
413
  # --- Build the Gradio App ---
414
- with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="blue"), title="Stock Intelligence Dashboard") as demo:
415
  gr.Markdown("# 📊 Unified Stock Intelligence Dashboard")
416
  gr.Markdown("An advanced tool for technical, sentiment, and predictive analysis of stocks.")
417
 
 
82
  df[col] = pd.to_datetime(df[col])
83
  # If the first column is the index, set it
84
  if 'Date' in df.columns and df.columns[0] == 'Date':
85
+ df.set_index('Date', inplace=True)
86
  return df
87
  except Exception:
88
  return None
 
121
  cache_file = f"{self.ticker}_sentiment.csv"
122
  df_daily = CacheManager.load_df(cache_file)
123
  if df_daily is not None and not self.force_refresh:
124
+ return df_daily, None # Detailed news_df not needed from cache
125
 
126
  end_date = datetime.now()
127
  start_date = end_date - timedelta(days=Config.SENTIMENT_DAYS)
 
219
  @staticmethod
220
  def _calculate_indicators(df: pd.DataFrame) -> pd.DataFrame:
221
  """Calculates a suite of technical indicators."""
222
+ df = df.copy() # Avoid modifying original
223
+
224
  # RSI
225
  delta = df['Close'].diff()
226
  gain = (delta.where(delta > 0, 0)).rolling(14).mean()
 
240
  df['SlowK'] = ((df['Close'] - ll) / (hh - ll)) * 100
241
  df['SlowD'] = df['SlowK'].rolling(3).mean()
242
 
243
+ # Chaikin Money Flow (CMF) — FIXED SECTION
244
+ price_range = df['High'] - df['Low']
245
+ # Avoid division by zero
246
+ price_range = price_range.replace(0, np.nan)
247
+ mfv = ((df['Close'] - df['Low']) - (df['High'] - df['Close'])) / price_range * df['Volume']
248
+
249
+ mfv_sum = mfv.rolling(20).sum()
250
+ vol_sum = df['Volume'].rolling(20).sum()
251
+
252
+ # Use .values to prevent pandas alignment errors
253
+ cmf_raw = mfv_sum.values / vol_sum.values
254
+ # Replace inf/-inf with NaN
255
+ cmf_clean = np.where(np.isfinite(cmf_raw), cmf_raw, np.nan)
256
+ df['CMF'] = cmf_clean
257
 
258
  # Signals (using stricter thresholds)
259
  df['RSI_Signal'] = np.where(df['RSI'] < 20, 1, np.where(df['RSI'] > 80, -1, 0))
 
261
  df['Stochastic_Signal'] = np.where((df['SlowK'] < 15) & (df['SlowD'] < 15), 1, np.where((df['SlowK'] > 85) & (df['SlowD'] > 85), -1, 0))
262
  df['CMF_Signal'] = np.where(df['CMF'] < -0.25, 1, np.where(df['CMF'] > 0.25, -1, 0))
263
  df['Technical_Score'] = df[['RSI_Signal', 'BB_Signal', 'Stochastic_Signal', 'CMF_Signal']].sum(axis=1)
264
+
265
  return df
266
 
267
  # ============================================================================
 
356
  # ============================================================================
357
  def run_full_analysis(tickers_str: str, time_range: str, show_bollinger: bool, force_refresh: bool, progress=gr.Progress()):
358
  """Main function triggered by the Gradio button."""
359
+ tickers = [t.strip().upper() for t in tickers_str.split(',') if t.strip()][:5] # Limit to 5 tickers
360
  if not tickers:
361
+ return "Please enter at least one ticker.", None, gr.Column(visible=False)
362
 
363
  progress(0, desc="Starting analysis...")
364
  all_results = {}
 
367
  try:
368
  analyzer = StockAnalyzer(ticker, force_refresh)
369
  if analyzer.tech_df.empty:
370
+ continue # Skip if no data
371
  all_results[ticker] = analyzer
372
  except Exception as e:
373
  print(f"Error analyzing {ticker}: {e}")
374
  continue
375
 
376
  if not all_results:
377
+ return "Could not retrieve data for the entered tickers.", None, gr.Column(visible=False)
378
 
379
  # 1. Create the comparative multi-ticker plot
380
  multi_plot = Plotter.create_multi_ticker_plot(
 
386
  accordion_items = []
387
  for ticker, analyzer in all_results.items():
388
  # Summary Tab Content
389
+ current_price = analyzer.tech_df['Close'].iloc[-1] if not analyzer.tech_df.empty else 0.0
390
+ avg_sent = analyzer.sentiment_daily['avg_sentiment'].mean() if analyzer.sentiment_daily is not None else 0.0
391
  summary_md = f"""
392
  ### 🎯 Decision: **{analyzer.decision}** (Score: {analyzer.total_score}/6)
393
+ - **Current Price:** `${current_price:.2f}`
394
  - **Technical Score:** `{analyzer.scores['Technical']}`
395
+ - **Sentiment Score:** `{analyzer.scores['Sentiment']}` (Avg: {avg_sent:.2f})
396
  - **Forecast Score:** `{analyzer.scores['Forecast']}` ({analyzer.forecast_pct:.1f}% change to `${analyzer.forecast_price:.2f}`)
397
  """
398
  gauge_plot = Plotter.create_decision_gauge(analyzer.decision, analyzer.total_score)
 
410
  # Assemble the accordion item
411
  ticker_accordion = gr.Accordion(
412
  label=f"📊 {ticker} Analysis",
413
+ open=ticker == tickers[0] # Open the first one by default
414
  )
415
  with ticker_accordion:
416
  with gr.Tabs():
417
  with gr.TabItem("📈 Summary & Decision"):
418
  summary_col.render()
419
  with gr.TabItem("😊 Sentiment Analysis"):
420
+ if isinstance(sentiment_plot, go.Figure):
421
+ gr.Plot(sentiment_plot).render()
422
+ else:
423
+ sentiment_plot.render()
424
  with gr.TabItem("🔮 Forecast"):
425
+ if isinstance(forecast_plot, go.Figure):
426
+ gr.Plot(forecast_plot).render()
427
+ else:
428
+ forecast_plot.render()
429
  accordion_items.append(ticker_accordion)
430
 
431
  progress(1, "Analysis complete!")
 
433
 
434
 
435
  # --- Build the Gradio App ---
436
+ with gr.Blocks(theme=gr.themes.Default(primary_hue="black", secondary_hue="purple"), title="Stock Intelligence Dashboard") as demo:
437
  gr.Markdown("# 📊 Unified Stock Intelligence Dashboard")
438
  gr.Markdown("An advanced tool for technical, sentiment, and predictive analysis of stocks.")
439