Nrup Parikh commited on
Commit
9a57870
·
1 Parent(s): b2232f9

Indian Stock Forcast Using Huggingface with search

Browse files
Files changed (3) hide show
  1. Dockerfile +19 -0
  2. app.py +323 -0
  3. requirements.txt +20 -0
Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
+ # you will also find guides on how best to write your Dockerfile
3
+
4
+ FROM python:3.9
5
+
6
+ RUN useradd -m -u 1000 user
7
+ USER user
8
+ ENV PATH="/home/user/.local/bin:$PATH"
9
+
10
+ WORKDIR /app
11
+
12
+ COPY --chown=user ./requirements.txt requirements.txt
13
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
14
+
15
+ COPY --chown=user . /app
16
+
17
+ # Corrected CMD line: Use -b (or --bind) for host:port
18
+ # The common best practice is to also specify the number of worker processes (-w)
19
+ CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:7860", "app:app"]
app.py ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, render_template_string, jsonify
2
+ from datasets import load_dataset
3
+ from datetime import datetime, timedelta
4
+ from sklearn.ensemble import RandomForestRegressor
5
+ from sklearn.model_selection import train_test_split
6
+ from sklearn.metrics import mean_absolute_error
7
+ import pandas as pd
8
+ import numpy as np
9
+ import yfinance as yf
10
+
11
+ app = Flask(__name__)
12
+
13
+ # ================= Load Indian Stock Symbol Data set =================
14
+ # Load a dataset from Hugging Face
15
+ dataset = load_dataset("ThunderDrag/India-Stock-Symbols-and-Metadata", split="train")
16
+ # Convert the dataset to a Pandas DataFrame (Table)
17
+ df_stocks = pd.DataFrame(dataset)
18
+
19
+
20
+ # =============== Data Fetching ===============
21
+ def fetch_stock_data(symbol, period="5y"):
22
+ """Fetch historical stock data from Yahoo Finance"""
23
+ stock = yf.Ticker(symbol)
24
+ data = stock.history(period=period)
25
+ data = data.sort_index(ascending=True)
26
+ return data
27
+
28
+
29
+ # ================= Technical Indicators =================
30
+ def calculate_moving_averages(data):
31
+ data["50MA"] = data["Close"].rolling(window=50).mean()
32
+ data["200MA"] = data["Close"].rolling(window=200).mean()
33
+ return data
34
+
35
+
36
+ def determin_trend(data):
37
+ if (
38
+ data["50MA"].iloc[-1] > data["200MA"].iloc[-1]
39
+ and data["Close"].iloc[-1] > data["50MA"].iloc[-1]
40
+ ):
41
+ trend = "UPTREND (bullish for next 1-3 months)"
42
+ elif (
43
+ data["50MA"].iloc[-1] < data["200MA"].iloc[-1]
44
+ and data["Close"].iloc[-1] < data["50MA"].iloc[-1]
45
+ ):
46
+ trend = "DOWNTREND (bearish for next 1-3 months)"
47
+ else:
48
+ trend = "SIDEWAYS (uncertain)"
49
+ return trend
50
+
51
+
52
+ def calculate_RSI(data, window=14):
53
+ delta = data["Close"].diff()
54
+ gain = delta.where(delta > 0, 0)
55
+ loss = -delta.where(delta < 0, 0)
56
+ avg_gain = gain.rolling(window=window).mean()
57
+ avg_loss = loss.rolling(window=window).mean()
58
+ rs = avg_gain / avg_loss
59
+ data["RSI"] = 100 - (100 / (1 + rs))
60
+ return data
61
+
62
+
63
+ def calculate_MACD(data, fast=12, slow=26, signal=9):
64
+ data["EMA_fast"] = data["Close"].ewm(span=fast, adjust=False).mean()
65
+ data["EMA_slow"] = data["Close"].ewm(span=slow, adjust=False).mean()
66
+ data["MACD"] = data["EMA_fast"] - data["EMA_slow"]
67
+ data["MACD_signal"] = data["MACD"].ewm(span=signal, adjust=False).mean()
68
+ return data
69
+
70
+
71
+ # =============== Random Forest Forecast ===============
72
+ """
73
+ This function uses a Random Forest ML model to learn from
74
+ historical stock indicators and predict stock prices for the next 30 days.
75
+ """
76
+
77
+
78
+ def random_forest_forecast(data, days_ahead=30):
79
+ """
80
+ Predict future stock prices using Random Forest Regressor
81
+ """
82
+ df = data.copy()
83
+ # next-day close as target
84
+ df["Target"] = df["Close"].shift(-1)
85
+ # Drop last row with NaN target
86
+ df = df.dropna()
87
+ # Features (you can add more indicators here)
88
+ features = ["Close", "50MA", "200MA", "RSI", "MACD", "MACD_signal"]
89
+ # drop rows with NaN from indicators
90
+ df = df.dropna()
91
+ # Feature matrix and target vector
92
+ X = df[features]
93
+ y = df["Target"]
94
+
95
+ # Train/test split
96
+ X_train, X_test, y_train, y_test = train_test_split(
97
+ X, y, test_size=0.2, shuffle=False
98
+ )
99
+
100
+ # Train model
101
+ model = RandomForestRegressor(n_estimators=200, random_state=42)
102
+ model.fit(X_train, y_train)
103
+
104
+ # Evaluate
105
+ y_pred = model.predict(X_test)
106
+ mae = mean_absolute_error(y_test, y_pred)
107
+ print(f"Random Forest MAE: {mae:.2f}")
108
+
109
+ # Forecast future price iteratively
110
+ last_known = X.iloc[-1].values.reshape(1, -1)
111
+ forecast_prices = []
112
+ for _ in range(days_ahead):
113
+ pred = model.predict(last_known)[0]
114
+ forecast_prices.append(pred)
115
+ # update only Close for simplicity
116
+ last_known[0, 0] = pred
117
+
118
+ return forecast_prices[-1], forecast_prices
119
+
120
+
121
+ # ================= Entry / Stoploss =================
122
+ def calculate_entry_stoploss(data, trend, stoploss_percent=5):
123
+ entry_price = None
124
+ stop_loss = None
125
+ if (
126
+ trend.startswith("UPTREND")
127
+ and data["RSI"].iloc[-1] < 70
128
+ and data["MACD"].iloc[-1] > data["MACD_signal"].iloc[-1]
129
+ ):
130
+ entry_price = data["Close"].iloc[-1]
131
+ stop_loss = entry_price * (1 - stoploss_percent / 100)
132
+ return entry_price, stop_loss
133
+
134
+
135
+ # ================= Autocomplete API =================
136
+ @app.route("/autocomplete")
137
+ def autocomplete():
138
+ query = request.args.get("q", "").lower()
139
+ if not query:
140
+ return jsonify([])
141
+
142
+ # Filter dataframe where stock name contains query (case-insensitive)
143
+ filtered = df_stocks[df_stocks["name"].str.lower().str.contains(query)].head(10)
144
+
145
+ # Convert to list of dicts
146
+ suggestions = (
147
+ filtered[["name", "ticker"]]
148
+ .rename(columns={"ticker": "symbol"})
149
+ .to_dict(orient="records")
150
+ )
151
+
152
+ return jsonify(suggestions)
153
+
154
+
155
+ # ================= Flask Routes =================
156
+ @app.route("/", methods=["GET", "POST"])
157
+ def index():
158
+ result = None
159
+ table_html = None
160
+
161
+ if request.method == "POST":
162
+ selected_symbol = request.form["symbol"]
163
+ selected_name = request.form.get("stock_name", selected_symbol)
164
+ yahoo_symbol = (
165
+ f"{selected_symbol}.NS"
166
+ if not selected_symbol.endswith(".NS")
167
+ else selected_symbol
168
+ )
169
+ print("Selected symbol for Yahoo Finance:", yahoo_symbol)
170
+ # Print selected symbol for debugging
171
+ print("Selected stock symbol:", selected_symbol)
172
+ data = fetch_stock_data(yahoo_symbol)
173
+
174
+ # Technical Indicators
175
+ data = calculate_moving_averages(data)
176
+ data = calculate_RSI(data)
177
+ data = calculate_MACD(data)
178
+ trend = determin_trend(data)
179
+ predicted_price, _ = random_forest_forecast(data)
180
+ entry_price, stop_loss = calculate_entry_stoploss(data, trend)
181
+ current_price = data["Close"].iloc[-1]
182
+ price_difference = predicted_price - current_price
183
+ profit_or_loss = ((predicted_price - current_price) / current_price) * 100
184
+
185
+ table_html = (
186
+ data.tail(30)
187
+ .reset_index()
188
+ .to_html(classes="table table-striped table-bordered", index=False)
189
+ )
190
+
191
+ result = {
192
+ "symbol": selected_symbol,
193
+ "name": selected_name,
194
+ "trend": trend,
195
+ "current_price": f"{current_price:.2f}",
196
+ "predicted_price": f"{predicted_price:.2f}",
197
+ "price_difference": f"{price_difference:.2f}",
198
+ "profit_or_loss": f"{profit_or_loss:.2f}%",
199
+ "entry_price": f"{entry_price:.2f}" if entry_price else "No Entry Signal",
200
+ "stop_loss": f"{stop_loss:.2f}" if stop_loss else "-",
201
+ "date_now": data.index[-1].date(),
202
+ "future_date": datetime.now().date() + timedelta(days=30),
203
+ }
204
+
205
+ return render_template_string(
206
+ """
207
+ <html>
208
+ <head>
209
+ <title>Stock Predictor</title>
210
+ <link rel="stylesheet"
211
+ href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
212
+ <style>
213
+ body { padding: 30px; }
214
+ th, td { text-align: center; }
215
+ .result-card { margin-top: 40px; }
216
+
217
+ #loader-overlay {
218
+ display: none;
219
+ position: fixed;
220
+ top: 0; left: 0;
221
+ width: 100%; height: 100%;
222
+ background: rgba(255, 255, 255, 0.8);
223
+ z-index: 9999;
224
+ justify-content: center;
225
+ align-items: center;
226
+ flex-direction: column;
227
+ }
228
+ </style>
229
+ </head>
230
+ <body>
231
+ <div id="loader-overlay">
232
+ <div class="spinner-border text-primary" role="status" style="width: 4rem; height: 4rem;"></div>
233
+ <p class="mt-3 fw-bold text-primary">Predicting...</p>
234
+ </div>
235
+
236
+ <div class="container">
237
+ <h3 class="text-center mb-4">Stock Prediction Dashboard</h3>
238
+ <form method="POST" class="text-center mb-4" onsubmit="showLoader()">
239
+ <div class="row justify-content-center">
240
+ <div class="col-md-4">
241
+ <input type="hidden" name="stock_name" id="stock-name">
242
+ <input type="text" name="symbol" id="stock-input" class="form-control" placeholder="Enter stock name" autocomplete="off" value="{{ result.symbol if result }}">
243
+ <div id="suggestions" class="list-group"></div>
244
+ </div>
245
+ <div class="col-md-2">
246
+ <button type="submit" class="btn btn-primary w-100">Predict</button>
247
+ </div>
248
+ </div>
249
+ </form>
250
+
251
+ {% if result %}
252
+ <div class="card result-card shadow">
253
+ <div class="card-body">
254
+ <h5 class="card-title text-center">Report for {{ result.name }}</h5>
255
+ <p><b>Trend:</b> {{ result.trend }}</p>
256
+ <p><b>Current Price:</b> ₹{{ result.current_price }} ({{ result.date_now }})</p>
257
+ <p><b>Predicted Price (Next 30 Days):</b> ₹{{ result.predicted_price }} ({{ result.future_date }})</p>
258
+ <p><b>Price Difference:</b> ₹{{ result.price_difference }}</p>
259
+ <p><b>Expected Return:</b> {{ result.profit_or_loss }}</p>
260
+ <p><b>Entry Price:</b> {{ result.entry_price }}</p>
261
+ <p><b>Stop Loss:</b> {{ result.stop_loss }}</p>
262
+ </div>
263
+ </div>
264
+
265
+ <div class="mt-4">
266
+ <h5>Last 30 Days Data</h5>
267
+ {{ table_html | safe }}
268
+ </div>
269
+ {% endif %}
270
+ </div>
271
+
272
+ <script>
273
+ function showLoader() {
274
+ document.getElementById("loader-overlay").style.display = "flex";
275
+ }
276
+
277
+ // Autocomplete
278
+ const input = document.getElementById("stock-input");
279
+ const stockNameInput = document.getElementById("stock-name");
280
+ const suggestionsBox = document.getElementById("suggestions");
281
+
282
+ input.addEventListener("input", async function() {
283
+ const query = this.value;
284
+ if (query.length < 1) {
285
+ suggestionsBox.innerHTML = "";
286
+ return;
287
+ }
288
+ const response = await fetch("/autocomplete?q=" + query);
289
+ const data = await response.json();
290
+ suggestionsBox.innerHTML = "";
291
+ data.forEach(item => {
292
+ const div = document.createElement("div");
293
+ div.classList.add("list-group-item", "list-group-item-action");
294
+ div.textContent = `${item.name} (${item.symbol})`;
295
+ div.onclick = () => {
296
+ // Symbol send to backend
297
+ input.value = item.symbol;
298
+ // Name displayed in report
299
+ stockNameInput.value = item.name;
300
+ suggestionsBox.innerHTML = "";
301
+ };
302
+ suggestionsBox.appendChild(div);
303
+ });
304
+ });
305
+ </script>
306
+ </body>
307
+ </html>
308
+ """,
309
+ result=result,
310
+ table_html=table_html,
311
+ )
312
+
313
+
314
+ # Run the app
315
+ if __name__ == "__main__":
316
+ # Run on local host
317
+ # app.run(debug=True)
318
+
319
+ # Run using public IP
320
+ # app.run(host="0.0.0.0", port=5000, debug=True)
321
+
322
+ # Hugging Face uses port 7860 by default
323
+ app.run(host="0.0.0.0", port=7860)
requirements.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Web Framework: The lightweight framework for building the application structure (routes, views, etc.)
2
+ flask
3
+
4
+ # Production Server: A critical component for deployment. Used by Docker to serve the Flask app live on Hugging Face Spaces.
5
+ gunicorn
6
+
7
+ # Data Acquisition: Used to download historical stock data from Yahoo Finance.
8
+ yfinance
9
+
10
+ # Machine Learning: The library containing the Random Forest algorithm for making predictions.
11
+ scikit-learn
12
+
13
+ # Data Processing: Used for handling data structures like DataFrames, essential for cleaning, manipulating, and preparing stock data.
14
+ pandas
15
+
16
+ # Scientific Computing: Provides high-performance array and mathematical operations, often used internally by pandas and scikit-learn.
17
+ numpy
18
+
19
+ # Hugging Face Datasets is a library for accessing and sharing AI datasets
20
+ datasets