import yfinance as yf import pandas as pd import numpy as np import plotly.graph_objs as go from sklearn.preprocessing import MinMaxScaler from keras.src.models.sequential import Sequential from keras.src.layers import LSTM, Dense import streamlit as st from keras.src.callbacks import EarlyStopping # Fetch stock data def fetch_data(ticker): return yf.download(ticker, period='6mo') # Add indicators def add_indicators(df): df['SMA_20'] = df['Close'].rolling(window=20).mean() df['SMA_50'] = df['Close'].rolling(window=50).mean() std_20 = df['Close'].rolling(window=20).std() std_20 = std_20.squeeze() # ensure it's a Series df['BB_Upper'] = df['SMA_20'] + 2 * std_20 df['BB_Lower'] = df['SMA_20'] - 2 * std_20 df['RSI'] = compute_rsi(df['Close']) df['MACD'], df['MACD_Signal'], df['MACD_Hist'] = compute_macd(df['Close']) return df # RSI calculation def compute_rsi(series, period=14): delta = series.diff() gain = delta.clip(lower=0) loss = -delta.clip(upper=0) avg_gain = gain.rolling(period).mean() avg_loss = loss.rolling(period).mean() rs = avg_gain / avg_loss return 100 - (100 / (1 + rs)) # MACD calculation def compute_macd(series): ema12 = series.ewm(span=12, adjust=False).mean() ema26 = series.ewm(span=26, adjust=False).mean() macd = ema12 - ema26 signal = macd.ewm(span=9, adjust=False).mean() hist = macd - signal return macd, signal, hist # --- Detect candlestick patterns manually --- def detect_patterns(df, sma_period=5): patterns = [] hover_texts = [] # Calculate Simple Moving Average for trend detection df['SMA'] = df['Close'].rolling(window=sma_period).mean() sma = df.Close.rolling(window=sma_period).mean() # Helper functions for trend direction, with NaN handling def is_downtrend(i): sma = df.iloc[i].SMA if sma is None: return False return (df['Close'].iloc[i] < df['SMA'].iloc[i]).bool() def is_uptrend(i): sma = df.iloc[i].SMA if sma is None: return False return (df['Close'].iloc[i] > df['SMA'].iloc[i]).bool() for i in range(2, len(df)): # Skip if not enough data for trend analysis if pd.isna(df['SMA'].iloc[i]): patterns.append("None") hover_texts.append("") continue candle = df.iloc[i] o = candle.Open.values h = candle.High.values l = candle.Low.values c = candle.Close.values # Previous candle prev_candle = df.iloc[i-1] prev_o = prev_candle.Open.values prev_h = prev_candle.High.values prev_l = prev_candle.Low.values prev_c = prev_candle.Close.values # Two candles back prev_candle2 = df.iloc[i-2] prev2_o = prev_candle2.Open.values prev2_c = prev_candle2.Close.values prev2_h = prev_candle2.High.values prev2_l = prev_candle2.Low.values # Calculations body = abs(c - o) upper_shadow = h - max(o, c) lower_shadow = min(o, c) - l total_range = h - l if h != l else 0.0001 prev_body = abs(prev_c - prev_o) pattern = "" # === Pattern Detection === lowShadBiggerBody = lower_shadow >= 2 * body upShadSmallerBody = upper_shadow <= body closeBiggerOpen = c > o openBiggerClose = c < o downTrend = is_downtrend(i - 1) upTrend = is_uptrend(i - 1) upperShadBiggerBody = upper_shadow >= 2 * body lowShadSmallerBody = lower_shadow <= body prevCloseSmallerPrevOpen = prev_c < prev_o openBiggerPrevClose = o >= prev_c openSmallerPrevClose = o <= prev_c closeBiggerPrevOpen = c >= prev_o closeSmallerPrevOpen = c <= prev_o if body <= 0.1 * total_range: pattern = "Doji" elif (lowShadBiggerBody & upShadSmallerBody & closeBiggerOpen & downTrend): pattern = "Hammer" elif (upperShadBiggerBody & lowShadSmallerBody & closeBiggerOpen & downTrend): pattern = "Inverted Hammer" elif (prevCloseSmallerPrevOpen & closeBiggerOpen & openSmallerPrevClose & closeBiggerPrevOpen & (body > prev_body) & downTrend): pattern = "Bullish Engulfing" elif ((prev_c > prev_o) & openBiggerClose & openBiggerPrevClose & closeSmallerPrevOpen & (body > prev_body) & upTrend): pattern = "Bearish Engulfing" elif ((upper_shadow >= 1.5 * body) & (lower_shadow <= 0.2 * body) & openBiggerClose & upTrend): pattern = "Shooting Star" elif ((lower_shadow >= 2 * body) & (upper_shadow <= 0.2 * body) & openBiggerClose & upTrend): pattern = "Hanging Man" elif (openBiggerClose & (body >= 0.6 * total_range) & (upper_shadow <= 0.15 * total_range) & (lower_shadow <= 0.15 * total_range)): pattern = "Dark Pool" elif ((prev2_c < prev2_o) & (abs(prev_c - prev_o) <= 0.3 * (prev_h - prev_l)) & closeBiggerOpen & (c > (prev_o + prev_c) / 2) & downTrend): pattern = "Morning Star" elif ((prev2_c > prev2_o) & (abs(prev_c - prev_o) <= 0.3 * (prev_h - prev_l)) & openBiggerClose & (c < (prev_o + prev_c) / 2) & upTrend): pattern = "Evening Star" else: pattern = "None" hover_text = PATTERN_DESCRIPTIONS.get(pattern, "") if pattern != "None" else "" patterns.append(pattern) hover_texts.append(hover_text) # Align output with sliced data df = df.iloc[2:].copy() df['Pattern'] = patterns df['HoverText'] = hover_texts df.drop(columns=['SMA'], inplace=True) return df PATTERN_DESCRIPTIONS = { "Hammer": "Bullish reversal pattern after a downtrend.", "Inverted Hammer": "Potential bullish reversal with a long upper wick.", "Bullish Engulfing": "Strong bullish signal after a bearish candle.", "Bearish Engulfing": "Strong bearish signal after a bullish candle.", "Doji": "Market indecision; open and close are very close.", "Shooting Star": "Bearish reversal after uptrend with long upper wick.", "Hanging Man": "Bearish signal with long lower shadow after uptrend.", "Dark Pool": "Heavy bearish candle with short shadows.", "Morning Star": "Bullish 3-candle reversal pattern.", "Evening Star": "Bearish 3-candle reversal pattern." } # Predict next close price using LSTM def predict_next_price(df): # Select features features = ['Close', 'SMA_20', 'SMA_50', 'RSI', 'MACD', 'MACD_Signal'] # Drop NaNs caused by rolling indicators df = df[features].dropna() if len(df) < 25: return None # not enough data # Scale all features scaler = MinMaxScaler() scaled_data = scaler.fit_transform(df) lookback = 20 X = [] y = [] for i in range(lookback, len(scaled_data)): X.append(scaled_data[i-lookback:i]) # shape: (lookback, num_features) y.append(scaled_data[i, 0]) # predict 'Close' X = np.array(X) y = np.array(y) # 🧠 Build model that takes multiple features model = Sequential() model.add(LSTM(64, return_sequences=True, input_shape=(lookback, len(features)))) model.add(LSTM(32)) model.add(Dense(1)) model.compile(optimizer='adam', loss='mean_squared_error') model.fit(X, y, epochs=20, batch_size=8, verbose=0) # Predict next day last_sequence = scaled_data[-lookback:] last_sequence = np.reshape(last_sequence, (1, lookback, len(features))) predicted_scaled = model.predict(last_sequence, verbose=0) # 🔄 Inverse-transform: only the 'Close' feature dummy = np.zeros((1, len(features))) dummy[0, 0] = predicted_scaled[0, 0] predicted_price = scaler.inverse_transform(dummy)[0, 0] return predicted_price # Chart generator def plot_chart(df, ticker): fig = go.Figure() fig.add_trace(go.Scatter(x=df.index, y=df['Close'], name='Close', line=dict(color='blue'))) fig.add_trace(go.Scatter(x=df.index, y=df['SMA_20'], name='SMA 20', line=dict(color='orange'))) fig.add_trace(go.Scatter(x=df.index, y=df['SMA_50'], name='SMA 50', line=dict(color='purple'))) fig.add_trace(go.Scatter(x=df.index, y=df['BB_Upper'], name='BB Upper', line=dict(color='green', dash='dot'))) fig.add_trace(go.Scatter(x=df.index, y=df['BB_Lower'], name='BB Lower', line=dict(color='red', dash='dot'))) fig.update_layout(title=f"{ticker} Price with Indicators", xaxis_title="Date", yaxis_title="Price") return fig def plot_macd(df): fig = go.Figure() fig.add_trace(go.Scatter(x=df.index, y=df['MACD'], name='MACD', line=dict(color='blue'))) fig.add_trace(go.Scatter(x=df.index, y=df['MACD_Signal'], name='Signal', line=dict(color='orange'))) fig.add_trace(go.Bar(x=df.index, y=df['MACD_Hist'], name='Histogram', marker_color='gray')) fig.update_layout(title='MACD line above the signal line -> (buy) signal ⬆️, MACD line below the signal line -> (sell) signal ⬇️', xaxis_title='Date', yaxis_title='MACD') return fig # Streamlit dashboard st.set_page_config("📈 Smart Stock Analyzer", layout="wide") st.title("📊 Smart Stock Analyzer with AI & Indicators") tickers_input = st.text_input("Enter tickers (comma-separated):", value="QNTM") tickers = [t.strip().upper() for t in tickers_input.split(",") if t.strip()] for ticker in tickers: st.header(f"🔍 Analyzing {ticker}") try: df = fetch_data(ticker) df = df.copy() patterns = detect_patterns(df, sma_period=5) df = add_indicators(df) current_price = df['Close'].iloc[-1].values[0] predicted_price = predict_next_price(df) col1, col2 = st.columns(2) with col1: st.write(patterns[patterns['Pattern'] != ''].tail(10)) #st.plotly_chart(plot_chart(df, ticker), use_container_width=True) with col2: st.plotly_chart(plot_macd(df), use_container_width=True) st.subheader("📈 Summary") rsi = df['RSI'].iloc[-1] st.markdown(f"- **RSI (above 70 = overbought conditions, below 30 = oversold conditions)**: {rsi:.2f} {'🟢 BUY' if rsi < 30 else '🔴 SELL' if rsi > 70 else '🟡 HOLD'}") st.markdown(f"- **SMA Crossover (potentially signaling a shift in trend direction)**: {'⬆️ Bullish' if df['SMA_20'].iloc[-1] > df['SMA_50'].iloc[-1] else '⬇️ Bearish'}") st.subheader("🔮 AI Price Prediction") st.markdown(f"- **Current Price**: ${current_price:.2f}") st.markdown(f"- **Predicted Next Close**: ${predicted_price:.2f}") change = predicted_price - current_price percent = (change / current_price) * 100 if percent > 3: st.success("🚀 AI says: Likely UP (>3%) – Consider BUYING") elif percent < -3: st.error("⚠️ AI says: Likely DOWN (>3%) – Consider SELLING") else: st.info("💤 AI says: Not much movement expected – HOLD") except Exception as e: st.error(f"Failed to analyze {ticker}: {e}")