stock_analyzer / app.py
tranquilTrill's picture
Update app.py
ecee59d verified
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}")