Jitendra12421 commited on
Commit
ee9a2a0
·
verified ·
1 Parent(s): 3b7452c

Upload 52 files

Browse files
app.py CHANGED
@@ -38,6 +38,7 @@ from kotak_neo import (
38
  KotakNeoSessionRequired,
39
  kotak_neo_manager,
40
  )
 
41
 
42
 
43
  app = FastAPI(title="NIFTY 50 Forecaster Backend")
@@ -667,3 +668,12 @@ def data_refresh_market_close(
667
  session_date: date | None = Query(default=None, description="Optional YYYY-MM-DD session date in IST."),
668
  ) -> dict:
669
  return refresh_market_close_data(session_date=session_date)
 
 
 
 
 
 
 
 
 
 
38
  KotakNeoSessionRequired,
39
  kotak_neo_manager,
40
  )
41
+ from scraper import get_stock_info
42
 
43
 
44
  app = FastAPI(title="NIFTY 50 Forecaster Backend")
 
668
  session_date: date | None = Query(default=None, description="Optional YYYY-MM-DD session date in IST."),
669
  ) -> dict:
670
  return refresh_market_close_data(session_date=session_date)
671
+
672
+
673
+ @app.get("/api/info/{ticker}")
674
+ def stock_info(ticker: str) -> dict:
675
+ data = get_stock_info(ticker)
676
+ if "error" in data:
677
+ raise HTTPException(status_code=404, detail=data["error"])
678
+ return data
679
+
models/nifty_opening_mfe_regressor/outputs/latest_prediction.csv CHANGED
@@ -1,2 +1,2 @@
1
  input_date,first5_start,first5_end,first5_close,predicted_up_points,predicted_down_points
2
- 2026-06-09,2026-06-09 09:15:00,2026-06-09 09:19:00,23234.849609375,63.15162391627527,71.5788486914341
 
1
  input_date,first5_start,first5_end,first5_close,predicted_up_points,predicted_down_points
2
+ 2026-06-11,2026-06-11 09:15:00,2026-06-11 09:19:00,23112.650390625,97.5434972440442,76.48054546871592
models/nifty_opening_mfe_regressor/outputs/mfe_live_history.csv ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ input_date,first5_start,first5_end,first5_close,predicted_up_points,predicted_down_points
2
+ 2026-06-11,2026-06-11 09:15:00,2026-06-11 09:19:00,23112.650390625,97.5434972440442,76.48054546871592
models/tomorrow_latest_prediction.csv CHANGED
@@ -1,2 +1,2 @@
1
  input_date,target_date,prediction,prob_up,confidence,threshold,model_name,source_model,validation_accuracy,test_accuracy,source
2
- 2026-06-11,2026-06-12,DOWN,0.46778826961945674,0.5322117303805433,0.534,nifty_tomorrow_direction_model,locked_multiwindow_nifty50_ensemble_v2,0.5886524822695035,0.7070707070707071,live
 
1
  input_date,target_date,prediction,prob_up,confidence,threshold,model_name,source_model,validation_accuracy,test_accuracy,source
2
+ 2026-06-12,2026-06-15,UP,0.5568798000516237,0.5568798000516237,0.534,nifty_tomorrow_direction_model,locked_multiwindow_nifty50_ensemble_v2,0.5886524822695035,0.7070707070707071,live
models/tomorrow_prediction_history.parquet CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:89f100aced6b7ff9d8d0c5cd0ce10a00e5a402bbf935949fac103b8a33d0dbca
3
- size 8273
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ba49d81e54af07af6cd1b7defee306558ab4377dc09888b1d8452446aae13f4a
3
+ size 8300
models/yahoo_history_cache.sqlite3 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:75c3f55a2adee670c10dbadd1c998d3ce773ed067c8e196ed2c7539ca6f8eb87
3
- size 397312
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8198956ffb6a3aca16eabd5f6fc4aa188ba816d324a7f9987caf631d1b0eea14
3
+ size 421888
nifty_backend/__pycache__/runtime.cpython-311.pyc CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:eac2a9d465d264f6ebb982a4f2b7fbd61ef4db81c5a3549543b07cb103b8d680
3
- size 138158
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:83ba07597a5f92396b6297f2fe401f732a2a617e82c6fd3311f2d3e5537475e5
3
+ size 143804
nifty_backend/runtime.py CHANGED
@@ -60,6 +60,8 @@ MFE_OUTPUT_DIR = MODEL_DIR / "nifty_opening_mfe_regressor" / "outputs"
60
  MFE_SUMMARY_PATH = MFE_OUTPUT_DIR / "summary.json"
61
  MFE_LATEST_PATH = MFE_OUTPUT_DIR / "latest_prediction.csv"
62
  MFE_TEST_PREDICTIONS_PATH = MFE_OUTPUT_DIR / "test_predictions.csv"
 
 
63
  TPLUS1_MODEL_PATH = MODEL_DIR / "nifty_1420_tplus1_logistic_model.joblib"
64
  TPLUS1_LATEST_PATH = MODEL_DIR / "tplus1_latest_prediction.csv"
65
  TPLUS1_SUMMARY_PATH = MODEL_DIR / "tplus1_summary.json"
@@ -945,6 +947,80 @@ def latest_tplus1_prediction() -> dict[str, Any]:
945
  }
946
 
947
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
948
  def _minute_frame_for_tplus1() -> pd.DataFrame:
949
  minute = pd.read_parquet(NIFTY_1M_PATH)
950
  minute = minute.copy()
@@ -1267,6 +1343,9 @@ def dashboard_payload() -> dict[str, Any]:
1267
  _file_cache_key(NIFTY_1M_PATH),
1268
  _file_cache_key(LIVE_ACCURACY_PATH),
1269
  _file_cache_key(TOMORROW_PREDICTION_HISTORY_PATH),
 
 
 
1270
  )
1271
  with _dashboard_payload_lock:
1272
  return copy.deepcopy(_dashboard_payload_cached(key))
@@ -1442,6 +1521,8 @@ def _dashboard_payload_cached(key: tuple[tuple[str, int | None, int | None], ...
1442
  tomorrow_test = load_tomorrow_test_predictions()
1443
  tomorrow_history = _load_prediction_history(TOMORROW_PREDICTION_HISTORY_PATH)
1444
  tplus1_test = load_tplus1_test_predictions()
 
 
1445
  daily = pd.read_parquet(NIFTY_1D_PATH)
1446
  daily["date"] = pd.to_datetime(daily["date"], errors="coerce")
1447
  daily = daily.sort_values("date").tail(180)
@@ -1521,6 +1602,10 @@ def _dashboard_payload_cached(key: tuple[tuple[str, int | None, int | None], ...
1521
  "latest": tplus1_latest,
1522
  "summary": tplus1_summary,
1523
  },
 
 
 
 
1524
  },
1525
  "metrics": metrics,
1526
  "models": model_metrics,
@@ -1551,6 +1636,10 @@ def refresh_first5_prediction(session_date: date | None = None, minutes: pd.Data
1551
  merged = merged.drop_duplicates(subset=["date"], keep="last").sort_values("date").reset_index(drop=True)
1552
  merged.to_parquet(OPENING_DATASET_PATH, index=False, compression="zstd")
1553
  prediction = predict_row(row)
 
 
 
 
1554
  return prediction
1555
 
1556
 
 
60
  MFE_SUMMARY_PATH = MFE_OUTPUT_DIR / "summary.json"
61
  MFE_LATEST_PATH = MFE_OUTPUT_DIR / "latest_prediction.csv"
62
  MFE_TEST_PREDICTIONS_PATH = MFE_OUTPUT_DIR / "test_predictions.csv"
63
+ MFE_MODEL_PATH = MFE_OUTPUT_DIR / "nifty_opening_mfe_regressor.joblib"
64
+ MFE_LIVE_HISTORY_PATH = MFE_OUTPUT_DIR / "mfe_live_history.csv"
65
  TPLUS1_MODEL_PATH = MODEL_DIR / "nifty_1420_tplus1_logistic_model.joblib"
66
  TPLUS1_LATEST_PATH = MODEL_DIR / "tplus1_latest_prediction.csv"
67
  TPLUS1_SUMMARY_PATH = MODEL_DIR / "tplus1_summary.json"
 
947
  }
948
 
949
 
950
+ def load_mfe_summary() -> dict[str, Any]:
951
+ if MFE_SUMMARY_PATH.exists():
952
+ return json.loads(MFE_SUMMARY_PATH.read_text(encoding="utf-8"))
953
+ return {}
954
+
955
+
956
+ def latest_mfe_prediction() -> dict[str, Any]:
957
+ if MFE_LATEST_PATH.exists():
958
+ row = pd.read_csv(MFE_LATEST_PATH).iloc[-1].to_dict()
959
+ return {k: (None if pd.isna(v) else v) for k, v in row.items()}
960
+ summary = load_mfe_summary()
961
+ return {
962
+ "input_date": summary.get("latest_input_date"),
963
+ "first5_start": summary.get("latest_first5_start"),
964
+ "first5_end": summary.get("latest_first5_end"),
965
+ "predicted_up_points": summary.get("latest_predicted_up_points"),
966
+ "predicted_down_points": summary.get("latest_predicted_down_points"),
967
+ }
968
+
969
+
970
+ def refresh_mfe_prediction(session_date: date | None = None) -> dict[str, Any]:
971
+ if not MFE_MODEL_PATH.exists():
972
+ return {}
973
+ payload = joblib.load(MFE_MODEL_PATH)
974
+ up_model = payload["up_model"]
975
+ down_model = payload["down_model"]
976
+ up_features = payload["up_features"]
977
+ down_features = payload["down_features"]
978
+ up_calib = payload["up_calibration"]
979
+ down_calib = payload["down_calibration"]
980
+
981
+ dataset = pd.read_parquet(OPENING_DATASET_PATH)
982
+ dataset["_session_date"] = pd.to_datetime(dataset["date"], errors="coerce").dt.normalize()
983
+ if session_date is not None:
984
+ latest_df = dataset[dataset["_session_date"].dt.date == session_date].tail(1)
985
+ else:
986
+ latest_df = dataset.tail(1)
987
+
988
+ if latest_df.empty:
989
+ return {}
990
+
991
+ raw_up = float(np.clip(up_model.predict(latest_df[up_features])[0], 0.0, None))
992
+ pred_up = float(np.clip((raw_up * up_calib["scale"]) + up_calib["offset"], 0.0, None))
993
+
994
+ raw_down = float(np.clip(down_model.predict(latest_df[down_features])[0], 0.0, None))
995
+ pred_down = float(np.clip((raw_down * down_calib["scale"]) + down_calib["offset"], 0.0, None))
996
+
997
+ out = {
998
+ "input_date": latest_df["_session_date"].dt.date.iloc[0].isoformat(),
999
+ "first5_start": str(latest_df["first5_start"].iloc[0]),
1000
+ "first5_end": str(latest_df["first5_end"].iloc[0]),
1001
+ "first5_close": float(latest_df["first5_close"].iloc[0]),
1002
+ "predicted_up_points": pred_up,
1003
+ "predicted_down_points": pred_down,
1004
+ }
1005
+ pd.DataFrame([out]).to_csv(MFE_LATEST_PATH, index=False)
1006
+
1007
+ # Append to live history
1008
+ live_df = pd.DataFrame([out])
1009
+ if MFE_LIVE_HISTORY_PATH.exists():
1010
+ try:
1011
+ existing = pd.read_csv(MFE_LIVE_HISTORY_PATH)
1012
+ # Avoid duplicates if refreshed multiple times in the same session
1013
+ existing = existing[existing["input_date"] != out["input_date"]]
1014
+ pd.concat([existing, live_df], ignore_index=True).to_csv(MFE_LIVE_HISTORY_PATH, index=False)
1015
+ except Exception:
1016
+ live_df.to_csv(MFE_LIVE_HISTORY_PATH, index=False)
1017
+ else:
1018
+ live_df.to_csv(MFE_LIVE_HISTORY_PATH, index=False)
1019
+
1020
+ clear_dashboard_payload_cache()
1021
+ return out
1022
+
1023
+
1024
  def _minute_frame_for_tplus1() -> pd.DataFrame:
1025
  minute = pd.read_parquet(NIFTY_1M_PATH)
1026
  minute = minute.copy()
 
1343
  _file_cache_key(NIFTY_1M_PATH),
1344
  _file_cache_key(LIVE_ACCURACY_PATH),
1345
  _file_cache_key(TOMORROW_PREDICTION_HISTORY_PATH),
1346
+ _file_cache_key(MFE_SUMMARY_PATH),
1347
+ _file_cache_key(MFE_LATEST_PATH),
1348
+ _file_cache_key(MFE_MODEL_PATH),
1349
  )
1350
  with _dashboard_payload_lock:
1351
  return copy.deepcopy(_dashboard_payload_cached(key))
 
1521
  tomorrow_test = load_tomorrow_test_predictions()
1522
  tomorrow_history = _load_prediction_history(TOMORROW_PREDICTION_HISTORY_PATH)
1523
  tplus1_test = load_tplus1_test_predictions()
1524
+ mfe_summary = load_mfe_summary()
1525
+ mfe_latest = latest_mfe_prediction()
1526
  daily = pd.read_parquet(NIFTY_1D_PATH)
1527
  daily["date"] = pd.to_datetime(daily["date"], errors="coerce")
1528
  daily = daily.sort_values("date").tail(180)
 
1602
  "latest": tplus1_latest,
1603
  "summary": tplus1_summary,
1604
  },
1605
+ "mfe": {
1606
+ "latest": mfe_latest,
1607
+ "summary": mfe_summary,
1608
+ },
1609
  },
1610
  "metrics": metrics,
1611
  "models": model_metrics,
 
1636
  merged = merged.drop_duplicates(subset=["date"], keep="last").sort_values("date").reset_index(drop=True)
1637
  merged.to_parquet(OPENING_DATASET_PATH, index=False, compression="zstd")
1638
  prediction = predict_row(row)
1639
+ try:
1640
+ refresh_mfe_prediction(session_date=session_date)
1641
+ except Exception as exc:
1642
+ print(f"MFE refresh failed: {exc}", flush=True)
1643
  return prediction
1644
 
1645
 
scraper.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from bs4 import BeautifulSoup
3
+
4
+ def get_stock_info(ticker):
5
+ url = f"https://www.screener.in/company/{ticker.upper()}/consolidated/"
6
+ headers = {
7
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
8
+ }
9
+
10
+ response = requests.get(url, headers=headers)
11
+ if response.status_code == 404:
12
+ url = f"https://www.screener.in/company/{ticker.upper()}/"
13
+ response = requests.get(url, headers=headers)
14
+
15
+ if response.status_code != 200:
16
+ return {"error": f"Failed to fetch data for {ticker.upper()}. Status {response.status_code}"}
17
+
18
+ soup = BeautifulSoup(response.text, 'html.parser')
19
+
20
+ data = {
21
+ "ticker": ticker.upper(),
22
+ "key_metrics": {},
23
+ "pros": [],
24
+ "cons": [],
25
+ "growth": [],
26
+ "history": {}
27
+ }
28
+
29
+ # 1. Key Metrics
30
+ top_ratios = soup.find('ul', id='top-ratios')
31
+ if top_ratios:
32
+ for li in top_ratios.find_all('li'):
33
+ n_span = li.find('span', class_='name')
34
+ v_span = li.find('span', class_='value')
35
+ if n_span and v_span:
36
+ name = n_span.text.strip().replace('₹', 'Rs.')
37
+ val = ' '.join(v_span.text.split()).replace('₹', 'Rs.')
38
+ data["key_metrics"][name] = val
39
+
40
+ # 2. Pros/Cons
41
+ analysis = soup.find('section', id='analysis')
42
+ if analysis:
43
+ pros = analysis.find('div', class_='pros')
44
+ if pros:
45
+ data["pros"] = [li.text.strip() for li in pros.find_all('li')]
46
+ cons = analysis.find('div', class_='cons')
47
+ if cons:
48
+ data["cons"] = [li.text.strip() for li in cons.find_all('li')]
49
+
50
+ # 3. Growth Metrics
51
+ ranges = soup.find_all('table', class_='ranges-table')
52
+ for table in ranges:
53
+ th = table.find('th')
54
+ if not th: continue
55
+ metric = th.text.strip()
56
+ for tr in table.find_all('tr')[1:]:
57
+ tds = tr.find_all('td')
58
+ if len(tds) == 2:
59
+ data["growth"].append({
60
+ "Metric": metric,
61
+ "Period": tds[0].text.strip(),
62
+ "Value": tds[1].text.strip()
63
+ })
64
+
65
+ # 4. Tables
66
+ sections = {
67
+ 'quarters': 'Quarterly Results',
68
+ 'profit-loss': 'Profit & Loss',
69
+ 'balance-sheet': 'Balance Sheet',
70
+ 'cash-flow': 'Cash Flows',
71
+ 'ratios': 'Financial Ratios',
72
+ 'shareholding': 'Shareholding Pattern'
73
+ }
74
+ for sec_id, sec_name in sections.items():
75
+ sec = soup.find('section', id=sec_id)
76
+ if not sec: continue
77
+ tbl = sec.find('table')
78
+ if not tbl: continue
79
+ thead = tbl.find('thead')
80
+ headers = [th.text.strip().replace('₹', 'Rs.') for th in thead.find_all('th')] if thead else []
81
+ rows = []
82
+ for tr in tbl.find('tbody').find_all('tr'):
83
+ cols = [td.text.strip().replace('₹', 'Rs.') for td in tr.find_all('td')]
84
+ rname = tr.find('td', class_='text')
85
+ if rname:
86
+ cols[0] = rname.text.replace('+', '').strip()
87
+ if cols:
88
+ rows.append(cols)
89
+ if headers and len(headers) == len(rows[0]) - 1:
90
+ headers.insert(0, 'Metric')
91
+ data["history"][sec_id] = {
92
+ "title": sec_name,
93
+ "headers": headers,
94
+ "rows": rows
95
+ }
96
+
97
+ return data