Spaces:
Sleeping
Sleeping
| 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" | |
| 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<br>{start_label}<br>βΉ{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<br>{end_label}<br>βΉ{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() |