Jitendra12421 commited on
Commit
1e8bf4c
·
verified ·
1 Parent(s): cf8e200

Upload 10 files

Browse files
app.py CHANGED
@@ -13,7 +13,16 @@ from fastapi.middleware.cors import CORSMiddleware
13
  from fastapi import FastAPI
14
 
15
  sys.path.insert(0, str(Path(__file__).resolve().parent))
16
- from nifty_backend.runtime import dashboard_payload, latest_saved_prediction, refresh_daily_data, refresh_first5_prediction, seconds_until_next_ist_run, IST
 
 
 
 
 
 
 
 
 
17
 
18
 
19
  app = FastAPI(title="NIFTY 50 Forecaster Backend")
@@ -36,22 +45,6 @@ def configured_pin() -> str:
36
  return os.getenv("DASHBOARD_PIN", "1979")
37
 
38
 
39
- def is_trading_day(day: date) -> bool:
40
- holidays = {
41
- d.strip()
42
- for d in os.getenv("NSE_HOLIDAYS", "").split(",")
43
- if d.strip()
44
- }
45
- return day.weekday() < 5 and day.isoformat() not in holidays
46
-
47
-
48
- def next_trading_day(start: date) -> date:
49
- day = start
50
- while not is_trading_day(day):
51
- day += timedelta(days=1)
52
- return day
53
-
54
-
55
  def latest_prediction_date(payload: dict | None = None) -> date | None:
56
  try:
57
  latest = payload if payload is not None else latest_saved_prediction()
@@ -68,12 +61,12 @@ def current_market_state(now: datetime | None = None) -> dict:
68
  current_time = now.time()
69
  trading_day = is_trading_day(today)
70
  latest_date = latest_prediction_date()
71
- has_live_first5 = trading_day and latest_date == today and FIRST5_READY <= current_time <= MARKET_CLOSE
 
72
 
73
  if not trading_day:
74
- next_day = next_trading_day(today + timedelta(days=1))
75
  status = "Market Closed"
76
- detail = f"Next trading session is {next_day.isoformat()}."
77
  elif current_time < time(9, 0):
78
  status = "Waiting for 9:00 AM"
79
  detail = "Market has not entered pre-open yet."
@@ -87,7 +80,7 @@ def current_market_state(now: datetime | None = None) -> dict:
87
  if market_status in {"Fetching T+5 Prediction Data...", "Prediction Failed"}:
88
  status = market_status
89
  detail = "The first-five-minute prediction job is still resolving."
90
- elif has_live_first5:
91
  status = "Prediction Ready"
92
  detail = "Today's first-five-minute prediction is available."
93
  else:
@@ -102,8 +95,9 @@ def current_market_state(now: datetime | None = None) -> dict:
102
  "market_detail": detail,
103
  "is_trading_day": trading_day,
104
  "session_date": today.isoformat(),
 
105
  "latest_prediction_date": latest_date.isoformat() if latest_date else None,
106
- "t5_available": has_live_first5,
107
  }
108
 
109
 
@@ -116,11 +110,17 @@ def attach_market_state(payload: dict) -> dict:
116
  t5_available = bool(state["t5_available"] and latest.get("prediction"))
117
  market_closed = state["market_status"] == "Market Closed"
118
  unavailable_reason = "Market Closed" if market_closed else state["market_status"]
 
119
  payload["predictions"] = {
120
  "tomorrow": {
121
- "available": False,
122
- "status": "Market Closed" if market_closed else "Pending",
123
- "reason": "No next-session model is generated by this backend.",
 
 
 
 
 
124
  },
125
  "t5": {
126
  "available": t5_available,
@@ -182,6 +182,28 @@ async def daily_ist_refresh_loop() -> None:
182
  except Exception as exc:
183
  print(f"[scheduler] daily refresh failed: {exc}", flush=True)
184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  @app.on_event("startup")
186
  async def start_scheduler() -> None:
187
  global market_status
@@ -196,9 +218,12 @@ async def start_scheduler() -> None:
196
  market_status = "Market Pre-Open"
197
  elif now < time(9, 20):
198
  market_status = "Market Officially Opened"
199
- else:
200
  market_status = "Prediction Ready"
 
 
201
 
 
202
  asyncio.create_task(daily_ist_refresh_loop())
203
 
204
 
 
13
  from fastapi import FastAPI
14
 
15
  sys.path.insert(0, str(Path(__file__).resolve().parent))
16
+ from nifty_backend.runtime import (
17
+ IST,
18
+ dashboard_payload,
19
+ is_trading_day,
20
+ latest_saved_prediction,
21
+ next_trading_day,
22
+ refresh_daily_data,
23
+ refresh_first5_prediction,
24
+ seconds_until_next_ist_run,
25
+ )
26
 
27
 
28
  app = FastAPI(title="NIFTY 50 Forecaster Backend")
 
45
  return os.getenv("DASHBOARD_PIN", "1979")
46
 
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  def latest_prediction_date(payload: dict | None = None) -> date | None:
49
  try:
50
  latest = payload if payload is not None else latest_saved_prediction()
 
61
  current_time = now.time()
62
  trading_day = is_trading_day(today)
63
  latest_date = latest_prediction_date()
64
+ has_current_first5 = trading_day and latest_date == today and FIRST5_READY <= current_time
65
+ next_session = today if trading_day and current_time < MARKET_CLOSE else next_trading_day(today + timedelta(days=1))
66
 
67
  if not trading_day:
 
68
  status = "Market Closed"
69
+ detail = f"Next trading session is {next_session.isoformat()}."
70
  elif current_time < time(9, 0):
71
  status = "Waiting for 9:00 AM"
72
  detail = "Market has not entered pre-open yet."
 
80
  if market_status in {"Fetching T+5 Prediction Data...", "Prediction Failed"}:
81
  status = market_status
82
  detail = "The first-five-minute prediction job is still resolving."
83
+ elif has_current_first5:
84
  status = "Prediction Ready"
85
  detail = "Today's first-five-minute prediction is available."
86
  else:
 
95
  "market_detail": detail,
96
  "is_trading_day": trading_day,
97
  "session_date": today.isoformat(),
98
+ "next_session_date": next_session.isoformat(),
99
  "latest_prediction_date": latest_date.isoformat() if latest_date else None,
100
+ "t5_available": has_current_first5,
101
  }
102
 
103
 
 
110
  t5_available = bool(state["t5_available"] and latest.get("prediction"))
111
  market_closed = state["market_status"] == "Market Closed"
112
  unavailable_reason = "Market Closed" if market_closed else state["market_status"]
113
+ tomorrow_available = bool(latest.get("prediction"))
114
  payload["predictions"] = {
115
  "tomorrow": {
116
+ "available": tomorrow_available,
117
+ "status": "Ready" if tomorrow_available else "Pending",
118
+ "reason": None if tomorrow_available else "No saved next-session signal is available.",
119
+ "target_date": state["next_session_date"],
120
+ "input_date": latest.get("input_date"),
121
+ "prediction": latest.get("prediction") if tomorrow_available else None,
122
+ "prob_up": latest.get("prob_up") if tomorrow_available else None,
123
+ "confidence": latest.get("confidence") if tomorrow_available else None,
124
  },
125
  "t5": {
126
  "available": t5_available,
 
182
  except Exception as exc:
183
  print(f"[scheduler] daily refresh failed: {exc}", flush=True)
184
 
185
+
186
+ async def refresh_current_session_once() -> None:
187
+ global market_status
188
+ now = datetime.now(IST)
189
+ if not is_trading_day(now.date()) or now.time() < FIRST5_READY:
190
+ return
191
+ if latest_prediction_date() == now.date():
192
+ return
193
+ market_status = "Fetching T+5 Prediction Data..."
194
+ print("[startup] Current session needs first-five refresh; fetching now.", flush=True)
195
+ try:
196
+ await asyncio.to_thread(refresh_first5_prediction)
197
+ market_status = "Prediction Ready"
198
+ except Exception as exc:
199
+ print(f"[startup] first5 refresh failed: {exc}", flush=True)
200
+ market_status = "Prediction Failed"
201
+ try:
202
+ await asyncio.to_thread(refresh_daily_data)
203
+ except Exception as exc:
204
+ print(f"[startup] daily refresh failed: {exc}", flush=True)
205
+
206
+
207
  @app.on_event("startup")
208
  async def start_scheduler() -> None:
209
  global market_status
 
218
  market_status = "Market Pre-Open"
219
  elif now < time(9, 20):
220
  market_status = "Market Officially Opened"
221
+ elif latest_prediction_date() == today:
222
  market_status = "Prediction Ready"
223
+ else:
224
+ market_status = "Prediction Pending"
225
 
226
+ asyncio.create_task(refresh_current_session_once())
227
  asyncio.create_task(daily_ist_refresh_loop())
228
 
229
 
nifty_backend/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (257 Bytes). View file
 
nifty_backend/__pycache__/runtime.cpython-311.pyc ADDED
Binary file (36 kB). View file
 
nifty_backend/runtime.py CHANGED
@@ -13,6 +13,11 @@ import numpy as np
13
  import pandas as pd
14
  import yfinance as yf
15
 
 
 
 
 
 
16
 
17
  IST = ZoneInfo("Asia/Kolkata")
18
  YAHOO_NIFTY_SYMBOL = "^NSEI"
@@ -42,6 +47,52 @@ DECISION_OVERLAYS = [
42
  ]
43
 
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  class ProbabilityBlend:
46
  def __init__(self, models: list[Any], weights: np.ndarray):
47
  self.models = models
@@ -370,6 +421,8 @@ def dashboard_payload() -> dict[str, Any]:
370
  "test_brier": summary.get("test_brier"),
371
  "feature_count": summary.get("feature_count"),
372
  "recent_accuracy": recent_accuracy,
 
 
373
  }
374
  return {
375
  "latest": latest,
@@ -394,6 +447,10 @@ def dashboard_payload() -> dict[str, Any]:
394
 
395
 
396
  def refresh_first5_prediction(session_date: date | None = None) -> Prediction:
 
 
 
 
397
  minutes = fetch_yahoo_minutes(period="5d")
398
  append_parquet_rows(NIFTY_1M_PATH, minutes, ["date"])
399
  first5 = first5_features_from_minutes(minutes, session_date=session_date)
@@ -415,9 +472,16 @@ def refresh_daily_data() -> dict[str, Any]:
415
  }
416
 
417
 
 
 
 
 
 
 
 
 
 
418
  def seconds_until_next_ist_run(run_time: time = time(9, 20)) -> float:
419
  now = datetime.now(IST)
420
- target = datetime.combine(now.date(), run_time, tzinfo=IST)
421
- if now >= target:
422
- target += timedelta(days=1)
423
  return max(1.0, (target - now).total_seconds())
 
13
  import pandas as pd
14
  import yfinance as yf
15
 
16
+ try:
17
+ import pandas_market_calendars as mcal
18
+ except ImportError: # pragma: no cover - production dependency, local fallback below.
19
+ mcal = None
20
+
21
 
22
  IST = ZoneInfo("Asia/Kolkata")
23
  YAHOO_NIFTY_SYMBOL = "^NSEI"
 
47
  ]
48
 
49
 
50
+ def _nse_calendar():
51
+ if mcal is None:
52
+ return None
53
+ for name in ("XNSE", "NSE", "BSE"):
54
+ try:
55
+ return mcal.get_calendar(name)
56
+ except Exception:
57
+ continue
58
+ return None
59
+
60
+
61
+ def trading_schedule(start: date, end: date) -> pd.DataFrame:
62
+ calendar = _nse_calendar()
63
+ if calendar is None:
64
+ days = pd.date_range(start=start, end=end, freq="B")
65
+ return pd.DataFrame(index=days)
66
+ return calendar.schedule(start_date=start, end_date=end)
67
+
68
+
69
+ def is_trading_day(day: date) -> bool:
70
+ schedule = trading_schedule(day, day)
71
+ return not schedule.empty
72
+
73
+
74
+ def next_trading_day(start: date) -> date:
75
+ end = start + timedelta(days=14)
76
+ schedule = trading_schedule(start, end)
77
+ if schedule.empty:
78
+ day = start
79
+ while not is_trading_day(day):
80
+ day += timedelta(days=1)
81
+ return day
82
+ return pd.Timestamp(schedule.index[0]).date()
83
+
84
+
85
+ def previous_trading_day(start: date) -> date:
86
+ begin = start - timedelta(days=14)
87
+ schedule = trading_schedule(begin, start)
88
+ if schedule.empty:
89
+ day = start
90
+ while not is_trading_day(day):
91
+ day -= timedelta(days=1)
92
+ return day
93
+ return pd.Timestamp(schedule.index[-1]).date()
94
+
95
+
96
  class ProbabilityBlend:
97
  def __init__(self, models: list[Any], weights: np.ndarray):
98
  self.models = models
 
421
  "test_brier": summary.get("test_brier"),
422
  "feature_count": summary.get("feature_count"),
423
  "recent_accuracy": recent_accuracy,
424
+ "recent_accuracy_days": int(len(recent_predictions)) if not recent_predictions.empty else 0,
425
+ "total_test_days": int(len(test)) if not test.empty else int(summary.get("test_rows") or 0),
426
  }
427
  return {
428
  "latest": latest,
 
447
 
448
 
449
  def refresh_first5_prediction(session_date: date | None = None) -> Prediction:
450
+ if session_date is None:
451
+ today = datetime.now(IST).date()
452
+ if not is_trading_day(today):
453
+ raise RuntimeError(f"{today.isoformat()} is not an NSE trading session.")
454
  minutes = fetch_yahoo_minutes(period="5d")
455
  append_parquet_rows(NIFTY_1M_PATH, minutes, ["date"])
456
  first5 = first5_features_from_minutes(minutes, session_date=session_date)
 
472
  }
473
 
474
 
475
+ def next_ist_run_at(run_time: time = time(9, 20), now: datetime | None = None) -> datetime:
476
+ now = now or datetime.now(IST)
477
+ target_day = now.date()
478
+ if now >= datetime.combine(target_day, run_time, tzinfo=IST):
479
+ target_day += timedelta(days=1)
480
+ target_day = next_trading_day(target_day)
481
+ return datetime.combine(target_day, run_time, tzinfo=IST)
482
+
483
+
484
  def seconds_until_next_ist_run(run_time: time = time(9, 20)) -> float:
485
  now = datetime.now(IST)
486
+ target = next_ist_run_at(run_time, now=now)
 
 
487
  return max(1.0, (target - now).total_seconds())
scripts/__pycache__/run_ist_scheduler.cpython-311.pyc ADDED
Binary file (4.45 kB). View file
 
scripts/run_ist_scheduler.py CHANGED
@@ -1,22 +1,53 @@
1
  from __future__ import annotations
2
 
3
- import time
4
  import sys
5
- from datetime import datetime
 
6
  from pathlib import Path
7
  from zoneinfo import ZoneInfo
8
 
9
  sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
10
- from nifty_backend.runtime import refresh_daily_data, refresh_first5_prediction, seconds_until_next_ist_run
 
 
 
 
 
 
11
 
12
 
13
  IST = ZoneInfo("Asia/Kolkata")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
 
16
  def main() -> None:
17
  print("[scheduler] NIFTY first-five-minute scheduler started.")
18
  print("[scheduler] Runs the opening prediction after 09:20 IST so the 09:15-09:19 candles are complete.")
19
  while True:
 
 
 
 
20
  sleep_for = seconds_until_next_ist_run()
21
  target = datetime.now(IST).timestamp() + sleep_for
22
  print(f"[scheduler] sleeping {sleep_for / 60:.1f} minutes; next wake timestamp={target:.0f}")
 
1
  from __future__ import annotations
2
 
 
3
  import sys
4
+ import time
5
+ from datetime import date, datetime, time as dt_time
6
  from pathlib import Path
7
  from zoneinfo import ZoneInfo
8
 
9
  sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
10
+ from nifty_backend.runtime import (
11
+ is_trading_day,
12
+ latest_saved_prediction,
13
+ refresh_daily_data,
14
+ refresh_first5_prediction,
15
+ seconds_until_next_ist_run,
16
+ )
17
 
18
 
19
  IST = ZoneInfo("Asia/Kolkata")
20
+ FIRST5_READY = dt_time(9, 20)
21
+
22
+
23
+ def latest_prediction_date() -> date | None:
24
+ try:
25
+ raw = latest_saved_prediction().get("input_date")
26
+ return date.fromisoformat(str(raw)) if raw else None
27
+ except Exception:
28
+ return None
29
+
30
+
31
+ def refresh_if_current_session_is_ready() -> None:
32
+ now = datetime.now(IST)
33
+ if not is_trading_day(now.date()) or now.time() < FIRST5_READY:
34
+ return
35
+ if latest_prediction_date() == now.date():
36
+ return
37
+ prediction = refresh_first5_prediction()
38
+ print(f"[scheduler] first5 prediction refreshed: {prediction.to_dict()}")
39
+ info = refresh_daily_data()
40
+ print(f"[scheduler] daily data refreshed: {info}")
41
 
42
 
43
  def main() -> None:
44
  print("[scheduler] NIFTY first-five-minute scheduler started.")
45
  print("[scheduler] Runs the opening prediction after 09:20 IST so the 09:15-09:19 candles are complete.")
46
  while True:
47
+ try:
48
+ refresh_if_current_session_is_ready()
49
+ except Exception as exc:
50
+ print(f"[scheduler] current-session refresh failed: {exc}")
51
  sleep_for = seconds_until_next_ist_run()
52
  target = datetime.now(IST).timestamp() + sleep_for
53
  print(f"[scheduler] sleeping {sleep_for / 60:.1f} minutes; next wake timestamp={target:.0f}")