import streamlit as st import yfinance as yf import pandas as pd import matplotlib.pyplot as plt import mplfinance as mpf import numpy as np import os from datetime import date, timedelta from langchain_openai import ChatOpenAI isPswdValid = False # Set to True to temporarily disable password checking. For production set to False. OPEN_ROUTER_KEY = st.secrets["OPEN_ROUTER_KEY"] OPEN_ROUTER_MODEL = "meta-llama/llama-3.3-70b-instruct:free" query_params = st.experimental_get_query_params() try: pswdVal = query_params['pwd'][0] # No need to worry about warning here as this will not be displayed in hg if pswdVal==st.secrets["PSWD"]: isPswdValid = True except: pass if not isPswdValid: st.write("Invalid Password") else: # Initialize language model llm = ChatOpenAI(model=OPEN_ROUTER_MODEL, temperature=0.1, openai_api_key=OPEN_ROUTER_KEY, openai_api_base="https://openrouter.ai/api/v1") # Set the Streamlit app title and icon st.set_page_config(page_title="Stock Analysis", page_icon="📈", layout="wide", initial_sidebar_state="expanded") # Global styling for a cleaner, modern look st.markdown( """ """, unsafe_allow_html=True, ) # Create a Streamlit sidebar for user input st.sidebar.title("Stock Analysis") ticker_symbol = st.sidebar.text_input("Enter Stock Ticker Symbol:", value='AAPL').strip().upper() default_end = date.today() - timedelta(days=1) default_start = default_end - timedelta(days=365 * 3) start_date = st.sidebar.date_input("Start Date", default_start) end_date = st.sidebar.date_input("End Date", default_end) st.sidebar.markdown("---") st.sidebar.caption("Tip: Choose a wide date range for smoother moving averages and richer AI insights.") def load_stock_data(symbol: str, start_dt, end_dt): """Download data with a flat schema to avoid Plotly/MultiIndex issues on some runtimes.""" try: ticker = yf.Ticker(symbol) data = ticker.history(start=start_dt, end=end_dt, actions=False, auto_adjust=False) except Exception: st.error("Error fetching stock data. Please check the ticker symbol and date range.") st.stop() if data.empty: st.error("No data returned. Try widening the date range or verifying the ticker.") st.stop() if isinstance(data.columns, pd.MultiIndex): data = data.copy() data.columns = data.columns.get_level_values(-1) data = data.reset_index() # Ensure Date is a column data["Date"] = pd.to_datetime(data["Date"]).dt.tz_localize(None) core_cols = ["Open", "High", "Low", "Close", "Volume"] if data[core_cols].dropna(how="all").empty: st.error("Downloaded data is empty or invalid. Try another ticker or date range.") st.stop() return data # Fetch stock data from Yahoo Finance df = load_stock_data(ticker_symbol, start_date, end_date) close_series = df['Close'] high_series = df['High'] low_series = df['Low'] volume_series = df['Volume'] debug_mode = query_params.get("debug", ["0"])[0] == "1" latest_close = close_series.iloc[-1] if not close_series.empty else None prev_close = close_series.iloc[-2] if len(close_series) > 1 else None change = (latest_close - prev_close) if latest_close is not None and prev_close is not None else None change_pct = (change / prev_close * 100) if change not in [None, 0] and prev_close not in [None, 0] else None period_high = high_series.max() if not high_series.empty else None period_low = low_series.min() if not low_series.empty else None avg_volume = volume_series.mean() if not volume_series.empty else None def fmt_currency(val): return "-" if val is None or pd.isna(val) else f"${val:,.2f}" def fmt_delta(delta_val, pct_val): if delta_val is None or pd.isna(delta_val): return "—", "" symbol = "negative" if delta_val < 0 else "" pct_text = f" ({pct_val:+.2f}%)" if pct_val not in [None, np.nan] else "" return f"{delta_val:+.2f}{pct_text}", symbol delta_text, delta_class = fmt_delta(change, change_pct) st.markdown( f"""
Market Pulse

{ticker_symbol.upper()} | Stock Intelligence

Sharper visuals for price action, technicals, and AI commentary across your chosen dates.

Range: {start_date.strftime('%b %d, %Y')} → {end_date.strftime('%b %d, %Y')}
""", unsafe_allow_html=True, ) mc1, mc2, mc3 = st.columns(3) mc1.markdown( f"""

Last Close

{fmt_currency(latest_close)}
{delta_text}
""", unsafe_allow_html=True, ) mc2.markdown( f"""

Period Range

{fmt_currency(period_low)} – {fmt_currency(period_high)}
Session High / Low
""", unsafe_allow_html=True, ) mc3.markdown( f"""

Avg Volume

{'-' if avg_volume is None or pd.isna(avg_volume) else f"{avg_volume:,.0f}"}
Across selected window
""", unsafe_allow_html=True, ) price_tab, indicators_tab, ai_tab = st.tabs(["Price Action", "Technical Indicators", "AI Deep Dive"]) with price_tab: st.subheader("Stock Price Chart") st.caption("Candlestick price action with volume on a shared timeline for quick at-a-glance context.") if debug_mode: st.write("Data sample:", df.head(3)) st.write("dtypes:", df.dtypes) df_plot = df.set_index("Date")[["Open", "High", "Low", "Close", "Volume"]] market_colors = mpf.make_marketcolors(up="#7cf0c6", down="#ff9b9b", edge="inherit", wick="inherit", volume="in") style = mpf.make_mpf_style(base_mpf_style="nightclouds", marketcolors=market_colors, facecolor="#0c1320", edgecolor="#0c1320", gridcolor="#1b2a45") fig, _ = mpf.plot( df_plot, type="candle", volume=True, style=style, returnfig=True, figsize=(10, 6), tight_layout=True, update_width_config=dict(candle_linewidth=0.8, candle_width=0.6), ) st.pyplot(fig, clear_figure=True) plt.close(fig) with indicators_tab: st.subheader("Moving Averages") st.caption("Compare recent closes against short and intermediate trend lines.") df['SMA_20'] = close_series.rolling(window=20).mean() df['SMA_50'] = close_series.rolling(window=50).mean() fig, ax = plt.subplots(figsize=(10, 4)) ax.plot(df['Date'], close_series, label='Close Price', color="#7cf0c6", linewidth=1.4) ax.plot(df['Date'], df['SMA_20'], label='20-Day SMA', color="#6dd6ff", linewidth=1.2) ax.plot(df['Date'], df['SMA_50'], label='50-Day SMA', color="#b0b8ff", linewidth=1.2) ax.set_ylabel("Price (USD)") ax.grid(alpha=0.2) ax.legend() fig.tight_layout() st.pyplot(fig, clear_figure=True) plt.close(fig) st.subheader("Relative Strength Index (RSI)") st.caption("Momentum oscillator highlighting overbought/oversold zones.") window_length = 14 delta = close_series.diff() gain = delta.where(delta > 0, 0) loss = -delta.where(delta < 0, 0) avg_gain = gain.rolling(window=window_length, min_periods=1).mean() avg_loss = loss.rolling(window=window_length, min_periods=1).mean() rs = avg_gain / avg_loss df['RSI'] = 100 - (100 / (1 + rs)) fig, ax = plt.subplots(figsize=(10, 3.5)) ax.plot(df['Date'], df['RSI'], label='RSI', color="#6dd6ff", linewidth=1.4) ax.axhline(70, color="#ff9b9b", linestyle="--", linewidth=1, label="Overbought") ax.axhline(30, color="#7cf0c6", linestyle="--", linewidth=1, label="Oversold") ax.set_ylabel("RSI") ax.grid(alpha=0.2) ax.legend() fig.tight_layout() st.pyplot(fig, clear_figure=True) plt.close(fig) st.subheader("Volume Analysis") st.caption("Volume bars styled to match the rest of the dashboard.") fig, ax = plt.subplots(figsize=(10, 3.5)) ax.bar(df['Date'], volume_series, color=(109/255, 214/255, 255/255, 0.55)) ax.set_ylabel("Volume") ax.grid(alpha=0.15) fig.tight_layout() st.pyplot(fig, clear_figure=True) plt.close(fig) with ai_tab: st.subheader("In-depth Analysis") st.caption("AI-generated commentary stays on a dedicated tab so charts remain uncluttered.") chatTextStr = f""" Analyze the following stock market data to identify notable patterns, trends, and anomalies. Summarize key price movements, volume behavior, and any significant shifts in market sentiment. Provide insights in clear, plain language and do not include any programming code. """ with st.spinner("Running in-depth AI analysis..."): try: answer = llm.predict(f''' I have yfinance data below on {ticker_symbol} symbol: {str(df[['Date', 'Open', 'High', 'Low', 'Close']].tail(30))} {chatTextStr} ''') st.write(answer) except Exception as exc: st.error(f"AI analysis failed: {exc}")