Jitendra12421 commited on
Commit
69f5fb0
·
verified ·
1 Parent(s): 4ef96a6

Upload 6 files

Browse files
app.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from datetime import datetime, time, date
4
+ from fastapi import FastAPI, BackgroundTasks, HTTPException
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ from zoneinfo import ZoneInfo
7
+ from data_updater import update_daily_data, is_trading_day
8
+ from forecaster_engine import generate_predictions
9
+
10
+ IST = ZoneInfo("Asia/Kolkata")
11
+ MARKET_CLOSE_BUFFER = time(15, 45) # Update runs after 3:45 PM
12
+ PREDICTIONS_FILE = os.path.join(os.path.dirname(__file__), "predictions.json")
13
+
14
+ app = FastAPI(title="HF NIFTY Forecaster Backend")
15
+
16
+ app.add_middleware(
17
+ CORSMiddleware,
18
+ allow_origins=["*"],
19
+ allow_methods=["*"],
20
+ allow_headers=["*"],
21
+ )
22
+
23
+ def run_update_pipeline():
24
+ try:
25
+ # Step 1: Update data
26
+ res = update_daily_data()
27
+ if res.get("status") == "error":
28
+ print(f"Update failed: {res.get('reason')}")
29
+ return
30
+
31
+ # Step 2: Generate predictions
32
+ generate_predictions()
33
+ except Exception as e:
34
+ print(f"Pipeline error: {e}")
35
+
36
+ @app.get("/predictions")
37
+ def get_predictions():
38
+ if not os.path.exists(PREDICTIONS_FILE):
39
+ raise HTTPException(status_code=404, detail="Predictions not yet generated")
40
+
41
+ with open(PREDICTIONS_FILE, "r") as f:
42
+ data = json.load(f)
43
+
44
+ return data
45
+
46
+ @app.post("/cron/update")
47
+ def cron_trigger(background_tasks: BackgroundTasks):
48
+ now = datetime.now(IST)
49
+ today = now.date()
50
+ current_time = now.time()
51
+
52
+ # 1. Check if it's a trading day
53
+ if not is_trading_day(today):
54
+ return {"status": "skipped", "reason": f"{today} is a holiday or weekend"}
55
+
56
+ # 2. Check if it's past 3:45 PM
57
+ if current_time < MARKET_CLOSE_BUFFER:
58
+ return {"status": "skipped", "reason": "Market is still open or buffer not reached. Runs after 3:45 PM IST."}
59
+
60
+ # Trigger the full pipeline in the background so Netlify doesn't timeout
61
+ background_tasks.add_task(run_update_pipeline)
62
+
63
+ return {"status": "triggered", "message": "Update and forecast pipeline started in the background."}
64
+
65
+ @app.get("/health")
66
+ def health_check():
67
+ return {"status": "alive", "server_time_ist": datetime.now(IST).isoformat()}
data/nifty50_daily.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e1ec56e84aa6a133397cbc6000ac563883df0f9287d2cafb434e9c7efcf7778a
3
+ size 1377561
data_updater.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import requests
4
+ import pandas as pd
5
+ from datetime import datetime, date, timedelta
6
+ from zoneinfo import ZoneInfo
7
+ import pandas_market_calendars as mcal
8
+
9
+ IST = ZoneInfo("Asia/Kolkata")
10
+ DATA_FILE = os.path.join(os.path.dirname(__file__), "data", "nifty50_daily.parquet")
11
+
12
+ TICKERS = [
13
+ 'ADANIENT', 'ADANIPORTS', 'APOLLOHOSP', 'ASIANPAINT', 'AXISBANK', 'BAJAJ-AUTO', 'BAJAJFINSV', 'BAJFINANCE',
14
+ 'BHARTIARTL', 'BPCL', 'BRITANNIA', 'CIPLA', 'COALINDIA', 'DIVISLAB', 'DRREDDY', 'EICHERMOT', 'GRASIM',
15
+ 'HCLTECH', 'HDFCBANK', 'HDFCLIFE', 'HEROMOTOCO', 'HINDALCO', 'HINDUNILVR', 'ICICIBANK', 'INDUSINDBK',
16
+ 'INFY', 'ITC', 'JSWSTEEL', 'KOTAKBANK', 'LT', 'M&M', 'MARUTI', 'NESTLEIND', 'NTPC', 'ONGC', 'POWERGRID',
17
+ 'RELIANCE', 'SBILIFE', 'SBIN', 'SUNPHARMA', 'TATACONSUM', 'TATAMOTORS', 'TATASTEEL', 'TCS', 'TECHM',
18
+ 'TITAN', 'ULTRACEMCO', 'UPL', 'WIPRO'
19
+ ]
20
+
21
+ def is_trading_day(target_date: date) -> bool:
22
+ try:
23
+ nse = mcal.get_calendar('NSE')
24
+ schedule = nse.schedule(start_date=target_date, end_date=target_date)
25
+ return not schedule.empty
26
+ except Exception as e:
27
+ print(f"Calendar check failed: {e}")
28
+ # Fallback: assume Monday-Friday is trading day
29
+ return target_date.weekday() < 5
30
+
31
+ def fetch_groww_data(ticker: str, start_ts: int, end_ts: int):
32
+ url = f"https://groww.in/v1/api/charting_service/v2/chart/exchange/NSE/segment/CASH/{ticker}?endTimeInMillis={end_ts}&intervalInMinutes=1&startTimeInMillis={start_ts}"
33
+ headers = {
34
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
35
+ "Accept": "application/json"
36
+ }
37
+
38
+ try:
39
+ response = requests.get(url, headers=headers, timeout=10)
40
+ if response.status_code == 200:
41
+ data = response.json()
42
+ if data and 'candles' in data and len(data['candles']) > 0:
43
+ # Candles format: [timestamp, open, high, low, close, volume]
44
+ last_candle = data['candles'][-1]
45
+ return last_candle[4] # Close price
46
+ return None
47
+ except Exception as e:
48
+ print(f"Error fetching {ticker}: {e}")
49
+ return None
50
+
51
+ def update_daily_data():
52
+ now = datetime.now(IST)
53
+ today = now.date()
54
+
55
+ if not is_trading_day(today):
56
+ print(f"{today} is not a trading day. Skipping update.")
57
+ return {"status": "skipped", "reason": "not a trading day"}
58
+
59
+ if not os.path.exists(DATA_FILE):
60
+ print(f"Data file {DATA_FILE} not found!")
61
+ return {"status": "error", "reason": "data file missing"}
62
+
63
+ # Read existing data
64
+ df = pd.read_parquet(DATA_FILE)
65
+
66
+ # Check if we already updated today
67
+ if not df.empty and pd.to_datetime(today) in df['date'].dt.date.values:
68
+ # We might have partial data or want to overwrite, but for safety:
69
+ # Let's delete today's entries if they exist so we can cleanly append
70
+ df = df[df['date'].dt.date != today]
71
+
72
+ print(f"Fetching data for {today}...")
73
+
74
+ # Market hours: 09:15 to 15:30 IST
75
+ start_dt = datetime.combine(today, datetime.strptime("09:15", "%H:%M").time()).replace(tzinfo=IST)
76
+ end_dt = datetime.combine(today, datetime.strptime("15:30", "%H:%M").time()).replace(tzinfo=IST)
77
+
78
+ start_ts = int(start_dt.timestamp() * 1000)
79
+ end_ts = int(end_dt.timestamp() * 1000)
80
+
81
+ new_rows = []
82
+
83
+ for ticker in TICKERS:
84
+ close_price = fetch_groww_data(ticker, start_ts, end_ts)
85
+ if close_price is not None:
86
+ new_rows.append({
87
+ 'date': pd.to_datetime(today),
88
+ 'close': float(close_price),
89
+ 'ticker': ticker
90
+ })
91
+ time.sleep(0.5) # Rate limiting
92
+
93
+ if new_rows:
94
+ new_df = pd.DataFrame(new_rows)
95
+ updated_df = pd.concat([df, new_df], ignore_index=True)
96
+ updated_df.sort_values(by=['ticker', 'date'], inplace=True)
97
+ updated_df.to_parquet(DATA_FILE)
98
+ print(f"Successfully updated {len(new_rows)} tickers for {today}")
99
+ return {"status": "success", "updated_count": len(new_rows)}
100
+ else:
101
+ print("No new data fetched.")
102
+ return {"status": "error", "reason": "fetch failed for all tickers"}
103
+
104
+ if __name__ == "__main__":
105
+ update_daily_data()
forecaster_engine.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import pandas as pd
4
+ import numpy as np
5
+ from datetime import datetime
6
+
7
+ DATA_FILE = os.path.join(os.path.dirname(__file__), "data", "nifty50_daily.parquet")
8
+ PREDICTIONS_FILE = os.path.join(os.path.dirname(__file__), "predictions.json")
9
+
10
+ def generate_predictions():
11
+ if not os.path.exists(DATA_FILE):
12
+ print(f"Data file missing: {DATA_FILE}")
13
+ return
14
+
15
+ df_all = pd.read_parquet(DATA_FILE)
16
+ tickers = df_all['ticker'].unique()
17
+
18
+ predictions = {}
19
+ forecast_date = None
20
+
21
+ for ticker in tickers:
22
+ df = df_all[df_all['ticker'] == ticker].copy()
23
+ df.sort_values('date', inplace=True)
24
+ df.set_index('date', inplace=True)
25
+
26
+ if len(df) < 130:
27
+ continue
28
+
29
+ forecast_date_ts = df.index[-1] + pd.Timedelta(days=1)
30
+ # Advance past weekends roughly for display
31
+ if forecast_date_ts.weekday() >= 5:
32
+ forecast_date_ts += pd.Timedelta(days=(7 - forecast_date_ts.weekday()))
33
+
34
+ if forecast_date is None:
35
+ forecast_date = forecast_date_ts.strftime('%Y-%m-%d')
36
+
37
+ daily_close = df['close']
38
+
39
+ # Features
40
+ delta = daily_close.diff()
41
+ gain = (delta.where(delta > 0, 0)).rolling(window=2).mean()
42
+ loss = (-delta.where(delta < 0, 0)).rolling(window=2).mean()
43
+ rs = gain / loss
44
+ rsi_2 = 100 - (100 / (1 + rs))
45
+
46
+ sma_3 = daily_close.rolling(window=3).mean()
47
+ dist_sma3 = daily_close / sma_3
48
+
49
+ sma_5 = daily_close.rolling(window=5).mean()
50
+ dist_sma5 = daily_close / sma_5
51
+
52
+ df_eval = pd.DataFrame({
53
+ 'close': daily_close,
54
+ 'rsi_2': rsi_2,
55
+ 'dist_sma3': dist_sma3,
56
+ 'dist_sma5': dist_sma5,
57
+ '1d_ret': daily_close.pct_change()
58
+ }).dropna()
59
+
60
+ # Target for historical testing
61
+ df_eval['actual_dir'] = np.where(df_eval['close'].shift(-1) > df_eval['close'], 1, -1)
62
+
63
+ # The last row is TODAY. We don't have tomorrow's close, so actual_dir is wrong for the last row.
64
+ # We test on the 120 days BEFORE today
65
+ test_set = df_eval.iloc[-121:-1]
66
+ today_data = df_eval.iloc[-1]
67
+
68
+ best_acc = 0
69
+ best_rule = None
70
+
71
+ # 1. RSI-2 threshold
72
+ for thresh in [10, 20, 30, 40, 50, 60, 70, 80, 90]:
73
+ for op in ['<', '>']:
74
+ sig = np.where(test_set['rsi_2'] < thresh if op == '<' else test_set['rsi_2'] > thresh, 1, -1)
75
+ acc = (sig == test_set['actual_dir']).mean()
76
+ if acc > best_acc:
77
+ best_acc = acc
78
+ best_rule = ('rsi_2', thresh, op)
79
+
80
+ # 2. SMA distance threshold
81
+ for feature in ['dist_sma3', 'dist_sma5']:
82
+ for thresh in [0.95, 0.98, 1.0, 1.02, 1.05]:
83
+ sig = np.where(test_set[feature] < thresh, 1, -1)
84
+ acc = (sig == test_set['actual_dir']).mean()
85
+ if acc > best_acc:
86
+ best_acc = acc
87
+ best_rule = (feature, thresh, '<')
88
+
89
+ # 3. 1d return
90
+ sig = np.where(test_set['1d_ret'] < 0, 1, -1)
91
+ acc = (sig == test_set['actual_dir']).mean()
92
+ if acc > best_acc:
93
+ best_acc = acc
94
+ best_rule = ('1d_ret', 0, '<')
95
+
96
+ feature, thresh, op = best_rule
97
+ val = today_data[feature]
98
+
99
+ if op == '<':
100
+ prediction = 1 if val < thresh else -1
101
+ else:
102
+ prediction = 1 if val > thresh else -1
103
+
104
+ predictions[ticker] = {
105
+ "prediction": "UP" if prediction == 1 else "DOWN",
106
+ "probability": round(best_acc * 100, 2),
107
+ "rule_used": f"{feature} {op} {thresh}"
108
+ }
109
+
110
+ output = {
111
+ "generated_at": datetime.now().isoformat(),
112
+ "forecast_date": forecast_date,
113
+ "predictions": predictions
114
+ }
115
+
116
+ with open(PREDICTIONS_FILE, "w") as f:
117
+ json.dump(output, f, indent=4)
118
+
119
+ print(f"Generated predictions for {forecast_date}. Saved to {PREDICTIONS_FILE}")
120
+ return output
121
+
122
+ if __name__ == "__main__":
123
+ generate_predictions()
predictions.json ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "generated_at": "2026-06-18T16:56:32.759921",
3
+ "forecast_date": "2026-06-16",
4
+ "predictions": {
5
+ "ADANIENT": {
6
+ "prediction": "DOWN",
7
+ "probability": 55.83,
8
+ "rule_used": "rsi_2 < 10"
9
+ },
10
+ "ADANIPORTS": {
11
+ "prediction": "UP",
12
+ "probability": 56.67,
13
+ "rule_used": "dist_sma5 < 1.0"
14
+ },
15
+ "APOLLOHOSP": {
16
+ "prediction": "DOWN",
17
+ "probability": 55.83,
18
+ "rule_used": "rsi_2 > 10"
19
+ },
20
+ "ASIANPAINT": {
21
+ "prediction": "DOWN",
22
+ "probability": 55.83,
23
+ "rule_used": "dist_sma5 < 0.95"
24
+ },
25
+ "AXISBANK": {
26
+ "prediction": "UP",
27
+ "probability": 59.17,
28
+ "rule_used": "rsi_2 > 90"
29
+ },
30
+ "BAJAJ-AUTO": {
31
+ "prediction": "UP",
32
+ "probability": 57.5,
33
+ "rule_used": "rsi_2 < 40"
34
+ },
35
+ "BAJAJFINSV": {
36
+ "prediction": "DOWN",
37
+ "probability": 55.0,
38
+ "rule_used": "rsi_2 < 20"
39
+ },
40
+ "BAJFINANCE": {
41
+ "prediction": "DOWN",
42
+ "probability": 56.67,
43
+ "rule_used": "dist_sma5 < 1.0"
44
+ },
45
+ "BHARTIARTL": {
46
+ "prediction": "DOWN",
47
+ "probability": 55.0,
48
+ "rule_used": "rsi_2 < 20"
49
+ },
50
+ "BPCL": {
51
+ "prediction": "DOWN",
52
+ "probability": 64.17,
53
+ "rule_used": "rsi_2 < 10"
54
+ },
55
+ "BRITANNIA": {
56
+ "prediction": "DOWN",
57
+ "probability": 54.17,
58
+ "rule_used": "rsi_2 < 70"
59
+ },
60
+ "CIPLA": {
61
+ "prediction": "DOWN",
62
+ "probability": 56.67,
63
+ "rule_used": "dist_sma3 < 0.95"
64
+ },
65
+ "COALINDIA": {
66
+ "prediction": "DOWN",
67
+ "probability": 57.5,
68
+ "rule_used": "rsi_2 < 30"
69
+ },
70
+ "DIVISLAB": {
71
+ "prediction": "UP",
72
+ "probability": 57.5,
73
+ "rule_used": "dist_sma5 < 1.0"
74
+ },
75
+ "DRREDDY": {
76
+ "prediction": "UP",
77
+ "probability": 65.0,
78
+ "rule_used": "rsi_2 < 60"
79
+ },
80
+ "EICHERMOT": {
81
+ "prediction": "DOWN",
82
+ "probability": 57.5,
83
+ "rule_used": "rsi_2 < 90"
84
+ },
85
+ "GRASIM": {
86
+ "prediction": "DOWN",
87
+ "probability": 63.33,
88
+ "rule_used": "1d_ret < 0"
89
+ },
90
+ "HCLTECH": {
91
+ "prediction": "UP",
92
+ "probability": 54.17,
93
+ "rule_used": "rsi_2 > 20"
94
+ },
95
+ "HDFCBANK": {
96
+ "prediction": "DOWN",
97
+ "probability": 59.17,
98
+ "rule_used": "dist_sma3 < 0.98"
99
+ },
100
+ "HDFCLIFE": {
101
+ "prediction": "DOWN",
102
+ "probability": 63.33,
103
+ "rule_used": "dist_sma3 < 0.98"
104
+ },
105
+ "HEROMOTOCO": {
106
+ "prediction": "DOWN",
107
+ "probability": 55.0,
108
+ "rule_used": "rsi_2 < 30"
109
+ },
110
+ "HINDALCO": {
111
+ "prediction": "UP",
112
+ "probability": 63.33,
113
+ "rule_used": "dist_sma5 < 1.05"
114
+ },
115
+ "HINDUNILVR": {
116
+ "prediction": "UP",
117
+ "probability": 53.33,
118
+ "rule_used": "rsi_2 > 20"
119
+ },
120
+ "ICICIBANK": {
121
+ "prediction": "DOWN",
122
+ "probability": 55.83,
123
+ "rule_used": "dist_sma5 < 0.98"
124
+ },
125
+ "INDUSINDBK": {
126
+ "prediction": "DOWN",
127
+ "probability": 54.17,
128
+ "rule_used": "rsi_2 < 30"
129
+ },
130
+ "INFY": {
131
+ "prediction": "DOWN",
132
+ "probability": 55.0,
133
+ "rule_used": "dist_sma3 < 0.95"
134
+ },
135
+ "ITC": {
136
+ "prediction": "DOWN",
137
+ "probability": 56.67,
138
+ "rule_used": "dist_sma3 < 0.95"
139
+ },
140
+ "JSWSTEEL": {
141
+ "prediction": "DOWN",
142
+ "probability": 61.67,
143
+ "rule_used": "rsi_2 < 40"
144
+ },
145
+ "KOTAKBANK": {
146
+ "prediction": "DOWN",
147
+ "probability": 53.33,
148
+ "rule_used": "rsi_2 < 10"
149
+ },
150
+ "LTIM": {
151
+ "prediction": "UP",
152
+ "probability": 60.0,
153
+ "rule_used": "1d_ret < 0"
154
+ },
155
+ "LT": {
156
+ "prediction": "DOWN",
157
+ "probability": 58.33,
158
+ "rule_used": "1d_ret < 0"
159
+ },
160
+ "MARUTI": {
161
+ "prediction": "DOWN",
162
+ "probability": 61.67,
163
+ "rule_used": "rsi_2 < 40"
164
+ },
165
+ "MM": {
166
+ "prediction": "DOWN",
167
+ "probability": 55.0,
168
+ "rule_used": "rsi_2 < 30"
169
+ },
170
+ "NESTLEIND": {
171
+ "prediction": "UP",
172
+ "probability": 57.5,
173
+ "rule_used": "rsi_2 < 70"
174
+ },
175
+ "NTPC": {
176
+ "prediction": "UP",
177
+ "probability": 57.5,
178
+ "rule_used": "rsi_2 < 80"
179
+ },
180
+ "ONGC": {
181
+ "prediction": "UP",
182
+ "probability": 58.33,
183
+ "rule_used": "rsi_2 < 40"
184
+ },
185
+ "POWERGRID": {
186
+ "prediction": "DOWN",
187
+ "probability": 55.0,
188
+ "rule_used": "rsi_2 > 70"
189
+ },
190
+ "RELIANCE": {
191
+ "prediction": "DOWN",
192
+ "probability": 55.0,
193
+ "rule_used": "dist_sma5 < 0.98"
194
+ },
195
+ "SBILIFE": {
196
+ "prediction": "DOWN",
197
+ "probability": 60.83,
198
+ "rule_used": "1d_ret < 0"
199
+ },
200
+ "SBIN": {
201
+ "prediction": "UP",
202
+ "probability": 55.83,
203
+ "rule_used": "rsi_2 > 10"
204
+ },
205
+ "SUNPHARMA": {
206
+ "prediction": "UP",
207
+ "probability": 54.17,
208
+ "rule_used": "rsi_2 > 60"
209
+ },
210
+ "TATACONSUM": {
211
+ "prediction": "UP",
212
+ "probability": 57.5,
213
+ "rule_used": "dist_sma3 < 1.0"
214
+ },
215
+ "TATASTEEL": {
216
+ "prediction": "UP",
217
+ "probability": 55.83,
218
+ "rule_used": "rsi_2 < 10"
219
+ },
220
+ "TCS": {
221
+ "prediction": "UP",
222
+ "probability": 57.5,
223
+ "rule_used": "rsi_2 > 20"
224
+ },
225
+ "TECHM": {
226
+ "prediction": "UP",
227
+ "probability": 57.5,
228
+ "rule_used": "dist_sma3 < 1.02"
229
+ },
230
+ "TITAN": {
231
+ "prediction": "DOWN",
232
+ "probability": 55.0,
233
+ "rule_used": "rsi_2 < 40"
234
+ },
235
+ "ULTRACEMCO": {
236
+ "prediction": "DOWN",
237
+ "probability": 57.5,
238
+ "rule_used": "rsi_2 < 50"
239
+ },
240
+ "UPL": {
241
+ "prediction": "DOWN",
242
+ "probability": 60.0,
243
+ "rule_used": "dist_sma5 < 1.0"
244
+ },
245
+ "WIPRO": {
246
+ "prediction": "UP",
247
+ "probability": 60.83,
248
+ "rule_used": "rsi_2 > 10"
249
+ }
250
+ }
251
+ }
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi==0.103.1
2
+ uvicorn==0.23.2
3
+ pandas==2.1.0
4
+ requests==2.31.0
5
+ pandas_market_calendars==4.3.0
6
+ pyarrow==13.0.0
7
+ fastparquet==2023.8.0