Spaces:
Sleeping
Sleeping
| from flask import Flask, request, render_template_string, jsonify | |
| from datasets import load_dataset | |
| from datetime import datetime, timedelta | |
| from sklearn.ensemble import RandomForestRegressor | |
| from sklearn.model_selection import train_test_split | |
| from sklearn.metrics import mean_absolute_error | |
| import pandas as pd | |
| import numpy as np | |
| import yfinance as yf | |
| app = Flask(__name__) | |
| # ================= Load Indian Stock Symbol Data set ================= | |
| # Load a dataset from Hugging Face | |
| dataset = load_dataset("ThunderDrag/India-Stock-Symbols-and-Metadata", split="train") | |
| # Convert the dataset to a Pandas DataFrame (Table) | |
| df_stocks = pd.DataFrame(dataset) | |
| # =============== Data Fetching =============== | |
| def fetch_stock_data(symbol, period="5y"): | |
| """Fetch historical stock data from Yahoo Finance""" | |
| stock = yf.Ticker(symbol) | |
| data = stock.history(period=period) | |
| data = data.sort_index(ascending=True) | |
| return data | |
| # ================= Technical Indicators ================= | |
| def calculate_moving_averages(data): | |
| data["50MA"] = data["Close"].rolling(window=50).mean() | |
| data["200MA"] = data["Close"].rolling(window=200).mean() | |
| return data | |
| def determin_trend(data): | |
| if ( | |
| data["50MA"].iloc[-1] > data["200MA"].iloc[-1] | |
| and data["Close"].iloc[-1] > data["50MA"].iloc[-1] | |
| ): | |
| trend = "UPTREND (bullish for next 1-3 months)" | |
| elif ( | |
| data["50MA"].iloc[-1] < data["200MA"].iloc[-1] | |
| and data["Close"].iloc[-1] < data["50MA"].iloc[-1] | |
| ): | |
| trend = "DOWNTREND (bearish for next 1-3 months)" | |
| else: | |
| trend = "SIDEWAYS (uncertain)" | |
| return trend | |
| def calculate_RSI(data, window=14): | |
| delta = data["Close"].diff() | |
| gain = delta.where(delta > 0, 0) | |
| loss = -delta.where(delta < 0, 0) | |
| avg_gain = gain.rolling(window=window).mean() | |
| avg_loss = loss.rolling(window=window).mean() | |
| rs = avg_gain / avg_loss | |
| data["RSI"] = 100 - (100 / (1 + rs)) | |
| return data | |
| def calculate_MACD(data, fast=12, slow=26, signal=9): | |
| data["EMA_fast"] = data["Close"].ewm(span=fast, adjust=False).mean() | |
| data["EMA_slow"] = data["Close"].ewm(span=slow, adjust=False).mean() | |
| data["MACD"] = data["EMA_fast"] - data["EMA_slow"] | |
| data["MACD_signal"] = data["MACD"].ewm(span=signal, adjust=False).mean() | |
| return data | |
| # =============== Random Forest Forecast =============== | |
| """ | |
| This function uses a Random Forest ML model to learn from | |
| historical stock indicators and predict stock prices for the next 30 days. | |
| """ | |
| def random_forest_forecast(data, days_ahead=30): | |
| """ | |
| Predict future stock prices using Random Forest Regressor | |
| """ | |
| df = data.copy() | |
| # next-day close as target | |
| df["Target"] = df["Close"].shift(-1) | |
| # Drop last row with NaN target | |
| df = df.dropna() | |
| # Features (you can add more indicators here) | |
| features = ["Close", "50MA", "200MA", "RSI", "MACD", "MACD_signal"] | |
| # drop rows with NaN from indicators | |
| df = df.dropna() | |
| # Feature matrix and target vector | |
| X = df[features] | |
| y = df["Target"] | |
| # Train/test split | |
| X_train, X_test, y_train, y_test = train_test_split( | |
| X, y, test_size=0.2, shuffle=False | |
| ) | |
| # Train model | |
| model = RandomForestRegressor(n_estimators=200, random_state=42) | |
| model.fit(X_train, y_train) | |
| # Evaluate | |
| y_pred = model.predict(X_test) | |
| mae = mean_absolute_error(y_test, y_pred) | |
| print(f"Random Forest MAE: {mae:.2f}") | |
| # Forecast future price iteratively | |
| last_known = X.iloc[-1].values.reshape(1, -1) | |
| forecast_prices = [] | |
| for _ in range(days_ahead): | |
| pred = model.predict(last_known)[0] | |
| forecast_prices.append(pred) | |
| # update only Close for simplicity | |
| last_known[0, 0] = pred | |
| return forecast_prices[-1], forecast_prices | |
| # ================= Entry / Stoploss ================= | |
| def calculate_entry_stoploss(data, trend, stoploss_percent=5): | |
| entry_price = None | |
| stop_loss = None | |
| if ( | |
| trend.startswith("UPTREND") | |
| and data["RSI"].iloc[-1] < 70 | |
| and data["MACD"].iloc[-1] > data["MACD_signal"].iloc[-1] | |
| ): | |
| entry_price = data["Close"].iloc[-1] | |
| stop_loss = entry_price * (1 - stoploss_percent / 100) | |
| return entry_price, stop_loss | |
| # ================= Autocomplete API ================= | |
| def autocomplete(): | |
| query = request.args.get("q", "").lower() | |
| if not query: | |
| return jsonify([]) | |
| # Filter dataframe where stock name contains query (case-insensitive) | |
| filtered = df_stocks[df_stocks["name"].str.lower().str.contains(query)].head(10) | |
| # Convert to list of dicts | |
| suggestions = ( | |
| filtered[["name", "ticker"]] | |
| .rename(columns={"ticker": "symbol"}) | |
| .to_dict(orient="records") | |
| ) | |
| return jsonify(suggestions) | |
| # ================= Flask Routes ================= | |
| def index(): | |
| result = None | |
| table_html = None | |
| if request.method == "POST": | |
| selected_symbol = request.form["symbol"] | |
| selected_name = request.form.get("stock_name", selected_symbol) | |
| yahoo_symbol = ( | |
| f"{selected_symbol}.NS" | |
| if not selected_symbol.endswith(".NS") | |
| else selected_symbol | |
| ) | |
| print("Selected symbol for Yahoo Finance:", yahoo_symbol) | |
| # Print selected symbol for debugging | |
| print("Selected stock symbol:", selected_symbol) | |
| data = fetch_stock_data(yahoo_symbol) | |
| # Technical Indicators | |
| data = calculate_moving_averages(data) | |
| data = calculate_RSI(data) | |
| data = calculate_MACD(data) | |
| trend = determin_trend(data) | |
| predicted_price, _ = random_forest_forecast(data) | |
| entry_price, stop_loss = calculate_entry_stoploss(data, trend) | |
| current_price = data["Close"].iloc[-1] | |
| price_difference = predicted_price - current_price | |
| profit_or_loss = ((predicted_price - current_price) / current_price) * 100 | |
| table_html = ( | |
| data.tail(30) | |
| .reset_index() | |
| .to_html(classes="table table-striped table-bordered", index=False) | |
| ) | |
| result = { | |
| "symbol": selected_symbol, | |
| "name": selected_name, | |
| "trend": trend, | |
| "current_price": f"{current_price:.2f}", | |
| "predicted_price": f"{predicted_price:.2f}", | |
| "price_difference": f"{price_difference:.2f}", | |
| "profit_or_loss": f"{profit_or_loss:.2f}%", | |
| "entry_price": f"{entry_price:.2f}" if entry_price else "No Entry Signal", | |
| "stop_loss": f"{stop_loss:.2f}" if stop_loss else "-", | |
| "date_now": data.index[-1].date(), | |
| "future_date": datetime.now().date() + timedelta(days=30), | |
| } | |
| return render_template_string( | |
| """ | |
| <html> | |
| <head> | |
| <title>Stock Predictor</title> | |
| <link rel="stylesheet" | |
| href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"> | |
| <style> | |
| body { padding: 30px; } | |
| th, td { text-align: center; } | |
| .result-card { margin-top: 40px; } | |
| #loader-overlay { | |
| display: none; | |
| position: fixed; | |
| top: 0; left: 0; | |
| width: 100%; height: 100%; | |
| background: rgba(255, 255, 255, 0.8); | |
| z-index: 9999; | |
| justify-content: center; | |
| align-items: center; | |
| flex-direction: column; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="loader-overlay"> | |
| <div class="spinner-border text-primary" role="status" style="width: 4rem; height: 4rem;"></div> | |
| <p class="mt-3 fw-bold text-primary">Predicting...</p> | |
| </div> | |
| <div class="container"> | |
| <h3 class="text-center mb-4">Stock Prediction Dashboard</h3> | |
| <form method="POST" class="text-center mb-4" onsubmit="showLoader()"> | |
| <div class="row justify-content-center"> | |
| <div class="col-md-4"> | |
| <input type="hidden" name="stock_name" id="stock-name"> | |
| <input type="text" name="symbol" id="stock-input" class="form-control" placeholder="Enter stock name" autocomplete="off" value="{{ result.symbol if result }}"> | |
| <div id="suggestions" class="list-group"></div> | |
| </div> | |
| <div class="col-md-2"> | |
| <button type="submit" class="btn btn-primary w-100">Predict</button> | |
| </div> | |
| </div> | |
| </form> | |
| {% if result %} | |
| <div class="card result-card shadow"> | |
| <div class="card-body"> | |
| <h5 class="card-title text-center">Report for {{ result.name }}</h5> | |
| <p><b>Trend:</b> {{ result.trend }}</p> | |
| <p><b>Current Price:</b> ₹{{ result.current_price }} ({{ result.date_now }})</p> | |
| <p><b>Predicted Price (Next 30 Days):</b> ₹{{ result.predicted_price }} ({{ result.future_date }})</p> | |
| <p><b>Price Difference:</b> ₹{{ result.price_difference }}</p> | |
| <p><b>Expected Return:</b> {{ result.profit_or_loss }}</p> | |
| <p><b>Entry Price:</b> {{ result.entry_price }}</p> | |
| <p><b>Stop Loss:</b> {{ result.stop_loss }}</p> | |
| </div> | |
| </div> | |
| <div class="mt-4"> | |
| <h5>Last 30 Days Data</h5> | |
| {{ table_html | safe }} | |
| </div> | |
| {% endif %} | |
| </div> | |
| <script> | |
| function showLoader() { | |
| document.getElementById("loader-overlay").style.display = "flex"; | |
| } | |
| // Autocomplete | |
| const input = document.getElementById("stock-input"); | |
| const stockNameInput = document.getElementById("stock-name"); | |
| const suggestionsBox = document.getElementById("suggestions"); | |
| input.addEventListener("input", async function() { | |
| const query = this.value; | |
| if (query.length < 1) { | |
| suggestionsBox.innerHTML = ""; | |
| return; | |
| } | |
| const response = await fetch("/autocomplete?q=" + query); | |
| const data = await response.json(); | |
| suggestionsBox.innerHTML = ""; | |
| data.forEach(item => { | |
| const div = document.createElement("div"); | |
| div.classList.add("list-group-item", "list-group-item-action"); | |
| div.textContent = `${item.name} (${item.symbol})`; | |
| div.onclick = () => { | |
| // Symbol send to backend | |
| input.value = item.symbol; | |
| // Name displayed in report | |
| stockNameInput.value = item.name; | |
| suggestionsBox.innerHTML = ""; | |
| }; | |
| suggestionsBox.appendChild(div); | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| """, | |
| result=result, | |
| table_html=table_html, | |
| ) | |
| # Run the app | |
| if __name__ == "__main__": | |
| # Run on local host | |
| # app.run(debug=True) | |
| # Run using public IP | |
| # app.run(host="0.0.0.0", port=5000, debug=True) | |
| # Hugging Face uses port 7860 by default | |
| app.run(host="0.0.0.0", port=7860) | |