JayLacoma commited on
Commit
7145874
·
verified ·
1 Parent(s): 7679fa0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +145 -91
app.py CHANGED
@@ -12,6 +12,7 @@ import warnings
12
  import timesfm
13
  from prophet import Prophet
14
 
 
15
  class StockDataFetcher:
16
  """Handles fetching and preprocessing stock data"""
17
 
@@ -274,100 +275,153 @@ def prophet_forecast(ticker, start_date, end_date):
274
  return f"Error: {str(e)}", f"Error: {str(e)}", None
275
 
276
  # Functions for technical analysis
277
- def calculate_sma(df, window):
278
- return df['Close'].rolling(window=window).mean()
279
-
280
- def calculate_ema(df, window):
281
- return df['Close'].ewm(span=window, adjust=False).mean()
282
-
283
- def calculate_macd(df):
284
- short_ema = df['Close'].ewm(span=12, adjust=False).mean()
285
- long_ema = df['Close'].ewm(span=26, adjust=False).mean()
286
- macd = short_ema - long_ema
287
- signal = macd.ewm(span=9, adjust=False).mean()
288
- return macd, signal
289
-
290
- def calculate_rsi(df):
291
- delta = df['Close'].diff()
292
- gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
293
- loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
294
- rs = gain / loss
295
- rsi = 100 - (100 / (1 + rs))
296
- return rsi
297
-
298
- def calculate_bollinger_bands(df):
299
- middle_bb = df['Close'].rolling(window=20).mean()
300
- upper_bb = middle_bb + 2 * df['Close'].rolling(window=20).std()
301
- lower_bb = middle_bb - 2 * df['Close'].rolling(window=20).std()
302
- return middle_bb, upper_bb, lower_bb
303
-
304
- def calculate_stochastic_oscillator(df):
305
- lowest_low = df['Low'].rolling(window=14).min()
306
- highest_high = df['High'].rolling(window=14).max()
307
- slowk = ((df['Close'] - lowest_low) / (highest_high - lowest_low)) * 100
308
- slowd = slowk.rolling(window=3).mean()
309
- return slowk, slowd
310
-
311
- def calculate_cmf(df, window=20):
312
- mfv = ((df['Close'] - df['Low']) - (df['High'] - df['Close'])) / (df['High'] - df['Low']) * df['Volume']
313
- cmf = mfv.rolling(window=window).sum() / df['Volume'].rolling(window=window).sum()
314
- return cmf
315
-
316
- def calculate_cci(df, window=20):
317
- """Calculate Commodity Channel Index (CCI)."""
318
- typical_price = (df['High'] + df['Low'] + df['Close']) / 3
319
- sma = typical_price.rolling(window=window).mean()
320
- mean_deviation = (typical_price - sma).abs().rolling(window=window).mean()
321
- cci = (typical_price - sma) / (0.015 * mean_deviation)
322
- return cci
323
 
324
 
325
- import numpy as np
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
- def generate_trading_signals(df):
328
- # Calculate various indicators
329
- df['SMA_30'] = calculate_sma(df, 30)
330
- df['SMA_100'] = calculate_sma(df, 100)
331
- df['EMA_12'] = calculate_ema(df, 12)
332
- df['EMA_26'] = calculate_ema(df, 26)
333
- df['RSI'] = calculate_rsi(df)
334
- df['MiddleBB'], df['UpperBB'], df['LowerBB'] = calculate_bollinger_bands(df)
335
- df['SlowK'], df['SlowD'] = calculate_stochastic_oscillator(df)
336
- df['CMF'] = calculate_cmf(df)
337
- df['CCI'] = calculate_cci(df)
338
-
339
- # Less strict SMA Signal - Require only 1% difference (was 3%)
340
- df['SMA_Signal'] = np.where((df['SMA_30'] > df['SMA_100']) &
341
- ((df['SMA_30'] - df['SMA_100']) / df['SMA_100'] > 0.01), 1, 0)
342
-
343
- macd, signal = calculate_macd(df)
344
-
345
- # Less strict MACD Signal - Reduce required MACD-signal gap from 1.0 to 0.3
346
- df['MACD_Signal'] = np.select([
347
- (macd > signal) & (macd.shift(1) <= signal.shift(1)) & ((macd - signal) > 0.3), # Bullish crossover → Sell (inverted logic)
348
- (macd < signal) & (macd.shift(1) >= signal.shift(1)) & ((signal - macd) > 0.3) # Bearish crossover → Buy
349
- ], [-1, 1], default=0)
350
-
351
- # Less strict RSI Signal - Use common thresholds (30/70 instead of 15/90)
352
- df['RSI_Signal'] = np.where(df['RSI'] < 30, 1, 0)
353
- df['RSI_Signal'] = np.where(df['RSI'] > 90, -1, df['RSI_Signal'])
354
-
355
- # Less strict Bollinger Bands Signal - Touch or slight breach (no 3% buffer)
356
- df['BB_Signal'] = np.where(df['Close'] <= df['LowerBB'], 1, 0)
357
- df['BB_Signal'] = np.where(df['Close'] >= df['UpperBB'], -1, df['BB_Signal'])
358
-
359
- # Less strict Stochastic Signal - Standard overbought/oversold (20/80 instead of 10/95)
360
- df['Stochastic_Signal'] = np.where((df['SlowK'] < 30) & (df['SlowD'] < 30), 1, 0)
361
- df['Stochastic_Signal'] = np.where((df['SlowK'] > 100) & (df['SlowD'] > 100), -1, df['Stochastic_Signal'])
362
-
363
- # Less strict CMF Signal - Use ±0.1 instead of ±0.4
364
- df['CMF_Signal'] = np.where(df['CMF'] > 0.4, -1, np.where(df['CMF'] < -0.4, 1, 0))
365
-
366
- # Less strict CCI Signal - Standard thresholds (±100 instead of ±220)
367
- df['CCI_Signal'] = np.where(df['CCI'] < -100, 1, 0)
368
- df['CCI_Signal'] = np.where(df['CCI'] > 120, -1, df['CCI_Signal'])
369
-
370
- # Combined signal (still sums all component signals)
371
  df['Combined_Signal'] = df[['MACD_Signal', 'RSI_Signal', 'BB_Signal',
372
  'Stochastic_Signal', 'CMF_Signal', 'CCI_Signal']].sum(axis=1)
373
 
 
12
  import timesfm
13
  from prophet import Prophet
14
 
15
+
16
  class StockDataFetcher:
17
  """Handles fetching and preprocessing stock data"""
18
 
 
275
  return f"Error: {str(e)}", f"Error: {str(e)}", None
276
 
277
  # Functions for technical analysis
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
 
280
+ def smooth_moving_average(series: pd.Series, window: int) -> pd.Series:
281
+ if len(series) < window or window <= 0:
282
+ return pd.Series(series.mean(), index=series.index)
283
+ result = pd.Series(index=series.index, dtype=float)
284
+ result.iloc[:window] = series.iloc[:window].mean()
285
+ for i in range(window, len(series)):
286
+ result.iloc[i] = (result.iloc[i-1] * (window - 1) + series.iloc[i]) / window
287
+ return result.ffill().bfill().fillna(series.mean())
288
+
289
+ def calculate_rsi(close: pd.Series, window: int = 14) -> pd.Series:
290
+ if len(close) <= window:
291
+ return pd.Series(50.0, index=close.index)
292
+ delta = close.diff()
293
+ gain = delta.where(delta > 0, 0.0)
294
+ loss = -delta.where(delta < 0, 0.0)
295
+ avg_gain = smooth_moving_average(gain, window)
296
+ avg_loss = smooth_moving_average(loss, window)
297
+ rs = np.where(avg_loss != 0, avg_gain / avg_loss, np.inf)
298
+ rsi = 100.0 - (100.0 / (1.0 + rs))
299
+ return pd.Series(rsi, index=close.index).replace([np.inf, -np.inf], np.nan).ffill().bfill().fillna(50.0)
300
+
301
+ def calculate_stochastic(high: pd.Series, low: pd.Series, close: pd.Series, k_window=14, d_window=3):
302
+ if len(close) < k_window:
303
+ return pd.Series(50.0, index=close.index), pd.Series(50.0, index=close.index)
304
+ lowest = low.rolling(k_window, min_periods=1).min()
305
+ highest = high.rolling(k_window, min_periods=1).max()
306
+ k_pct = ((close - lowest) / (highest - lowest + 1e-10)) * 100
307
+ k_pct = k_pct.clip(0, 100)
308
+ d_pct = k_pct.rolling(d_window, min_periods=1).mean()
309
+ return k_pct.ffill().bfill().fillna(50.0), d_pct.ffill().bfill().fillna(50.0)
310
+
311
+ def calculate_cci(high: pd.Series, low: pd.Series, close: pd.Series, window=20):
312
+ if len(close) < window:
313
+ return pd.Series(0.0, index=close.index)
314
+ typical_price = (high + low + close) / 3.0
315
+ sma = typical_price.rolling(window, min_periods=1).mean()
316
+ mean_deviation = (typical_price - sma).abs().rolling(window, min_periods=1).mean()
317
+ cci = (typical_price - sma) / (0.015 * mean_deviation + 1e-10)
318
+ return cci.ffill().bfill().fillna(0.0)
319
+
320
+ # --- New Robust Helper Functions ---
321
+ def calculate_sma_robust(series: pd.Series, window: int) -> pd.Series:
322
+ if len(series) < window or window <= 0:
323
+ return pd.Series(series.mean(), index=series.index)
324
+ return series.rolling(window=window, min_periods=window).mean().ffill().bfill().fillna(series.mean())
325
+
326
+ def calculate_ema_robust(series: pd.Series, span: int) -> pd.Series:
327
+ if len(series) < span or span <= 0:
328
+ return pd.Series(series.mean(), index=series.index)
329
+ return series.ewm(span=span, adjust=False, min_periods=span).mean().ffill().bfill().fillna(series.mean())
330
+
331
+ def calculate_macd_robust(close: pd.Series):
332
+ ema12 = calculate_ema_robust(close, 12)
333
+ ema26 = calculate_ema_robust(close, 26)
334
+ macd_line = ema12 - ema26
335
+ signal_line = calculate_ema_robust(macd_line, 9)
336
+ return macd_line, signal_line
337
+
338
+ def calculate_bollinger_bands_robust(close: pd.Series, window=20, num_std=2.0):
339
+ if len(close) < window:
340
+ mid = pd.Series(close.mean(), index=close.index)
341
+ return mid, mid, mid
342
+ sma = calculate_sma_robust(close, window)
343
+ std = close.rolling(window=window, min_periods=window).std().fillna(1e-10)
344
+ upper = sma + num_std * std
345
+ lower = sma - num_std * std
346
+ return sma.ffill().bfill(), upper.ffill().bfill(), lower.ffill().bfill()
347
+
348
+ # --- The Core Integration: generate_trading_signals ---
349
+ def generate_trading_signals(df: pd.DataFrame) -> pd.DataFrame:
350
+ """
351
+ Generates trading signals using strict thresholds to minimize false positives.
352
+ Output columns match the expected names for the plotting functions.
353
+ """
354
+ df = df.copy()
355
+ close = df['Close']
356
+ has_hl = all(col in df.columns for col in ['High', 'Low'])
357
+ has_vol = 'Volume' in df.columns
358
+
359
+ high = df['High'] if has_hl else close
360
+ low = df['Low'] if has_hl else close
361
+ volume = df['Volume'] if has_vol else pd.Series(1.0, index=close.index)
362
+
363
+ # Calculate indicators using robust methods
364
+ rsi = calculate_rsi(close, window=14)
365
+ stoch_k, stoch_d = calculate_stochastic(high, low, close, k_window=14, d_window=3)
366
+ cci = calculate_cci(high, low, close, window=20)
367
+ sma30 = calculate_sma_robust(close, 30)
368
+ sma100 = calculate_sma_robust(close, 100)
369
+ macd_line, macd_signal_line = calculate_macd_robust(close)
370
+ _, bb_upper, bb_lower = calculate_bollinger_bands_robust(close, window=20, num_std=2.5)
371
+
372
+ # CMF Calculation
373
+ if has_hl and has_vol:
374
+ mfv = ((close - low) - (high - close)) / (high - low + 1e-10) * volume
375
+ cmf = mfv.rolling(window=20, min_periods=20).sum() / (volume.rolling(window=20, min_periods=20).sum() + 1e-10)
376
+ cmf = cmf.ffill().bfill().fillna(0.0)
377
+ else:
378
+ cmf = pd.Series(0.0, index=close.index)
379
+
380
+ # --- STRICT SIGNAL LOGIC (Output matches old function's schema) ---
381
+
382
+ # Initialize all signal columns to 0
383
+ for col in ['MACD_Signal', 'RSI_Signal', 'BB_Signal', 'Stochastic_Signal', 'CMF_Signal', 'CCI_Signal']:
384
+ df[col] = 0
385
+
386
+ # 1. MACD Signal
387
+ macd_bull = (
388
+ (macd_line > macd_signal_line) &
389
+ (macd_line.shift(1) <= macd_signal_line.shift(1)) &
390
+ (macd_line > 0.5) &
391
+ ((macd_line - macd_signal_line) > 0.8)
392
+ )
393
+ macd_bear = (
394
+ (macd_line < macd_signal_line) &
395
+ (macd_line.shift(1) >= macd_signal_line.shift(1)) &
396
+ (macd_line < -0.5) &
397
+ ((macd_signal_line - macd_line) > 0.8)
398
+ )
399
+ df.loc[macd_bull, 'MACD_Signal'] = 1
400
+ df.loc[macd_bear, 'MACD_Signal'] = -1
401
+
402
+ # 2. RSI Signal
403
+ df.loc[rsi < 15, 'RSI_Signal'] = 1
404
+ df.loc[rsi > 85, 'RSI_Signal'] = -1
405
+
406
+ # 3. Bollinger Bands Signal
407
+ df.loc[close <= bb_lower, 'BB_Signal'] = 1
408
+ df.loc[close >= bb_upper, 'BB_Signal'] = -1
409
+
410
+ # 4. Stochastic Signal
411
+ stoch_buy = (stoch_k < 5) & (stoch_d < 5)
412
+ stoch_sell = (stoch_k > 95) & (stoch_d > 95)
413
+ df.loc[stoch_buy, 'Stochastic_Signal'] = 1
414
+ df.loc[stoch_sell, 'Stochastic_Signal'] = -1
415
+
416
+ # 5. CMF Signal
417
+ df.loc[cmf < -0.5, 'CMF_Signal'] = 1
418
+ df.loc[cmf > 0.5, 'CMF_Signal'] = -1
419
+
420
+ # 6. CCI Signal
421
+ df.loc[cci < -250, 'CCI_Signal'] = 1
422
+ df.loc[cci > 250, 'CCI_Signal'] = -1
423
 
424
+ # Create the Combined_Signal by summing the individual signals
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  df['Combined_Signal'] = df[['MACD_Signal', 'RSI_Signal', 'BB_Signal',
426
  'Stochastic_Signal', 'CMF_Signal', 'CCI_Signal']].sum(axis=1)
427