import streamlit as st import tensorflow as tf import numpy as np import pandas as pd import pickle import json import requests import yfinance as yf from tensorflow.keras.preprocessing.sequence import pad_sequences # ===================================================== # PAGE CONFIG # ===================================================== st.set_page_config( page_title="Stock Market AI Chatbot", page_icon="🤖", layout="centered" ) # ===================================================== # LOAD MODEL # ===================================================== from pathlib import Path import tensorflow as tf import pickle import json import streamlit as st BASE_DIR = Path(__file__).resolve().parent.parent MODEL_DIR = BASE_DIR / "saved_chatbot" @st.cache_resource def load_chatbot(): encoder = tf.keras.models.load_model( MODEL_DIR / "encoder.keras", compile=False ) decoder = tf.keras.models.load_model( MODEL_DIR / "decoder.keras", compile=False ) with open(MODEL_DIR / "tokenizer.pkl", "rb") as f: tokenizer = pickle.load(f) with open(MODEL_DIR / "config.json", "r") as f: config = json.load(f) index_word = { v: k for k, v in tokenizer.word_index.items() } return { "encoder": encoder, "decoder": decoder, "tokenizer": tokenizer, "index_word": index_word, "MAX_ENC_LEN": config["MAX_ENC_LEN"], "START_IDX": config["START_IDX"], "END_IDX": config["END_IDX"] } model_data = load_chatbot() # ===================================================== # STOCK DATABASE # ===================================================== STOCK_SYMBOLS = { "reliance": "RELIANCE.NS", "tcs": "TCS.NS", "infosys": "INFY.NS", "hdfc": "HDFCBANK.NS", "icici": "ICICIBANK.NS", "sbi": "SBIN.NS", "wipro": "WIPRO.NS", "adani": "ADANIENT.NS", "zomato": "ZOMATO.NS", "tata motors": "TATAMOTORS.NS" } # ===================================================== # STOCK DATA # ===================================================== import matplotlib.pyplot as plt from datetime import datetime import plotly.graph_objects as go import yfinance as yf def plot_stock_chart(symbol, start_dt=None, end_dt=None): stock = yf.Ticker(symbol) if start_dt and end_dt: df = stock.history(start=start_dt, end=end_dt) else: df = stock.history(period="6mo") if df.empty: st.error("No stock data found.") return # Fix MultiIndex columns if present if isinstance(df.columns, pd.MultiIndex): df.columns = df.columns.get_level_values(0) if "Close" not in df.columns: st.error("Close price data not available.") return # Index IS the date with stock.history() date_series = pd.to_datetime(df.index) close_series = df["Close"].squeeze() current_time = datetime.now().strftime("%d-%m-%Y %H:%M:%S") st.info(f"Chart generated on: {current_time}") start_label = date_series[0].strftime("%d %b %Y") end_label = date_series[-1].strftime("%d %b %Y") start_price = round(float(close_series.iloc[0]), 2) end_price = round(float(close_series.iloc[-1]), 2) fig = go.Figure() fig.add_trace( go.Scatter( x=date_series, y=close_series, mode="lines", line=dict(color="#00C9FF", width=2), name="Close Price" ) ) # Start marker fig.add_trace( go.Scatter( x=[date_series[0]], y=[start_price], mode="markers+text", marker=dict(color="#00FF99", size=10), text=[f"Start
{start_label}
₹{start_price}"], textposition="top right", textfont=dict(size=11, color="#00FF99"), showlegend=False ) ) # End marker fig.add_trace( go.Scatter( x=[date_series[-1]], y=[end_price], mode="markers+text", marker=dict(color="#FF6B6B", size=10), text=[f"End
{end_label}
₹{end_price}"], textposition="top left", textfont=dict(size=11, color="#FF6B6B"), showlegend=False ) ) fig.update_layout( title=f"{symbol} Price History | {start_label} → {end_label}", xaxis_title="Date", yaxis_title="Price (₹)", height=500, template="plotly_dark", hovermode="x unified" ) st.plotly_chart(fig, use_container_width=True) st.subheader("Recent Closing Prices") temp = df[["Open", "Close"]].tail(20).copy() temp.index = pd.to_datetime(temp.index).strftime("%d %b %Y") st.dataframe(temp, use_container_width=True) def get_stock_info(symbol): try: stock = yf.Ticker(symbol) df = stock.history(period="1mo") if df.empty: return None return { "price": round(df["Close"].iloc[-1], 2), "high": round(df["High"].iloc[-1], 2), "low": round(df["Low"].iloc[-1], 2), "volume": int(df["Volume"].iloc[-1]) } except: return None # ===================================================== # CHATBOT RESPONSE # ===================================================== def generate_response(user_text): tokenizer = model_data["tokenizer"] encoder = model_data["encoder"] decoder = model_data["decoder"] seq = tokenizer.texts_to_sequences( [user_text.lower()] ) seq = pad_sequences( seq, maxlen=model_data["MAX_ENC_LEN"], padding="post" ) lstm_h, lstm_c, gru_h = encoder.predict( seq, verbose=0 ) target_seq = np.array( [[model_data["START_IDX"]]] ) reply = [] for _ in range(25): output, lstm_h, lstm_c, gru_h = decoder.predict( [ target_seq, lstm_h, lstm_c, gru_h ], verbose=0 ) idx = np.argmax( output[0, 0, :] ) if idx == model_data["END_IDX"]: break word = model_data["index_word"].get( idx, "" ) if word: reply.append(word) target_seq = np.array([[idx]]) result = " ".join(reply) if len(result.strip()) == 0: return "Sorry, I don't know that yet." return result # ===================================================== # WEATHER # ===================================================== def get_weather(city): """Fetch weather using Open-Meteo (free, no API key needed).""" try: city_clean = city.strip() # Step 1: Geocode city name → lat/lon geo_url = ( f"https://geocoding-api.open-meteo.com/v1/search" f"?name={requests.utils.quote(city_clean)}&count=1&language=en&format=json" ) geo_res = requests.get(geo_url, timeout=10) if geo_res.status_code != 200 or not geo_res.json().get("results"): return f"⚠️ Could not find city **{city_clean}**. Try a different spelling." geo = geo_res.json()["results"][0] lat = geo["latitude"] lon = geo["longitude"] area = geo.get("name", city_clean) country = geo.get("country", "") # Step 2: Fetch weather from Open-Meteo weather_url = ( f"https://api.open-meteo.com/v1/forecast" f"?latitude={lat}&longitude={lon}" f"¤t=temperature_2m,relative_humidity_2m,apparent_temperature," f"weather_code,wind_speed_10m,wind_direction_10m,surface_pressure,visibility" f"&daily=weather_code,temperature_2m_max,temperature_2m_min" f"&timezone=auto&forecast_days=3" ) w_res = requests.get(weather_url, timeout=10) if w_res.status_code != 200: return f"⚠️ Weather data unavailable for **{area}**." w = w_res.json() curr = w["current"] daily = w["daily"] # WMO weather code → description WMO = { 0:"Clear sky", 1:"Mainly clear", 2:"Partly cloudy", 3:"Overcast", 45:"Foggy", 48:"Icy fog", 51:"Light drizzle", 53:"Drizzle", 55:"Heavy drizzle", 61:"Slight rain", 63:"Rain", 65:"Heavy rain", 71:"Slight snow", 73:"Snow", 75:"Heavy snow", 80:"Rain showers", 81:"Heavy showers", 82:"Violent showers", 95:"Thunderstorm", 96:"Thunderstorm w/ hail", 99:"Heavy thunderstorm" } code = curr.get("weather_code", 0) description = WMO.get(code, f"Code {code}") wind_deg = curr.get("wind_direction_10m", 0) directions = ["N","NE","E","SE","S","SW","W","NW"] wind_dir = directions[round(wind_deg / 45) % 8] # 3-day forecast forecast_lines = [] for i in range(len(daily["time"])): d_code = daily["weather_code"][i] d_desc = WMO.get(d_code, f"Code {d_code}") forecast_lines.append( f" 📅 {daily['time'][i]} — {d_desc}, " f"Max: {daily['temperature_2m_max'][i]}°C / Min: {daily['temperature_2m_min'][i]}°C" ) forecast_str = "\n".join(forecast_lines) return f""" 🌤️ **Weather Report — {area}, {country}** 🌡️ Temperature : {curr['temperature_2m']}°C (Feels like {curr['apparent_temperature']}°C) ☁️ Condition : {description} 💧 Humidity : {curr['relative_humidity_2m']}% 💨 Wind : {curr['wind_speed_10m']} km/h {wind_dir} 🔵 Pressure : {curr['surface_pressure']} hPa 📆 **3-Day Forecast:** {forecast_str} """ except requests.exceptions.ConnectionError: return "⚠️ No internet connection. Could not fetch weather data." except requests.exceptions.Timeout: return "⚠️ Weather request timed out. Please try again." except (KeyError, ValueError, IndexError) as e: return f"⚠️ Could not parse weather data for **{city}**. Try e.g. `weather in Mumbai`." except Exception as e: return f"⚠️ Unexpected error: {str(e)}" # ===================================================== # ROUTER # ===================================================== from datetime import datetime from datetime import datetime def route_message(msg): text = msg.lower() current_datetime = datetime.now().strftime( "%d-%m-%Y %I:%M:%S %p" ) if text == "time": return f"Current date & time: {current_datetime}" # Show all available stocks if any(kw in text for kw in ["stocks", "stock list", "show stocks", "all stocks", "available stocks"]): lines = [] for i, (company, symbol) in enumerate(STOCK_SYMBOLS.items(), 1): lines.append(f"{i}. **{company.title()}** — `{symbol}`") return "📊 **Available Stocks:**\n\n" + "\n".join(lines) # Weather trigger: "weather in mumbai" / "weather delhi" / "mumbai weather" if "weather" in text: import re # Remove the word "weather", "in", "of", "the" carefully city = re.sub(r'\bweather\b', '', text) city = re.sub(r'^\s*(in|of|the)\s+', '', city.strip()) city = city.strip() if city: return get_weather(city) else: return "🌍 Please tell me a city name, e.g. **weather in Mumbai**" for company, symbol in STOCK_SYMBOLS.items(): if company.lower() in text: # Show graph — ask for date range if "graph" in text or "chart" in text: st.session_state.pending_chart = { "symbol": symbol, "company": company } return f"📅 Please select the **start and end date** for the **{company.upper()}** chart using the date pickers below." stock = get_stock_info(symbol) if stock: return f""" 📈 {company.upper()} 📅 Today : {current_datetime} Current Price : ₹{stock['price']} Day High : ₹{stock['high']} Day Low : ₹{stock['low']} Volume : {stock['volume']:,} """ else: return "Unable to fetch stock data." return generate_response(text) # ===================================================== # UI # ===================================================== st.title("🤖 Stock Market AI Chatbot") st.caption("chatbot BiLSTM + BiGRU ") # ── Available Stocks Panel ───────────────────────────────────────────── with st.expander("📊 Available Stocks — click to see all", expanded=False): cols = st.columns(2) for i, (company, symbol) in enumerate(STOCK_SYMBOLS.items()): with cols[i % 2]: st.markdown( f"🔹 **{company.title()}**   `{symbol}`" ) st.caption("💡 Try: *hdfc graph*, *reliance price*, *tcs chart*") if "messages" not in st.session_state: st.session_state.messages = [ { "role": "assistant", "content": "Hello! Ask me about stocks or general questions." } ] # Show messages # Show messages - render charts inline inside history for msg in st.session_state.messages: with st.chat_message(msg["role"]): st.markdown(msg["content"]) # Re-render chart inline if this message had one if msg.get("chart"): c = msg["chart"] plot_stock_chart(c["symbol"], c.get("start"), c.get("end")) # Chat input prompt = st.chat_input("Type your question...") if prompt: st.session_state.messages.append({"role": "user", "content": prompt}) with st.chat_message("user"): st.markdown(prompt) answer = route_message(prompt) with st.chat_message("assistant"): st.markdown(answer) st.session_state.messages.append({"role": "assistant", "content": answer}) # ===================================================== # DATE PICKER + CHART RENDER # ===================================================== if "pending_chart" in st.session_state: info = st.session_state.pending_chart company = info["company"] symbol = info["symbol"] st.markdown("---") st.markdown(f"### 📅 Select Date Range for **{company.upper()}** Chart") col1, col2 = st.columns(2) with col1: start_date = st.date_input( "Start Date", value=pd.Timestamp.today() - pd.DateOffset(months=6), max_value=pd.Timestamp.today() - pd.DateOffset(days=1), key="chart_start_date" ) with col2: end_date = st.date_input( "End Date", value=pd.Timestamp.today(), max_value=pd.Timestamp.today(), key="chart_end_date" ) if st.button("📈 Show Chart", key="show_chart_btn"): if start_date >= end_date: st.error("⚠️ Start date must be before end date.") else: start_str = start_date.strftime("%Y-%m-%d") end_str = end_date.strftime("%Y-%m-%d") # Attach chart to last assistant message so it renders inline in history for msg in reversed(st.session_state.messages): if msg["role"] == "assistant": msg["chart"] = { "symbol": symbol, "start": start_str, "end": end_str } break del st.session_state.pending_chart st.rerun() # Clear chat if st.button("Clear Chat"): st.session_state.messages = [ {"role": "assistant", "content": "Chat cleared."} ] for key in ["pending_chart", "chart_symbol", "chart_start", "chart_end"]: if key in st.session_state: del st.session_state[key] st.rerun()