Spaces:
Running
Running
| import gradio as gr | |
| import tensorflow as tf | |
| import pandas as pd | |
| import numpy as np | |
| import yfinance as yf | |
| from sklearn.preprocessing import MinMaxScaler | |
| import matplotlib.pyplot as plt | |
| import PIL.Image | |
| import io | |
| from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer | |
| from transformers import AutoTokenizer, AutoModelForSequenceClassification | |
| import torch | |
| import feedparser | |
| import functools | |
| from datetime import datetime, timedelta | |
| import shap | |
| import ta | |
| import zipfile | |
| import requests | |
| from bs4 import BeautifulSoup | |
| import re | |
| import json | |
| import os | |
| from googleapiclient.discovery import build | |
| from textblob import TextBlob | |
| # Load the LSTM model | |
| model = tf.keras.models.load_model("nse_lstm_model_fixed.h5") | |
| model.compile(optimizer='adam', loss='mean_squared_error') | |
| from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer | |
| from transformers import AutoTokenizer, AutoModelForSequenceClassification | |
| import torch | |
| import numpy as np | |
| vader = SentimentIntensityAnalyzer() | |
| finbert_tokenizer = AutoTokenizer.from_pretrained("yiyanghkust/finbert-tone") | |
| finbert_model = AutoModelForSequenceClassification.from_pretrained("yiyanghkust/finbert-tone") | |
| def get_actual_cash_flows(symbol, api_key): | |
| try: | |
| url = f"https://financialmodelingprep.com/api/v3/cash-flow-statement/{symbol}?apikey={api_key}&limit=5" | |
| response = requests.get(url) | |
| data = response.json() | |
| if not data or 'freeCashFlow' not in data[0]: | |
| return None | |
| # Extract latest 5 years of FCF | |
| fcfs = [entry['freeCashFlow'] for entry in data if 'freeCashFlow' in entry] | |
| return fcfs[::-1] # oldest to latest | |
| except Exception as e: | |
| print(f"Error fetching cash flows: {e}") | |
| return None | |
| # Mint green UI theme | |
| custom_css = """ | |
| .gr-button { | |
| background-color: #4CAF50 !important; | |
| color: black !important; | |
| font-weight: bold; | |
| box-shadow: 0 4px #2e7d32; | |
| border-radius: 8px; | |
| padding: 10px 16px; | |
| transition: all 0.1s ease-in-out; | |
| } | |
| .gr-button:active { | |
| box-shadow: 0 2px #1b5e20; | |
| transform: translateY(2px); | |
| } | |
| body { | |
| background-color: #eaf4f8 !important; | |
| color: #000000 !important; | |
| } | |
| .gr-textbox, .gr-json, .gr-image { | |
| background-color: #ffffff !important; | |
| color: #000000 !important; | |
| } | |
| @media only screen and (max-width: 768px) { | |
| .gr-block, .gr-button, .gr-textbox, .gr-dropdown { | |
| width: 100% !important; | |
| } | |
| } | |
| """ | |
| # NSE sector mapping | |
| SECTOR_STOCKS = { | |
| "IT": "INFY.NS", | |
| "Pharma": "SUNPHARMA.NS", | |
| "FMCG": "HINDUNILVR.NS", | |
| "Auto": "TATAMOTORS.NS", | |
| "Banking": "ICICIBANK.NS", | |
| "Energy": "RELIANCE.NS", | |
| "Infra": "LT.NS", | |
| "Metal": "TATASTEEL.NS", | |
| "Finance": "BAJFINANCE.NS", | |
| "Realty": "DLF.NS" | |
| } | |
| # --- RSI Calculations --- | |
| def compute_rsi(series, window=14): | |
| delta = series.diff() | |
| gain = delta.clip(lower=0) | |
| loss = -delta.clip(upper=0) | |
| avg_gain = gain.rolling(window).mean() | |
| avg_loss = loss.rolling(window).mean() | |
| rs = avg_gain / (avg_loss + 1e-10) | |
| rsi_series = 100 - (100 / (1 + rs)) | |
| rsi_value = rsi_series.iloc[-1] # โ This is now a float | |
| return float(rsi_value) if not pd.isna(rsi_value) else 50.0 # Default to neutral if NaN | |
| def compute_rsi_series(series, window=14): | |
| delta = series.diff() | |
| gain = delta.clip(lower=0) | |
| loss = -delta.clip(upper=0) | |
| avg_gain = gain.rolling(window).mean() | |
| avg_loss = loss.rolling(window).mean() | |
| rs = avg_gain / (avg_loss + 1e-10) | |
| return 100 - (100 / (1 + rs)) | |
| def run_technical_screener(symbol): | |
| try: | |
| if not symbol.endswith(".NS"): | |
| symbol += ".NS" | |
| df = yf.download(symbol, period="60d", interval="1d", progress=False, threads=False) | |
| if df.empty or len(df) < 30: | |
| return f"โ Not enough data for {symbol}" | |
| close = df['Close'].values.astype(float) | |
| high = df['High'].values.astype(float) | |
| low = df['Low'].values.astype(float) | |
| volume = df['Volume'].values.astype(float) | |
| # --- MACD Calculation --- | |
| def ema(values, span): | |
| alpha = 2 / (span + 1) | |
| ema_vals = [values[0]] | |
| for price in values[1:]: | |
| ema_vals.append((price - ema_vals[-1]) * alpha + ema_vals[-1]) | |
| return np.array(ema_vals) | |
| ema12 = ema(close, 12) | |
| ema26 = ema(close, 26) | |
| macd_line = float(ema12[-1] - ema26[-1]) | |
| # --- Stochastic %K Calculation --- | |
| if len(close) < 14: | |
| stoch_k = 50.0 | |
| else: | |
| low14 = np.min(low[-14:]) | |
| high14 = np.max(high[-14:]) | |
| stoch_k = 100 * (close[-1] - low14) / (high14 - low14 + 1e-10) | |
| stoch_k = float(stoch_k) | |
| # --- Volume Spike --- | |
| vol_spike = bool(volume[-1] > 1.5 * np.mean(volume[-10:])) | |
| # --- Build Result --- | |
| result = f"๐ Technical Screener for {symbol}\n" | |
| result += f"โข Stochastic %K: {round(stoch_k, 2)}\n" | |
| result += f"โข MACD: {round(macd_line, 2)}\n" | |
| result += f"โข Volume Spike: {'โ Yes' if vol_spike else 'โ No'}\n" | |
| if stoch_k < 20 and macd_line > 0 and vol_spike: | |
| result += "\nโ Signal: Strong **Bullish** Setup" | |
| elif stoch_k > 80 and macd_line < 0 and vol_spike: | |
| result += "\nโ ๏ธ Signal: Potential **Bearish** Reversal" | |
| else: | |
| result += "\n๐ Signal: **Neutral or Inconclusive**" | |
| return result.strip() | |
| except Exception as e: | |
| return f"โ Error analyzing {symbol}: {str(e)}" | |
| def get_vader_sentiment(texts): | |
| scores = [vader.polarity_scores(t)['compound'] for t in texts] | |
| return round(sum(scores) / len(scores), 3) if scores else 0.0 | |
| def get_finbert_sentiment(texts): | |
| scores = [] | |
| for text in texts: | |
| inputs = finbert_tokenizer(text, return_tensors="pt", truncation=True, max_length=512) | |
| with torch.no_grad(): | |
| logits = finbert_model(**inputs).logits | |
| probs = torch.softmax(logits, dim=1).squeeze().numpy() | |
| sentiment_score = probs[2] - probs[0] # positive - negative | |
| scores.append(sentiment_score) | |
| return round(np.mean(scores), 3) if scores else 0.0 | |
| # --- Google News Sentiment from RSS --- | |
| def get_headlines_sentiment(query): | |
| try: | |
| query = query.replace(" ", "+") | |
| rss_url = f"https://news.google.com/rss/search?q={query}+stock" | |
| feed = feedparser.parse(rss_url) | |
| headlines = [entry.title for entry in feed.entries[:5]] | |
| sentiment = get_vader_sentiment(headlines) | |
| return sentiment, headlines or ["No news found"] | |
| except Exception as e: | |
| return 0.0, [f"Error: {str(e)}"] | |
| import signal | |
| class TimeoutException(Exception): pass | |
| def timeout_handler(signum, frame): | |
| raise TimeoutException("YouTube sentiment request timed out.") | |
| signal.signal(signal.SIGALRM, timeout_handler) | |
| import concurrent.futures | |
| def market_mind_reader(symbol): | |
| try: | |
| if not symbol.endswith(".NS"): | |
| symbol += ".NS" | |
| print("๐ Fetching ticker data...") | |
| ticker = yf.Ticker(symbol) | |
| hist = ticker.history(period='90d') | |
| info = ticker.info or {} | |
| if hist.empty: | |
| return "โ No historical data found for this symbol." | |
| # ๐ฐ News Sentiment | |
| print("๐ฐ Fetching news headlines...") | |
| query = symbol.replace(".NS", "") | |
| rss_url = f"https://news.google.com/rss/search?q={query}+stock" | |
| feed = feedparser.parse(rss_url) | |
| headlines = [entry.title for entry in feed.entries[:5]] | |
| sentiments = [TextBlob(title).sentiment.polarity for title in headlines] | |
| news_sent = np.mean(sentiments) if sentiments else 0.0 | |
| # ๐ฅ YouTube Sentiment (using safe timeout) | |
| print("๐ฅ Fetching YouTube sentiment...") | |
| yt_sent = 0.0 | |
| try: | |
| with concurrent.futures.ThreadPoolExecutor() as executor: | |
| future = executor.submit(get_youtube_sentiment, symbol) | |
| yt_sent = future.result(timeout=10) # 10 seconds max | |
| except concurrent.futures.TimeoutError: | |
| print("โ ๏ธ YouTube sentiment call timed out.") | |
| except Exception as e: | |
| print("โ ๏ธ YouTube Sentiment Error:", e) | |
| # ๐ Metrics | |
| pe = info.get('trailingPE', "N/A") | |
| close = hist['Close'] | |
| rsi = compute_rsi(close) | |
| avg_vol = hist['Volume'].mean() | |
| vol_spike = hist['Volume'].iloc[-1] / avg_vol if avg_vol > 0 else 1.0 | |
| # ๐ง Inference | |
| if rsi < 30 and news_sent > 0: | |
| action = "Likely Buying" | |
| elif rsi > 70 and news_sent < 0: | |
| action = "Likely Selling" | |
| else: | |
| action = "Likely Holding / Sideways" | |
| summary = f""" | |
| ๐ง Market Mind Reader - {symbol} | |
| Sentiment Summary: | |
| โข Google News Sentiment: {round(news_sent, 2)} | |
| โข YouTube Sentiment: {round(yt_sent, 2)} | |
| Fundamentals & Technicals: | |
| โข PE Ratio: {pe} | |
| โข RSI: {round(rsi, 2)} | |
| โข Volume Spike: {round(vol_spike, 2)}x avg | |
| ๐งพ Predicted Retail Behavior: {action} | |
| """ | |
| return summary.strip() | |
| except Exception as e: | |
| return f"โ Error: {str(e)}" | |
| def greed_fear_gauge(symbol): | |
| try: | |
| ticker = yf.Ticker(symbol) | |
| hist = ticker.history(period='30d') | |
| rsi = compute_rsi(hist['Close']) | |
| volume = hist['Volume'] | |
| avg_vol = volume.mean() | |
| vol_spike = volume.iloc[-1] / avg_vol | |
| news_sent = get_headlines_sentiment(symbol)[0] | |
| score = (rsi / 100 * 40) + (vol_spike * 10) + (news_sent * 50) | |
| score = max(0, min(100, round(score, 2))) | |
| if score > 70: | |
| zone = "๐ฅ Extreme Greed" | |
| elif score < 30: | |
| zone = "๐ฉ Fear / Panic" | |
| else: | |
| zone = "๐จ Neutral / Caution" | |
| return f"Greed & Fear Score: {score}/100\nMarket Mood: {zone}" | |
| except Exception as e: | |
| return f"โ Error: {str(e)}" | |
| # --- YouTube Sentiment Analysis --- | |
| def get_youtube_sentiment(stock_name): | |
| try: | |
| api_key = "AIzaSyCb_-cGsEJV4RMfuyz7h1XVH891fXxdvvU" # โ Your real API key | |
| youtube = build('youtube', 'v3', developerKey=api_key) | |
| request = youtube.search().list( | |
| q=f"{stock_name} stock analysis", | |
| part='snippet', | |
| type='video', | |
| maxResults=5 | |
| ) | |
| response = request.execute() | |
| texts = [ | |
| f"{item['snippet']['title']}. {item['snippet']['description']}" | |
| for item in response['items'] | |
| ] | |
| return get_finbert_sentiment(texts) | |
| except Exception as e: | |
| print("YouTube Sentiment Error:", str(e)) | |
| return 0.0 | |
| # --- Financial Ratios (Cached) --- | |
| def get_financials(symbol): | |
| try: | |
| info = yf.Ticker(symbol.upper()).info | |
| return { | |
| "PE Ratio": info.get("trailingPE", "N/A"), | |
| "Debt-to-Equity": info.get("debtToEquity", "N/A"), | |
| "Market Cap": info.get("marketCap", "N/A"), | |
| "Book Value": info.get("bookValue", "N/A") | |
| } | |
| except: | |
| return {"PE Ratio": "N/A", "Debt-to-Equity": "N/A", "Market Cap": "N/A", "Book Value": "N/A"} | |
| # --- Corporate Actions --- | |
| def get_corporate_actions(symbol): | |
| try: | |
| info = yf.Ticker(symbol).info | |
| events = [] | |
| if info.get("dividendYield"): | |
| events.append(f"Dividend Yield: {round(info['dividendYield'] * 100, 2)}%") | |
| if info.get("dividendRate"): | |
| events.append(f"Dividend Rate: โน{info['dividendRate']}") | |
| if info.get("lastSplitFactor"): | |
| split_date_unix = info.get("lastSplitDate", None) | |
| if isinstance(split_date_unix, (int, float)): | |
| split_date = datetime.fromtimestamp(split_date_unix).strftime("%Y-%m-%d") | |
| else: | |
| split_date = "N/A" | |
| events.append(f"Last Split: {info['lastSplitFactor']} on {split_date}") | |
| return "\n".join(events) if events else "No corporate actions upcoming." | |
| except Exception as e: | |
| return f"Error fetching corporate actions: {str(e)}" | |
| # --- Sector Comparison Chart --- | |
| # โ Manual overrides for known misclassifications or missing info | |
| MANUAL_SECTOR_OVERRIDES = { | |
| "BLUEJET.NS": "Pharma", | |
| # Add more manual mappings here as needed | |
| } | |
| # โ Yahoo Finance sector index mapping | |
| SECTOR_INDEX_MAPPING = { | |
| "IT": "^CNXIT", | |
| "Pharma": "^CNXPHARMA", | |
| "FMCG": "^CNXFMCG", | |
| "Auto": "^CNXAUTO", | |
| "Banking": "^NSEBANK", | |
| "Energy": "^CNXENERGY", | |
| "Infra": "^CNXINFRA", | |
| "Metal": "^CNXMETAL", | |
| "Finance": "^CNXFINANCE", | |
| "Realty": "^CNXREALTY", | |
| "General": "^NSEI" | |
| } | |
| # โ Friendly names for plotting | |
| INDEX_NAME_MAP = { | |
| "^CNXIT": "NIFTY IT", | |
| "^CNXPHARMA": "NIFTY Pharma", | |
| "^CNXFMCG": "NIFTY FMCG", | |
| "^CNXAUTO": "NIFTY Auto", | |
| "^NSEBANK": "NIFTY Bank", | |
| "^CNXENERGY": "NIFTY Energy", | |
| "^CNXINFRA": "NIFTY Infra", | |
| "^CNXMETAL": "NIFTY Metal", | |
| "^CNXFINANCE": "NIFTY Finance", | |
| "^CNXREALTY": "NIFTY Realty", | |
| "^NSEI": "NIFTY 50" | |
| } | |
| # โ Robust keyword-based fallback for automatic classification | |
| def map_industry_to_sector(industry): | |
| industry = industry.lower() | |
| if "pharma" in industry or "biotech" in industry or "healthcare" in industry: | |
| return "Pharma" | |
| elif "logistics" in industry and "pharma" in industry: | |
| return "Pharma" | |
| elif "logistics" in industry or "freight" in industry or "cargo" in industry: | |
| return "Infra" | |
| elif "it" in industry or "software" in industry or "technology" in industry: | |
| return "IT" | |
| elif "bank" in industry: | |
| return "Banking" | |
| elif "auto" in industry or "vehicle" in industry: | |
| return "Auto" | |
| elif "fmcg" in industry or "consumer" in industry: | |
| return "FMCG" | |
| elif "metal" in industry or "mining" in industry: | |
| return "Metal" | |
| elif "energy" in industry or "oil" in industry or "gas" in industry: | |
| return "Energy" | |
| elif "real estate" in industry or "property" in industry: | |
| return "Realty" | |
| elif "finance" in industry or "nbfc" in industry or "investment" in industry: | |
| return "Finance" | |
| elif "infra" in industry or "construction" in industry or "engineering" in industry: | |
| return "Infra" | |
| else: | |
| return "General" | |
| # โ Use this inside get_sector_comparison_chart() | |
| def get_sector_comparison_chart(symbol): | |
| try: | |
| ticker = yf.Ticker(symbol) | |
| info = ticker.info | |
| industry = info.get("industry", "") | |
| sector = MANUAL_SECTOR_OVERRIDES.get(symbol.upper(), map_industry_to_sector(industry)) | |
| index_symbol = SECTOR_INDEX_MAPPING.get(sector, "^NSEI") | |
| index_name = INDEX_NAME_MAP.get(index_symbol, index_symbol) | |
| df_stock = yf.download(symbol, period='60d') | |
| df_index = yf.download(index_symbol, period='60d') | |
| if df_stock.empty or df_index.empty: | |
| raise Exception("Empty data for stock or sector index.") | |
| stock_returns = df_stock['Close'].pct_change().cumsum() | |
| index_returns = df_index['Close'].pct_change().cumsum() | |
| plt.figure(figsize=(8, 4)) | |
| plt.plot(stock_returns, label=symbol) | |
| plt.plot(index_returns, label=index_name, linestyle='--') | |
| plt.title(f"{symbol} vs {index_name}") | |
| plt.legend() | |
| plt.tight_layout() | |
| buf = io.BytesIO() | |
| plt.savefig(buf, format="png") | |
| buf.seek(0) | |
| return PIL.Image.open(buf) | |
| except Exception as e: | |
| print(f"[โ] Sector comparison error for {symbol}: {e}") | |
| return PIL.Image.new("RGB", (400, 200), color="gray") | |
| # --- Fetch Industry of a Stock --- | |
| def get_industry(symbol): | |
| try: | |
| return yf.Ticker(symbol).info.get("industry", "Finance") | |
| except: | |
| return "Finance" | |
| def fetch_live_snapshot(symbol): | |
| try: | |
| ticker = yf.Ticker(symbol) | |
| data = ticker.history(period='1d', interval='1m') | |
| info = ticker.info | |
| last_price = round(data['Close'].iloc[-1], 2) | |
| high = round(data['High'].max(), 2) | |
| low = round(data['Low'].min(), 2) | |
| rsi = compute_rsi(data['Close']) if len(data['Close']) >= 15 else "N/A" | |
| pe_ratio = info.get('trailingPE', 'N/A') | |
| upper_bb = last_price * 1.05 | |
| lower_bb = last_price * 0.95 | |
| high52 = info.get('fiftyTwoWeekHigh', 'N/A') | |
| low52 = info.get('fiftyTwoWeekLow', 'N/A') | |
| return [ | |
| f"โน{last_price}", | |
| f"โน{high} / โน{low}", | |
| f"{round(rsi, 2)}" if isinstance(rsi, float) else "N/A", | |
| f"{pe_ratio}", | |
| f"โน{round(upper_bb, 2)} / โน{round(lower_bb, 2)}", | |
| f"โน{high52} / โน{low52}", | |
| ] | |
| except Exception as e: | |
| return [f"โ Error: {str(e)}"] * 6 | |
| def predict_price(symbol): | |
| try: | |
| df = yf.download(symbol, period='90d', interval='1d') | |
| if df.empty or len(df) < 60: | |
| raise Exception("Not enough data") | |
| df = df.tail(60).copy() | |
| # Compute RSI | |
| delta = df['Close'].diff() | |
| gain = delta.clip(lower=0) | |
| loss = -delta.clip(upper=0) | |
| avg_gain = gain.rolling(14).mean() | |
| avg_loss = loss.rolling(14).mean() | |
| rs = avg_gain / (avg_loss + 1e-10) | |
| df['RSI'] = 100 - (100 / (1 + rs)).fillna(0) | |
| # MACD | |
| ema12 = df['Close'].ewm(span=12, adjust=False).mean() | |
| ema26 = df['Close'].ewm(span=26, adjust=False).mean() | |
| df['MACD'] = ema12 - ema26 | |
| # ADX | |
| high = df['High'] | |
| low = df['Low'] | |
| close = df['Close'] | |
| df['TR'] = np.maximum(high - low, np.maximum(abs(high - close.shift()), abs(low - close.shift()))) | |
| df['+DM'] = np.where((high - high.shift()) > (low.shift() - low), np.maximum(high - high.shift(), 0), 0) | |
| df['-DM'] = np.where((low.shift() - low) > (high - high.shift()), np.maximum(low.shift() - low, 0), 0) | |
| tr14 = df['TR'].rolling(14).sum() | |
| plus_dm14 = df['+DM'].rolling(14).sum() | |
| minus_dm14 = df['-DM'].rolling(14).sum() | |
| plus_di14 = 100 * (plus_dm14 / tr14) | |
| minus_di14 = 100 * (minus_dm14 / tr14) | |
| dx = 100 * abs(plus_di14 - minus_di14) / (plus_di14 + minus_di14 + 1e-10) | |
| df['ADX'] = dx.rolling(14).mean() | |
| # CCI | |
| tp = (df['High'] + df['Low'] + df['Close']) / 3 | |
| cci_ma = tp.rolling(20).mean() | |
| cci_std = tp.rolling(20).std() | |
| df['CCI'] = (tp - cci_ma) / (0.015 * cci_std + 1e-10) | |
| indicators = { | |
| "MACD": round(df['MACD'].iloc[-1], 2), | |
| "ADX": round(df['ADX'].iloc[-1], 2), | |
| "CCI": round(df['CCI'].iloc[-1], 2), | |
| "RSI": round(df['RSI'].iloc[-1], 2) | |
| } | |
| tech_summary = "\n".join([f"{k}: {v}" for k, v in indicators.items()]) | |
| # Prepare features for LSTM | |
| features = np.column_stack([ | |
| df['Open'], df['High'], df['Low'], df['Close'], | |
| df['RSI'], df['RSI'], df['RSI'] | |
| ]) | |
| scaler = MinMaxScaler() | |
| scaled = scaler.fit_transform(features) | |
| predicted_prices = [] | |
| for i in range(60): | |
| sample = scaled[max(0, i - 59):i + 1] | |
| if sample.shape[0] < 60: | |
| sample = np.pad(sample, ((60 - sample.shape[0], 0), (0, 0)), mode='edge') | |
| sample = sample.reshape(1, 60, 7) | |
| pred = model.predict(sample, verbose=0) | |
| price = scaler.inverse_transform([[0, 0, 0, float(pred[0][0]), 0, 0, 0]])[0][3] | |
| predicted_prices.append(price) | |
| predicted_price = round(predicted_prices[-1], 2) | |
| last_close = float(df['Close'].iloc[-1]) | |
| confidence = round(abs(predicted_price - last_close) * 0.05, 2) | |
| # Adjust with YouTube Sentiment | |
| yt_sent = get_youtube_sentiment(symbol) | |
| yt_adj = predicted_price * (0.02 * yt_sent) | |
| adjusted_price = round(predicted_price + yt_adj, 2) | |
| reco = "Hold" | |
| if predicted_price > last_close * 1.02: | |
| reco = "Buy" | |
| elif predicted_price < last_close * 0.98: | |
| reco = "Sell" | |
| # Sentiment & Info | |
| industry = get_industry(symbol) | |
| stock_sent, stock_headlines = get_headlines_sentiment(symbol) | |
| industry_sent, industry_headlines = get_headlines_sentiment(industry) | |
| financials = get_financials(symbol) | |
| corp_info = get_corporate_actions(symbol) | |
| sector_chart = get_sector_comparison_chart(symbol) | |
| # ๐ Reason | |
| reason = f"""๐ Predicted Close: โน{adjusted_price} (YouTube Adj.) ยฑ โน{confidence} | |
| ๐ผ Last Close: โน{round(last_close, 2)} | |
| ๐ง Recommendation: {reco} | |
| ๐ฃ Sentiment score: {round(stock_sent, 2)} | Industry: {round(industry_sent, 2)} | |
| ๐ Financials: {', '.join([f"{k}: {v}" for k, v in financials.items()])}""" | |
| # Charts | |
| plt.figure(figsize=(8, 4)) | |
| plt.plot(df.index, df['Close'], label='Actual', marker='o') | |
| plt.plot(df.index, predicted_prices, label='Predicted', marker='x') | |
| plt.title(f"Actual vs Predicted (60D) - {symbol}") | |
| plt.xticks(rotation=45) | |
| plt.tight_layout() | |
| buf1 = io.BytesIO() | |
| plt.savefig(buf1, format="png") | |
| buf1.seek(0) | |
| chart1 = PIL.Image.open(buf1) | |
| intraday = yf.download(symbol, period='1d', interval='1m') | |
| plt.figure(figsize=(8, 4)) | |
| plt.plot(intraday.index, intraday['Close'], color='orange') | |
| plt.title(f"Live 1-Minute Chart - {symbol}") | |
| plt.tight_layout() | |
| buf2 = io.BytesIO() | |
| plt.savefig(buf2, format="png") | |
| buf2.seek(0) | |
| chart2 = PIL.Image.open(buf2) | |
| # Volume Chart | |
| plt.figure(figsize=(8, 3)) | |
| plt.plot(df.index, df['Volume'], color='green') | |
| plt.title(f"Volume Trend - {symbol}") | |
| plt.tight_layout() | |
| buf3 = io.BytesIO() | |
| plt.savefig(buf3, format="png") | |
| buf3.seek(0) | |
| volume_chart = PIL.Image.open(buf3) | |
| # Sentiment Chart | |
| def sentiment_label(score): | |
| if score > 0.2: | |
| return "๐ Bullish" | |
| elif score < -0.2: | |
| return "๐ Bearish" | |
| else: | |
| return "โ๏ธ Neutral" | |
| fig_sent, ax_sent = plt.subplots(figsize=(6, 4)) | |
| bars = ax_sent.bar(["Stock", "Industry"], [stock_sent, industry_sent], color=['purple', 'orange']) | |
| ax_sent.set_ylim(-1, 1) | |
| ax_sent.set_title("Sentiment Comparison") | |
| ax_sent.set_ylabel("Sentiment Score") | |
| ax_sent.grid(True, axis='y', linestyle='--', alpha=0.3) | |
| # Add sentiment labels | |
| for bar, score in zip(bars, [stock_sent, industry_sent]): | |
| label = sentiment_label(score) | |
| ax_sent.text( | |
| bar.get_x() + bar.get_width()/2, | |
| bar.get_height() + 0.05 if score > 0 else score - 0.15, | |
| f"{score:.2f}\n{label}", | |
| ha='center', fontsize=10 | |
| ) | |
| # Save chart | |
| buf_sent = io.BytesIO() | |
| fig_sent.savefig(buf_sent, format="png") | |
| buf_sent.seek(0) | |
| sentiment_chart = PIL.Image.open(buf_sent) | |
| return ( | |
| f"๐ LSTM: โน{predicted_price} | ๐ฅ YouTube Adjusted: โน{adjusted_price} ยฑ โน{confidence}", | |
| reco, | |
| reason.strip(), | |
| chart1, | |
| chart2, | |
| volume_chart, | |
| sentiment_chart, | |
| "\n".join(stock_headlines), | |
| "\n".join(industry_headlines), | |
| "\n".join([f"{k}: โน{v:,}" if isinstance(v, (int, float)) else f"{k}: {v}" for k, v in financials.items()]), | |
| tech_summary, | |
| corp_info, | |
| sector_chart, | |
| sentiment_interpretation(stock_sent, industry_sent), | |
| ) | |
| except Exception as e: | |
| blank = PIL.Image.new("RGB", (400, 200)) | |
| return ("Error", "-", str(e), blank, blank, blank, blank, "No news", "No news", {}, "", "", blank) | |
| def sentiment_interpretation(stock_sent, industry_sent): | |
| def classify(score): | |
| if score > 0.2: | |
| return "๐ Bullish" | |
| elif score < -0.2: | |
| return "๐ Bearish" | |
| else: | |
| return "โ๏ธ Neutral" | |
| summary = f"""๐ฃ Sentiment Interpretation: | |
| โข Stock Sentiment: {stock_sent:.2f} โ {classify(stock_sent)} | |
| โข Industry Sentiment: {industry_sent:.2f} โ {classify(industry_sent)}""" | |
| if stock_sent > industry_sent + 0.2: | |
| summary += "\nโ The stock is being viewed more positively than its industry." | |
| elif stock_sent < industry_sent - 0.2: | |
| summary += "\nโ ๏ธ The stock is less favored compared to its industry peers." | |
| else: | |
| summary += "\nโ๏ธ Sentiment is aligned with its sector." | |
| return summary | |
| def simulate_portfolio(portfolio_str): | |
| try: | |
| entries = [e.strip() for e in portfolio_str.split(',')] | |
| total_invested = total_now = total_pred_now = 0.0 | |
| details = [] | |
| stock_charts = [] | |
| for entry in entries: | |
| if not entry: | |
| continue | |
| parts = entry.split(':') | |
| if len(parts) != 3: | |
| continue | |
| symbol, qty_str, buy_date_str = parts | |
| try: | |
| qty = int(qty_str) | |
| buy_date = pd.to_datetime(buy_date_str) | |
| except: | |
| continue | |
| df = yf.download(symbol, start=buy_date - timedelta(days=70), end=datetime.today()) | |
| if df.empty or len(df) < 70: | |
| continue | |
| df = df.reset_index() | |
| # Compute RSI | |
| delta = df['Close'].diff() | |
| gain = delta.clip(lower=0) | |
| loss = -delta.clip(upper=0) | |
| avg_gain = gain.rolling(window=14).mean() | |
| avg_loss = loss.rolling(window=14).mean() | |
| rs = avg_gain / (avg_loss + 1e-10) | |
| df['RSI'] = (100 - (100 / (1 + rs))).fillna(50) | |
| actual_prices, predicted_prices, dates = [], [], [] | |
| for i in range(60, len(df)): | |
| window = df.iloc[i - 60:i] | |
| feats = np.column_stack([ | |
| window['Open'].values, | |
| window['High'].values, | |
| window['Low'].values, | |
| window['Close'].values, | |
| window['RSI'].values, | |
| window['RSI'].values, | |
| window['RSI'].values, | |
| ]) | |
| scaler = MinMaxScaler().fit(feats) | |
| scaled = scaler.transform(feats).reshape(1, 60, 7) | |
| raw_pred = model.predict(scaled, verbose=0)[0][0] | |
| pred_price = float(scaler.inverse_transform([[0, 0, 0, raw_pred, 0, 0, 0]])[0][3]) | |
| act_price = float(window['Close'].iloc[-1]) | |
| date_i = window['Date'].iloc[-1] | |
| if isinstance(date_i, pd.Timestamp): | |
| date_i = date_i.to_pydatetime() | |
| predicted_prices.append(pred_price * qty) | |
| actual_prices.append(act_price * qty) | |
| dates.append(date_i) | |
| if not actual_prices: | |
| continue | |
| buy_price = actual_prices[0] / qty | |
| current_price = actual_prices[-1] / qty | |
| pred_price_now = predicted_prices[-1] / qty | |
| invested = buy_price * qty | |
| current_value = current_price * qty | |
| pred_value_now = pred_price_now * qty | |
| total_invested += invested | |
| total_now += current_value | |
| total_pred_now += pred_value_now | |
| gain = current_value - invested | |
| percent = round((gain / invested) * 100, 2) | |
| details.append(f"{symbol}: Buy โน{buy_price:.2f} โ Now โน{current_price:.2f} | Return: {percent}%") | |
| # ๐ Individual chart | |
| plt.figure(figsize=(8, 4)) | |
| plt.plot(dates, actual_prices, label=f"{symbol} (Actual)", color='blue') | |
| plt.plot(dates, predicted_prices, '--', label=f"{symbol} (LSTM)", color='orange') | |
| plt.title(f"{symbol}: Actual vs LSTM Portfolio Value") | |
| plt.legend() | |
| plt.xticks(rotation=45) | |
| plt.tight_layout() | |
| buf = io.BytesIO() | |
| plt.savefig(buf, format='png') | |
| buf.seek(0) | |
| stock_charts.append(PIL.Image.open(buf)) | |
| if not stock_charts: | |
| blank = PIL.Image.new("RGB", (400, 200)) | |
| return "No valid portfolio data found. Please check format: SYMBOL:QTY:YYYY-MM-DD", [blank] | |
| summary = ( | |
| f"๐ผ Total Invested: โน{total_invested:.2f}\n" | |
| f"๐ Current Value: โน{total_now:.2f}\n" | |
| f"๐ค LSTM Predicted Value: โน{total_pred_now:.2f}\n\n" | |
| f"๐ Net Return (Actual): โน{total_now - total_invested:.2f} " | |
| f"({((total_now - total_invested)/total_invested)*100:.2f}%)\n" | |
| f"๐ Net Return (LSTM): โน{total_pred_now - total_invested:.2f} " | |
| f"({((total_pred_now - total_invested)/total_invested)*100:.2f}%)\n\n" | |
| + "\n".join(details) | |
| ) | |
| return summary, stock_charts | |
| except Exception as e: | |
| blank = PIL.Image.new("RGB", (400, 200)) | |
| return f"Error: {str(e)}", [blank] | |
| def generate_recommendation(summary_text): | |
| try: | |
| lines = summary_text.strip().split("\n") | |
| if not lines or "Total Invested" not in lines[0]: | |
| return "Please simulate your portfolio first before generating advice." | |
| invested = float(lines[0].split(": โน")[1].replace(',', '')) | |
| current = float(lines[1].split(": โน")[1].replace(',', '')) | |
| predicted = float(lines[2].split(": โน")[1].replace(',', '')) | |
| actual_return = round((current - invested) / invested * 100, 2) | |
| predicted_return = round((predicted - invested) / invested * 100, 2) | |
| gap = round(predicted_return - actual_return, 2) | |
| direction = "underperformed" if gap > 0 else "outperformed" | |
| stock_lines = [line for line in lines if ':' in line and 'Buy' in line and 'Return:' in line] | |
| high_performers = [] | |
| low_performers = [] | |
| sectors = [] | |
| for stock_line in stock_lines: | |
| try: | |
| parts = stock_line.split("Return: ") | |
| percent = float(parts[1].replace('%', '')) | |
| symbol = stock_line.split(":")[0] | |
| if percent >= 10: | |
| high_performers.append((stock_line, percent)) | |
| elif percent <= -5: | |
| low_performers.append((stock_line, percent)) | |
| if symbol in ["TCS", "INFY", "WIPRO", "HCLTECH"]: | |
| sectors.append("IT") | |
| elif symbol in ["RELIANCE", "ONGC", "IOC"]: | |
| sectors.append("Energy") | |
| elif symbol in ["ICICIBANK", "HDFCBANK", "KOTAKBANK"]: | |
| sectors.append("Banking") | |
| except: | |
| continue | |
| sector_comment = "" | |
| if sectors: | |
| sector_summary = ", ".join(sorted(set(sectors))) | |
| if len(set(sectors)) == 1: | |
| sector_comment = f"Note: Your portfolio is concentrated in the {sector_summary} sector. This may expose you to sector-specific risks." | |
| elif len(set(sectors)) <= 2: | |
| sector_comment = f"Your holdings are mostly in {sector_summary} sectors. Consider diversifying further for better stability." | |
| recommendation = f""" | |
| Portfolio Overview | |
| Total Invested: โน{invested:,.2f} | |
| Current Market Value: โน{current:,.2f} | |
| Model Projection: โน{predicted:,.2f} | |
| Actual Return: {actual_return:.2f}% | |
| Projected Return: {predicted_return:.2f}% | |
| Performance Gap: {abs(gap):.2f}% ({'Below Expectation' if gap > 0 else 'Above Expectation'}) | |
| Performance Insights | |
| Your portfolio has {direction} the model forecast by {abs(gap):.2f}%. | |
| """ | |
| if high_performers: | |
| recommendation += "\n\nTop Performing Stocks\n" | |
| for stock, _ in high_performers: | |
| recommendation += f"- {stock}\n" | |
| if low_performers: | |
| recommendation += "\nStocks to Watch Closely\n" | |
| for stock, _ in low_performers: | |
| recommendation += f"- {stock}\n" | |
| if sector_comment: | |
| recommendation += f"\n{sector_comment}\n" | |
| recommendation += """ | |
| Strategic Suggestions | |
| * Consider trimming underperforming stocks with consistent decline. | |
| * Hold top-performing stocks with positive RSI and sentiment. | |
| * Reassess your industry bias if your portfolio underperforms. | |
| Risk Management | |
| * Use stop-loss orders (~7% below CMP) on volatile stocks. | |
| * RSI > 70 = overbought (consider profit booking). | |
| * RSI < 30 = oversold (consider averaging if fundamentals strong). | |
| Final Thoughts | |
| Diversification, review cycles (quarterly), and informed decisions based on predictions, sentiment, and fundamentals will improve resilience and returns. | |
| """ | |
| return recommendation.strip() | |
| except Exception as e: | |
| return f"Error generating recommendation: {str(e)}" | |
| import yfinance as yf | |
| import numpy as np | |
| # Fallback average sector PE values | |
| SECTOR_PE_AVERAGES = { | |
| 'IT': 24, 'Banking': 14, 'FMCG': 32, 'Auto': 18, 'Energy': 11, | |
| 'Pharma': 21, 'Metal': 9, 'Finance': 13, 'Realty': 19, 'Infra': 15, 'General': 17 | |
| } | |
| def estimate_cagr(eps_now, eps_past, years): | |
| try: | |
| return (eps_now / eps_past)**(1 / years) - 1 | |
| except: | |
| return 0.10 # fallback growth | |
| def eps_based_dcf(symbol, years=5): | |
| if not symbol.endswith(".NS"): | |
| symbol += ".NS" | |
| try: | |
| ticker = yf.Ticker(symbol) | |
| info = ticker.info | |
| current_price = info.get("currentPrice", 0) | |
| eps_now = info.get("trailingEps", 0) | |
| book_value = info.get("bookValue", 0) | |
| roe = info.get("returnOnEquity", 0) | |
| beta = info.get("beta", 1.0) | |
| sector = info.get("sector", "General") | |
| if eps_now <= 0: | |
| return f"โ ๏ธ EPS is negative or zero. DCF model not applicable." | |
| # Estimate EPS growth | |
| eps_past = eps_now / 1.8 if eps_now > 0 else 1 | |
| growth_rate = estimate_cagr(eps_now, eps_past, years) | |
| # Apply minimum discount rate (8%) | |
| discount_rate = max(0.06 + beta * 0.05, 0.08) | |
| # Project future EPS and discount them | |
| projected_eps = [] | |
| discounted_eps = [] | |
| for t in range(1, years + 1): | |
| future_eps = eps_now * (1 + growth_rate) ** t | |
| pv = future_eps / (1 + discount_rate) ** t | |
| projected_eps.append(future_eps) | |
| discounted_eps.append(pv) | |
| # Cap terminal P/E at 30 | |
| raw_pe = info.get("forwardPE", SECTOR_PE_AVERAGES.get(sector, 17)) | |
| pe_terminal = min(raw_pe if raw_pe and raw_pe > 0 else 17, 30) | |
| terminal_eps = projected_eps[-1] | |
| terminal_value = terminal_eps * pe_terminal | |
| terminal_pv = terminal_value / (1 + discount_rate) ** years | |
| intrinsic_value = round(sum(discounted_eps) + terminal_pv, 2) | |
| summary = f""" | |
| ๐ EPS-Based DCF Valuation for {symbol.upper()} | |
| ๐ Current Price: โน{current_price:.2f} | |
| ๐ EPS: โน{eps_now:.2f} | |
| ๐ Estimated EPS CAGR: {growth_rate*100:.2f}% | |
| โ๏ธ Discount Rate (CAPM): {discount_rate*100:.2f}% | |
| ๐ญ Sector: {sector}, Terminal PE (capped): {pe_terminal} | |
| ๐ฐ Valuation: | |
| โข PV of 5Y EPS: โน{round(sum(discounted_eps), 2)} | |
| โข Terminal Value (discounted): โน{round(terminal_pv, 2)} | |
| โข ๐ฏ Estimated Fair Value: โน{intrinsic_value} | |
| Verdict: {"โ Undervalued" if intrinsic_value > current_price else "โ ๏ธ Possibly Overvalued"} | |
| """ | |
| return summary.strip() | |
| except Exception as e: | |
| return f"โ Error calculating DCF for {symbol}: {str(e)}" | |
| # Optional wrapper for Gradio or UI integration | |
| def fair_value_ui(symbol): | |
| try: | |
| return eps_based_dcf(symbol) | |
| except Exception as e: | |
| return f"โ Error: {str(e)}" | |
| # Example run | |
| if __name__ == "__main__": | |
| print(fair_value_ui("RELIANCE.NS")) | |
| with gr.Blocks(css=custom_css) as app: | |
| with gr.Tabs(): | |
| # ๐ Prediction Tab | |
| with gr.Tab("๐ Prediction"): | |
| gr.Markdown("## ๐ Stock Prediction with LSTM & Sentiment") | |
| symbol = gr.Textbox(label="๐ Stock Symbol (e.g., RELIANCE.NS)", value="RELIANCE.NS") | |
| predict_btn = gr.Button("๐ฎ Predict Tomorrow") | |
| refresh_btn = gr.Button("๐ Refresh Live Snapshot") | |
| with gr.Row(): | |
| live_price = gr.Textbox(label="๐ฐ Live Price") | |
| day_range = gr.Textbox(label="๐ Day High / Low") | |
| rsi_val = gr.Textbox(label="๐ RSI") | |
| pe_val = gr.Textbox(label="๐ PE Ratio") | |
| with gr.Row(): | |
| bb_range = gr.Textbox(label="๐ Bollinger Bands") | |
| week_52 = gr.Textbox(label="๐ 52W High / Low") | |
| with gr.Row(): | |
| pred = gr.Textbox(label="๐ Predicted Price ยฑ Range") | |
| reco = gr.Textbox(label="๐ก Recommendation") | |
| reason = gr.Textbox(label="๐ Prediction Reason", lines=12, show_copy_button=True) | |
| chart1 = gr.Image(label="๐ 60-Day Actual vs Predicted", height=250) | |
| chart2 = gr.Image(label="โฑ๏ธ Live 1-Minute Chart", height=250) | |
| volume_chart = gr.Image(label="๐ Volume Trend", height=250) | |
| sentiment_chart = gr.Image(label="๐ง Sentiment Score Comparison", height=250) | |
| with gr.Row(): | |
| stock_news = gr.Textbox(label="๐ฐ Stock Headlines", lines=4) | |
| industry_news = gr.Textbox(label="๐ญ Industry Headlines", lines=4) | |
| finance = gr.Textbox(label="๐ Financial Indicators", lines=5) | |
| tech_box = gr.Textbox(label="๐ Technical Indicators", lines=4) | |
| corp_actions_box = gr.Textbox(label="๐ข Dividends & Splits", lines=3) | |
| sector_chart = gr.Image(label="๐ Sector vs Stock Comparison", height=250) | |
| sentiment_summary = gr.Textbox(label="๐ฃ Sentiment Interpretation", lines=4, show_copy_button=True) | |
| # ๐งฎ Portfolio Tab | |
| with gr.Tab("๐งฎ Portfolio"): | |
| gr.Markdown("### ๐ Portfolio Simulator") | |
| portfolio_input = gr.Textbox(label="๐ฅ Portfolio (SYMBOL:QTY:YYYY-MM-DD)", placeholder="e.g. RELIANCE.NS:10:2024-12-01") | |
| simulate_btn = gr.Button("๐ Simulate Portfolio") | |
| port_summary = gr.Textbox(label="๐ Summary", lines=10, show_copy_button=True) | |
| port_chart = gr.Gallery(label="๐ Stockwise Charts", columns=2, height=400) | |
| rec_btn = gr.Button("๐ง Personalized Advice") | |
| advice_box = gr.Textbox(label="๐ AI-Powered Investment Recommendation", lines=25, show_copy_button=True) | |
| # ๐ง Market Mind Reader Tab | |
| with gr.Tab("๐ง Market Mind Reader"): | |
| gr.Markdown("### ๐ง Predict Retail Investor Behavior Using Sentiment + Fundamentals") | |
| mm_symbol = gr.Textbox(label="Enter Stock Symbol", value="RELIANCE.NS") | |
| mm_btn = gr.Button("๐ Analyze Behavior") | |
| mm_output = gr.Textbox(label="๐ง Behavior Prediction", lines=10, show_copy_button=True) | |
| # ๐งญ Greed & Fear Gauge Tab | |
| with gr.Tab("๐งญ Greed & Fear Gauge"): | |
| gr.Markdown("### ๐งญ Real-Time Greed & Fear Score") | |
| gr.Markdown(""" | |
| **๐ฅ Red** โ **Extreme Greed** (Score > 70) | |
| **๐จ Yellow** โ **Neutral / Caution** (Score between 30โ70) | |
| **๐ฉ Green** โ **Fear / Panic** (Score < 30) | |
| """) | |
| gf_symbol = gr.Textbox(label="Enter Stock Symbol", value="RELIANCE.NS") | |
| gf_btn = gr.Button("๐ Show Market Mood") | |
| gf_output = gr.Textbox(label="๐ Greed & Fear Result", lines=5, show_copy_button=True) | |
| # ๐ Screener Tab (with user stock input) | |
| with gr.Tab("๐ Screener"): | |
| gr.Markdown("### ๐ Technical Screener for Any Stock") | |
| gr.Markdown("Enter any NSE stock and analyze RSI, MACD, and volume spike.") | |
| screen_symbol = gr.Textbox(label="Enter NSE Stock Symbol", value="RELIANCE.NS") | |
| screen_btn = gr.Button("๐ฆ Run Screener") | |
| screen_output = gr.Textbox(label="๐ Screener Result", lines=10, show_copy_button=True) | |
| # ๐ Fair Price Estimator Tab | |
| with gr.Tab("๐ Fair Price"): | |
| gr.Markdown("### ๐ Estimate the Intrinsic Fair Value of a Stock") | |
| fair_symbol = gr.Textbox(label="Enter NSE Symbol", value="RELIANCE.NS") | |
| fair_btn = gr.Button("๐ Calculate Fair Price") | |
| fair_output = gr.Textbox(label="๐งฎ Fair Value Summary", lines=10, show_copy_button=True) | |
| # ๐ Glossary Tab | |
| with gr.Tab("๐ Glossary"): | |
| gr.Markdown("### ๐ Financial & Technical Terms Explained") | |
| gr.Markdown(""" | |
| **1. RSI** โ Measures momentum; >70 overbought, <30 oversold. | |
| **2. PE Ratio** โ Price divided by earnings per share. | |
| **3. Bollinger Bands** โ Shows volatility range around a moving average. | |
| **4. MACD** โ Signals momentum via moving average crossovers. | |
| **5. ADX** โ Measures trend strength. Above 25 = strong trend. | |
| **6. CCI** โ Shows how much price deviates from average trend. | |
| **7. Volume** โ Number of shares traded; high = strong interest. | |
| **8. Book Value** โ Net asset value per share. | |
| **9. Market Cap** โ Companyโs total stock value. | |
| **10. Debt-to-Equity** โ Ratio showing financial leverage. | |
| **11. Dividend Yield** โ Dividend as a % of current price. | |
| **12. Stop-Loss** โ Risk control to limit losses on a trade. | |
| **13. 52W High/Low** โ Highest and lowest prices in the last 1 year. | |
| **14. Support/Resistance** โ Key levels where price often reverses. | |
| **15. Moving Averages** โ Smoothens price trends over time. | |
| **16. Sentiment Score** โ Score based on news/public mood. | |
| **17. Insider Trading** โ Buying/selling by company insiders. | |
| **18. Sector Comparison** โ Compare your stock to sector index. | |
| **19. Beta** โ Volatility relative to the market. | |
| **20. LSTM** โ AI model that predicts price using past data. | |
| """) | |
| # โน๏ธ About Tab | |
| with gr.Tab("โน๏ธ About"): | |
| gr.Markdown(""" | |
| ### โน๏ธ About Trade Sense โ Your AI Investing Assistant | |
| **Trade Sense** is a powerful AI-based platform designed to empower Indian investors with predictive analytics, technical insights, and smart financial decision-making. | |
| --- | |
| ### ๐ Features Overview: | |
| - **๐ Stock Prediction**: | |
| Forecast next-day prices using LSTM neural networks and real-time indicators like RSI, MACD, and sentiment from Google News and YouTube. | |
| - **๐งฎ Portfolio Simulator**: | |
| Backtest and simulate your investments. Compare actual returns with AI-predicted returns. Get visual charts and personalized AI advice. | |
| - **๐ง Market Mind Reader**: | |
| Predict retail investor behavior using a mix of fundamentals (PE ratio, volume) and sentiment scores. | |
| - **๐งญ Greed & Fear Gauge**: | |
| Market mood index based on volatility, RSI, volume spikes, and media sentiment. | |
| - **๐ Screener**: | |
| Scan NSE stocks for bullish or bearish breakout patterns using Stochastic Oscillator, MACD, and volume surge. | |
| - **๐ Fair Price**: | |
| Estimate a stockโs true worth using an EPS-based DCF model. Projects earnings growth, caps terminal PE, and discounts using CAPM logic. | |
| - **๐ Glossary**: | |
| Understand key financial and technical terms that power your investment insights. | |
| --- | |
| ### ๐ค Under the Hood: | |
| - **AI Model**: LSTM (Long Short-Term Memory) trained on 10 years of NSE data | |
| - **Sentiment Analysis**: Google News RSS, YouTube API, TextBlob | |
| - **Data Sources**: yFinance, Matplotlib | |
| - **Platform**: Built using Gradio and hosted via Hugging Face or Render | |
| --- | |
| ๐ก Whether you're a beginner or a pro, Trade Sense helps you make smarter, data-driven investment decisions. | |
| **Made by Akshayaa ๐ฉโ๐ป** โ Powered by AI, driven by passion. | |
| """) | |
| # โ ๏ธ Disclaimer Tab | |
| with gr.Tab("โ ๏ธ Disclaimer"): | |
| gr.HTML(""" | |
| <div style='background-color:#fff8e1; padding:12px; border-radius:8px; color:#000; font-size:14px;'> | |
| โ ๏ธ <strong>Disclaimer</strong>: This application is for educational purposes only. All AI-based predictions and insights are illustrative and should not be considered financial advice. Always consult a licensed financial advisor before investing. | |
| </div> | |
| """) | |
| # --- Button Bindings --- | |
| refresh_btn.click(fetch_live_snapshot, inputs=symbol, outputs=[ | |
| live_price, day_range, rsi_val, pe_val, bb_range, week_52 | |
| ]) | |
| predict_btn.click(predict_price, inputs=symbol, outputs=[ | |
| pred, reco, reason, | |
| chart1, chart2, volume_chart, sentiment_chart, | |
| stock_news, industry_news, finance, | |
| tech_box, corp_actions_box, sector_chart, | |
| sentiment_summary | |
| ]) | |
| simulate_btn.click(simulate_portfolio, inputs=portfolio_input, outputs=[ | |
| port_summary, port_chart | |
| ]) | |
| rec_btn.click(generate_recommendation, inputs=port_summary, outputs=advice_box) | |
| mm_btn.click(market_mind_reader, inputs=mm_symbol, outputs=mm_output) | |
| gf_btn.click(greed_fear_gauge, inputs=gf_symbol, outputs=gf_output) | |
| screen_btn.click(run_technical_screener, inputs=screen_symbol, outputs=screen_output) | |
| fair_btn.click(fair_value_ui, inputs=fair_symbol, outputs=fair_output) | |
| # ๐ Launch the App | |
| app.launch() | |