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

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +143 -12
app.py CHANGED
@@ -1,13 +1,16 @@
1
  from __future__ import annotations
2
 
3
  import asyncio
4
- from datetime import date, datetime, time
 
 
5
 
6
  import sys
7
  from pathlib import Path
8
 
9
- from fastapi import FastAPI, Query
10
  from fastapi.middleware.cors import CORSMiddleware
 
11
 
12
  sys.path.insert(0, str(Path(__file__).resolve().parent))
13
  from nifty_backend.runtime import dashboard_payload, latest_saved_prediction, refresh_daily_data, refresh_first5_prediction, seconds_until_next_ist_run, IST
@@ -24,12 +27,136 @@ app.add_middleware(
24
 
25
 
26
  market_status = "Waiting for next session"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  async def daily_ist_refresh_loop() -> None:
29
  global market_status
30
  while True:
31
  # Wait until 9:00 AM IST
32
  await asyncio.sleep(seconds_until_next_ist_run(time(9, 0)))
 
 
 
33
  market_status = "Market Pre-Open"
34
  print("[scheduler] 9:00 AM IST - Market Pre-Open", flush=True)
35
 
@@ -60,7 +187,10 @@ async def start_scheduler() -> None:
60
  global market_status
61
  # Initialize correct status on startup based on current time
62
  now = datetime.now(IST).time()
63
- if now < time(9, 0):
 
 
 
64
  market_status = "Waiting for 9:00 AM"
65
  elif now < time(9, 15):
66
  market_status = "Market Pre-Open"
@@ -83,22 +213,23 @@ def root() -> dict[str, str]:
83
 
84
 
85
  @app.get("/dashboard")
86
- def dashboard() -> dict:
87
- global market_status
88
- payload = dashboard_payload()
89
- if "data_status" not in payload:
90
- payload["data_status"] = {}
91
- payload["data_status"]["market_status"] = market_status
92
- return payload
 
93
 
94
 
95
  @app.get("/cron/keepalive")
96
  def cron_keepalive() -> dict:
97
- return {"status": "awake", "latest": latest_saved_prediction()}
98
 
99
 
100
  @app.get("/prediction/latest")
101
- def prediction_latest() -> dict:
102
  return latest_saved_prediction()
103
 
104
 
 
1
  from __future__ import annotations
2
 
3
  import asyncio
4
+ import hmac
5
+ import os
6
+ from datetime import date, datetime, time, timedelta
7
 
8
  import sys
9
  from pathlib import Path
10
 
11
+ from fastapi import Body, Depends, Header, HTTPException, Query
12
  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
 
27
 
28
 
29
  market_status = "Waiting for next session"
30
+ MARKET_OPEN = time(9, 15)
31
+ FIRST5_READY = time(9, 20)
32
+ MARKET_CLOSE = time(15, 30)
33
+
34
+
35
+ 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()
58
+ raw = latest.get("input_date")
59
+ return date.fromisoformat(str(raw)) if raw else None
60
+ except Exception:
61
+ return None
62
+
63
+
64
+ def current_market_state(now: datetime | None = None) -> dict:
65
+ global market_status
66
+ now = now or datetime.now(IST)
67
+ today = now.date()
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."
80
+ elif current_time < MARKET_OPEN:
81
+ status = "Market Pre-Open"
82
+ detail = "Market opens at 9:15 AM IST."
83
+ elif current_time < FIRST5_READY:
84
+ status = "Market Officially Opened"
85
+ detail = "Waiting for the first 5 one-minute bars."
86
+ elif current_time <= MARKET_CLOSE:
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:
94
+ status = "Prediction Pending"
95
+ detail = "No current-session prediction has been generated yet."
96
+ else:
97
+ status = "Market Closed"
98
+ detail = "Trading session has ended."
99
+
100
+ return {
101
+ "market_status": status,
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
+
110
+ def attach_market_state(payload: dict) -> dict:
111
+ state = current_market_state()
112
+ payload.setdefault("data_status", {})
113
+ payload["data_status"].update(state)
114
+
115
+ latest = payload.get("latest") or {}
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,
127
+ "status": "Ready" if t5_available else unavailable_reason,
128
+ "reason": None if t5_available else state["market_detail"],
129
+ "input_date": latest.get("input_date"),
130
+ "prediction": latest.get("prediction") if t5_available else None,
131
+ "prob_up": latest.get("prob_up") if t5_available else None,
132
+ "confidence": latest.get("confidence") if t5_available else None,
133
+ },
134
+ }
135
+ return payload
136
+
137
+
138
+ def verify_pin(pin: str | None) -> None:
139
+ if not pin or not hmac.compare_digest(pin, configured_pin()):
140
+ raise HTTPException(status_code=401, detail="Invalid PIN")
141
+
142
+
143
+ def verify_pin_header(
144
+ x_dashboard_pin: str | None = Header(default=None),
145
+ authorization: str | None = Header(default=None),
146
+ ) -> None:
147
+ pin = x_dashboard_pin
148
+ if not pin and authorization and authorization.lower().startswith("bearer "):
149
+ pin = authorization[7:].strip()
150
+ verify_pin(pin)
151
 
152
  async def daily_ist_refresh_loop() -> None:
153
  global market_status
154
  while True:
155
  # Wait until 9:00 AM IST
156
  await asyncio.sleep(seconds_until_next_ist_run(time(9, 0)))
157
+ if not is_trading_day(datetime.now(IST).date()):
158
+ market_status = "Market Closed"
159
+ continue
160
  market_status = "Market Pre-Open"
161
  print("[scheduler] 9:00 AM IST - Market Pre-Open", flush=True)
162
 
 
187
  global market_status
188
  # Initialize correct status on startup based on current time
189
  now = datetime.now(IST).time()
190
+ today = datetime.now(IST).date()
191
+ if not is_trading_day(today):
192
+ market_status = "Market Closed"
193
+ elif now < time(9, 0):
194
  market_status = "Waiting for 9:00 AM"
195
  elif now < time(9, 15):
196
  market_status = "Market Pre-Open"
 
213
 
214
 
215
  @app.get("/dashboard")
216
+ def dashboard(_: None = Depends(verify_pin_header)) -> dict:
217
+ return attach_market_state(dashboard_payload())
218
+
219
+
220
+ @app.post("/dashboard/unlock")
221
+ def dashboard_unlock(body: dict = Body(default_factory=dict)) -> dict:
222
+ verify_pin(str(body.get("pin", "")))
223
+ return attach_market_state(dashboard_payload())
224
 
225
 
226
  @app.get("/cron/keepalive")
227
  def cron_keepalive() -> dict:
228
+ return {"status": "awake", "market": current_market_state()}
229
 
230
 
231
  @app.get("/prediction/latest")
232
+ def prediction_latest(_: None = Depends(verify_pin_header)) -> dict:
233
  return latest_saved_prediction()
234
 
235