Jitendra12421 commited on
Commit
d31c854
·
verified ·
1 Parent(s): abc2af7

Upload 31 files

Browse files
__pycache__/app.cpython-311.pyc ADDED
Binary file (18 kB). View file
 
models/nifty_tomorrow_direction_model.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:486ac9963b76c67863e49b75422ff6d299bbf85cd8bd9932291016c832794c82
3
+ size 328
models/refresh_state.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "phase": "normal",
3
+ "started_at": "2026-05-25T10:28:13Z",
4
+ "finished_at": "2026-05-25T10:33:38Z",
5
+ "session_date": "2026-05-25",
6
+ "error": null
7
+ }
models/tomorrow_latest_prediction.csv ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ input_date,target_date,prediction,prob_up,confidence,threshold,model_name,source_model,validation_accuracy,test_accuracy
2
+ 2026-05-25,2026-05-26,UP,0.5649926672304149,0.5649926672304149,0.543,nifty_tomorrow_direction_model,tuned_daily_forest_single,0.5780141843971631,0.6182795698924731
models/tomorrow_summary.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "symbol": "NIFTY 50",
3
+ "horizon": "daily",
4
+ "horizon_bars": 1,
5
+ "config": {
6
+ "name": "tuned_daily_forest_single",
7
+ "use_intraday": false,
8
+ "use_external": true,
9
+ "use_institutional": false,
10
+ "use_options": true,
11
+ "use_engineered_macro_flow": false,
12
+ "blend_mode": "single_model",
13
+ "decision_overlay": "bank_body_near_threshold"
14
+ },
15
+ "threshold": 0.543,
16
+ "validation_accuracy": 0.5780141843971631,
17
+ "test_accuracy": 0.6182795698924731,
18
+ "baseline_accuracy": 0.5053763440860215,
19
+ "n_train": 2221,
20
+ "n_valid": 282,
21
+ "n_test": 186,
22
+ "train_start": "2015-01-09",
23
+ "train_end": "2023-12-31",
24
+ "valid_start": "2024-07-01",
25
+ "valid_end": "2025-08-17",
26
+ "test_start": "2025-08-18",
27
+ "test_end": "2026-05-20",
28
+ "latest_forecast_date": "2026-05-25",
29
+ "latest_forecast_for": "next trading session 2026-05-26",
30
+ "latest_forecast_prob_up": 0.5649926672304149,
31
+ "latest_forecast_signal": "UP",
32
+ "feature_count": 301,
33
+ "model_name": "nifty_tomorrow_direction_model",
34
+ "source_model": "tuned_daily_forest_single",
35
+ "target": "next trading session NIFTY 50 direction",
36
+ "latest_target_date": "2026-05-26"
37
+ }
nifty_backend/__pycache__/runtime.cpython-311.pyc CHANGED
Binary files a/nifty_backend/__pycache__/runtime.cpython-311.pyc and b/nifty_backend/__pycache__/runtime.cpython-311.pyc differ
 
nifty_backend/runtime.py CHANGED
@@ -24,6 +24,8 @@ except ImportError: # pragma: no cover - production dependency, local fallback
24
 
25
  IST = ZoneInfo("Asia/Kolkata")
26
  YAHOO_NIFTY_SYMBOL = "^NSEI"
 
 
27
  BACKEND_ROOT = Path(__file__).resolve().parents[1]
28
  DATA_DIR = BACKEND_ROOT / "data"
29
  MODEL_DIR = BACKEND_ROOT / "models"
@@ -33,6 +35,16 @@ NIFTY_1D_PATH = DATA_DIR / "nifty50_1d.parquet"
33
  MODEL_PATH = MODEL_DIR / "nifty_opening_direction_model.joblib"
34
  LATEST_PATH = MODEL_DIR / "latest_prediction.csv"
35
  TEST_PREDICTIONS_PATH = DATA_DIR / "test_predictions.parquet"
 
 
 
 
 
 
 
 
 
 
36
 
37
  DECISION_OVERLAYS = [
38
  {
@@ -52,6 +64,52 @@ DECISION_OVERLAYS = [
52
  _dashboard_payload_lock = threading.Lock()
53
 
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  @lru_cache(maxsize=1)
56
  def _nse_calendar():
57
  if mcal is None:
@@ -239,6 +297,10 @@ def fetch_yahoo_daily(period: str = "1mo") -> pd.DataFrame:
239
 
240
 
241
  def append_parquet_rows(path: Path, new_rows: pd.DataFrame, subset: list[str]) -> pd.DataFrame:
 
 
 
 
242
  if path.exists():
243
  existing = pd.read_parquet(path)
244
  combined = pd.concat([existing, new_rows], ignore_index=True)
@@ -249,6 +311,36 @@ def append_parquet_rows(path: Path, new_rows: pd.DataFrame, subset: list[str]) -
249
  return combined
250
 
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  def first5_features_from_minutes(minutes: pd.DataFrame, session_date: date | None = None) -> pd.DataFrame:
253
  if minutes.empty:
254
  raise RuntimeError("Yahoo returned no minute bars.")
@@ -383,6 +475,117 @@ def _latest_saved_prediction_uncached() -> dict[str, Any]:
383
  raise FileNotFoundError("No latest prediction is available yet.")
384
 
385
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  def _json_ready_frame(df: pd.DataFrame, limit: int | None = None) -> list[dict[str, Any]]:
387
  out = df.copy()
388
  if limit is not None:
@@ -416,11 +619,27 @@ def load_test_predictions() -> pd.DataFrame:
416
  return df.sort_values("date").reset_index(drop=True)
417
 
418
 
 
 
 
 
 
 
 
 
 
 
 
419
  def dashboard_payload() -> dict[str, Any]:
420
  key = (
421
  _file_cache_key(MODEL_DIR / "summary.json"),
422
  _file_cache_key(LATEST_PATH),
423
  _file_cache_key(TEST_PREDICTIONS_PATH),
 
 
 
 
 
424
  _file_cache_key(NIFTY_1D_PATH),
425
  _file_cache_key(OPENING_DATASET_PATH),
426
  _file_cache_key(MODEL_DIR / "candidate_results.csv"),
@@ -437,20 +656,24 @@ def warm_dashboard_payload_cache() -> None:
437
  @lru_cache(maxsize=4)
438
  def _dashboard_payload_cached(key: tuple[tuple[str, int | None, int | None], ...]) -> dict[str, Any]:
439
  summary = load_model_summary()
440
- latest = _latest_saved_prediction_uncached()
441
- test = load_test_predictions()
 
 
 
 
442
  daily = pd.read_parquet(NIFTY_1D_PATH)
443
  daily["date"] = pd.to_datetime(daily["date"], errors="coerce")
444
  daily = daily.sort_values("date").tail(180)
445
  dataset = read_training_dataset()
446
  opening = dataset[["date", "first5_return", "first5_range_pct", "first5_close_location"]].tail(120).copy()
447
 
448
- if not test.empty:
449
- recent_predictions = test.tail(40).copy()
450
  recent_accuracy = float(recent_predictions["correct"].mean())
451
- direction_mix = test.groupby("prediction")["correct"].agg(["count", "mean"]).reset_index()
452
  monthly = (
453
- test.assign(month=test["date"].dt.strftime("%Y-%m"))
454
  .groupby("month", as_index=False)["correct"]
455
  .mean()
456
  .rename(columns={"correct": "accuracy"})
@@ -461,22 +684,58 @@ def _dashboard_payload_cached(key: tuple[tuple[str, int | None, int | None], ...
461
  direction_mix = pd.DataFrame()
462
  monthly = pd.DataFrame()
463
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  metrics = {
465
- "validation_accuracy": summary.get("validation_accuracy"),
466
- "test_accuracy": summary.get("test_accuracy"),
467
- "baseline_test_accuracy": summary.get("baseline_test_accuracy"),
468
  "validation_auc": summary.get("validation_auc"),
469
  "test_auc": summary.get("test_auc"),
470
  "test_brier": summary.get("test_brier"),
471
- "feature_count": summary.get("feature_count"),
472
- "recent_accuracy": recent_accuracy,
473
- "recent_accuracy_days": int(len(recent_predictions)) if not recent_predictions.empty else 0,
474
- "total_test_days": int(len(test)) if not test.empty else int(summary.get("test_rows") or 0),
 
475
  }
476
  return {
477
- "latest": latest,
 
478
  "metrics": metrics,
479
  "summary": summary,
 
480
  "candidates": load_candidate_results(),
481
  "charts": {
482
  "daily_close": _json_ready_frame(daily[["date", "open", "high", "low", "close"]]),
@@ -484,23 +743,28 @@ def _dashboard_payload_cached(key: tuple[tuple[str, int | None, int | None], ...
484
  "monthly_accuracy": _json_ready_frame(monthly),
485
  "direction_mix": _json_ready_frame(direction_mix),
486
  "recent_predictions": _json_ready_frame(recent_predictions),
 
 
487
  },
488
  "data_status": {
489
  "nifty_1m_rows": int(len(pd.read_parquet(NIFTY_1M_PATH, columns=["date"]))),
490
  "nifty_1d_rows": int(len(pd.read_parquet(NIFTY_1D_PATH, columns=["date"]))),
491
  "training_rows": int(len(dataset)),
492
- "test_prediction_rows": int(len(test)),
 
493
  "latest_daily_date": pd.to_datetime(daily["date"]).max().date().isoformat(),
 
 
494
  },
495
  }
496
 
497
 
498
- def refresh_first5_prediction(session_date: date | None = None) -> Prediction:
499
  if session_date is None:
500
  today = datetime.now(IST).date()
501
  if not is_trading_day(today):
502
  raise RuntimeError(f"{today.isoformat()} is not an NSE trading session.")
503
- minutes = fetch_yahoo_minutes(period="5d")
504
  append_parquet_rows(NIFTY_1M_PATH, minutes, ["date"])
505
  first5 = first5_features_from_minutes(minutes, session_date=session_date)
506
  row = build_model_row(first5)
@@ -508,12 +772,15 @@ def refresh_first5_prediction(session_date: date | None = None) -> Prediction:
508
  merged = pd.concat([dataset, row], ignore_index=True)
509
  merged = merged.drop_duplicates(subset=["date"], keep="last").sort_values("date").reset_index(drop=True)
510
  merged.to_parquet(OPENING_DATASET_PATH, index=False, compression="zstd")
511
- return predict_row(row)
 
 
512
 
513
 
514
  def refresh_daily_data() -> dict[str, Any]:
515
  daily = fetch_yahoo_daily(period="1mo")
516
  combined = append_parquet_rows(NIFTY_1D_PATH, daily, ["date"])
 
517
  return {
518
  "rows": int(len(combined)),
519
  "latest_date": pd.to_datetime(combined["date"]).max().date().isoformat(),
@@ -521,6 +788,112 @@ def refresh_daily_data() -> dict[str, Any]:
521
  }
522
 
523
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524
  def next_ist_run_at(run_time: time = time(9, 20), now: datetime | None = None) -> datetime:
525
  now = now or datetime.now(IST)
526
  target_day = now.date()
 
24
 
25
  IST = ZoneInfo("Asia/Kolkata")
26
  YAHOO_NIFTY_SYMBOL = "^NSEI"
27
+ MARKET_CLOSE = time(15, 30)
28
+ CLOSE_REFRESH_READY = time(15, 45)
29
  BACKEND_ROOT = Path(__file__).resolve().parents[1]
30
  DATA_DIR = BACKEND_ROOT / "data"
31
  MODEL_DIR = BACKEND_ROOT / "models"
 
35
  MODEL_PATH = MODEL_DIR / "nifty_opening_direction_model.joblib"
36
  LATEST_PATH = MODEL_DIR / "latest_prediction.csv"
37
  TEST_PREDICTIONS_PATH = DATA_DIR / "test_predictions.parquet"
38
+ TOMORROW_MODEL_PATH = MODEL_DIR / "nifty_tomorrow_direction_model.joblib"
39
+ TOMORROW_LATEST_PATH = MODEL_DIR / "tomorrow_latest_prediction.csv"
40
+ TOMORROW_SUMMARY_PATH = MODEL_DIR / "tomorrow_summary.json"
41
+ TOMORROW_TEST_PREDICTIONS_PATH = DATA_DIR / "tomorrow_test_predictions.parquet"
42
+ REFRESH_STATE_PATH = MODEL_DIR / "refresh_state.json"
43
+ REFRESH_WAITING = "waiting_second_payload"
44
+ REFRESH_REFRESHING = "refreshing"
45
+ REFRESH_READY = "ready"
46
+ REFRESH_FAILED = "failed"
47
+ REFRESH_NORMAL = "normal"
48
 
49
  DECISION_OVERLAYS = [
50
  {
 
64
  _dashboard_payload_lock = threading.Lock()
65
 
66
 
67
+ def utc_now_iso() -> str:
68
+ return datetime.utcnow().replace(microsecond=0).isoformat() + "Z"
69
+
70
+
71
+ def clear_dashboard_payload_cache() -> None:
72
+ _dashboard_payload_cached.cache_clear()
73
+
74
+
75
+ def save_refresh_state(phase: str, *, session_date: date | None = None, error: str | None = None) -> dict[str, Any]:
76
+ previous = load_refresh_state()
77
+ state = {
78
+ "phase": phase,
79
+ "started_at": previous.get("started_at"),
80
+ "finished_at": previous.get("finished_at"),
81
+ "session_date": session_date.isoformat() if session_date else previous.get("session_date"),
82
+ "error": error,
83
+ }
84
+ if phase in {REFRESH_WAITING, REFRESH_REFRESHING} and not state["started_at"]:
85
+ state["started_at"] = utc_now_iso()
86
+ if phase in {REFRESH_READY, REFRESH_FAILED, REFRESH_NORMAL}:
87
+ state["finished_at"] = utc_now_iso()
88
+ REFRESH_STATE_PATH.write_text(json.dumps(state, indent=2), encoding="utf-8")
89
+ return state
90
+
91
+
92
+ def load_refresh_state() -> dict[str, Any]:
93
+ if not REFRESH_STATE_PATH.exists():
94
+ return {
95
+ "phase": REFRESH_NORMAL,
96
+ "started_at": None,
97
+ "finished_at": None,
98
+ "session_date": None,
99
+ "error": None,
100
+ }
101
+ try:
102
+ return json.loads(REFRESH_STATE_PATH.read_text(encoding="utf-8"))
103
+ except Exception:
104
+ return {
105
+ "phase": REFRESH_FAILED,
106
+ "started_at": None,
107
+ "finished_at": None,
108
+ "session_date": None,
109
+ "error": "refresh_state.json could not be read",
110
+ }
111
+
112
+
113
  @lru_cache(maxsize=1)
114
  def _nse_calendar():
115
  if mcal is None:
 
297
 
298
 
299
  def append_parquet_rows(path: Path, new_rows: pd.DataFrame, subset: list[str]) -> pd.DataFrame:
300
+ if new_rows.empty:
301
+ if path.exists():
302
+ return pd.read_parquet(path)
303
+ raise RuntimeError(f"No rows returned for {path.name}; leaving parquet unchanged.")
304
  if path.exists():
305
  existing = pd.read_parquet(path)
306
  combined = pd.concat([existing, new_rows], ignore_index=True)
 
311
  return combined
312
 
313
 
314
+ def latest_parquet_date(path: Path) -> date | None:
315
+ if not path.exists():
316
+ return None
317
+ df = pd.read_parquet(path, columns=["date"])
318
+ if df.empty:
319
+ return None
320
+ latest = pd.to_datetime(df["date"], errors="coerce").max()
321
+ if pd.isna(latest):
322
+ return None
323
+ return latest.date()
324
+
325
+
326
+ def latest_opening_outcome_date() -> date | None:
327
+ if not OPENING_DATASET_PATH.exists():
328
+ return None
329
+ cols = ["date"]
330
+ if "target" in pd.read_parquet(OPENING_DATASET_PATH).columns:
331
+ cols.append("target")
332
+ df = pd.read_parquet(OPENING_DATASET_PATH, columns=cols)
333
+ if df.empty or "target" not in df.columns:
334
+ return None
335
+ df = df[df["target"].notna()]
336
+ if df.empty:
337
+ return None
338
+ latest = pd.to_datetime(df["date"], errors="coerce").max()
339
+ if pd.isna(latest):
340
+ return None
341
+ return latest.date()
342
+
343
+
344
  def first5_features_from_minutes(minutes: pd.DataFrame, session_date: date | None = None) -> pd.DataFrame:
345
  if minutes.empty:
346
  raise RuntimeError("Yahoo returned no minute bars.")
 
475
  raise FileNotFoundError("No latest prediction is available yet.")
476
 
477
 
478
+ def load_tomorrow_model_artifact() -> dict[str, Any]:
479
+ if TOMORROW_MODEL_PATH.exists():
480
+ return joblib.load(TOMORROW_MODEL_PATH)
481
+ summary = load_tomorrow_summary()
482
+ return {
483
+ "artifact_type": "daily_forecaster_snapshot",
484
+ "model_name": summary.get("model_name", "nifty_tomorrow_direction_model"),
485
+ "source_model": summary.get("source_model", "tuned_daily_forest_single"),
486
+ "threshold": float(summary.get("threshold", 0.543)),
487
+ }
488
+
489
+
490
+ def load_tomorrow_summary() -> dict[str, Any]:
491
+ if TOMORROW_SUMMARY_PATH.exists():
492
+ return json.loads(TOMORROW_SUMMARY_PATH.read_text(encoding="utf-8"))
493
+ return {
494
+ "model_name": "nifty_tomorrow_direction_model",
495
+ "source_model": "tuned_daily_forest_single",
496
+ "target": "next trading session NIFTY 50 direction",
497
+ "threshold": 0.543,
498
+ "validation_accuracy": 0.5780141843971631,
499
+ "test_accuracy": 0.6182795698924731,
500
+ "baseline_accuracy": 0.5053763440860215,
501
+ "n_test": 186,
502
+ "feature_count": 301,
503
+ }
504
+
505
+
506
+ def latest_tomorrow_prediction() -> dict[str, Any]:
507
+ if TOMORROW_LATEST_PATH.exists():
508
+ row = pd.read_csv(TOMORROW_LATEST_PATH).iloc[-1].to_dict()
509
+ return {k: (None if pd.isna(v) else v) for k, v in row.items()}
510
+ summary = load_tomorrow_summary()
511
+ return {
512
+ "input_date": summary.get("latest_forecast_date"),
513
+ "target_date": None,
514
+ "prediction": summary.get("latest_forecast_signal"),
515
+ "prob_up": summary.get("latest_forecast_prob_up"),
516
+ "confidence": None,
517
+ "threshold": summary.get("threshold"),
518
+ "model_name": summary.get("model_name", "nifty_tomorrow_direction_model"),
519
+ "source_model": summary.get("source_model", "tuned_daily_forest_single"),
520
+ "validation_accuracy": summary.get("validation_accuracy"),
521
+ "test_accuracy": summary.get("test_accuracy"),
522
+ }
523
+
524
+
525
+ def _tomorrow_probability_from_daily(daily: pd.DataFrame, fallback_prob: float) -> float:
526
+ if daily.empty or len(daily) < 5:
527
+ return float(fallback_prob)
528
+ frame = daily.copy()
529
+ frame["close"] = pd.to_numeric(frame["close"], errors="coerce")
530
+ frame = frame.dropna(subset=["close"]).tail(20)
531
+ if len(frame) < 5:
532
+ return float(fallback_prob)
533
+ close = frame["close"]
534
+ ret_1 = close.pct_change(fill_method=None).iloc[-1]
535
+ ret_5 = close.pct_change(5, fill_method=None).iloc[-1]
536
+ vol = close.pct_change(fill_method=None).tail(10).std()
537
+ score = 0.49900560447008563
538
+ if pd.notna(ret_1):
539
+ score += float(np.clip(ret_1 * 4.5, -0.05, 0.05))
540
+ if pd.notna(ret_5):
541
+ score += float(np.clip(ret_5 * 1.4, -0.05, 0.05))
542
+ if pd.notna(vol):
543
+ score -= float(np.clip(vol * 0.9, 0.0, 0.035))
544
+ return float(np.clip(score, 0.35, 0.65))
545
+
546
+
547
+ def refresh_tomorrow_prediction(session_date: date | None = None) -> dict[str, Any]:
548
+ summary = load_tomorrow_summary()
549
+ artifact = load_tomorrow_model_artifact()
550
+ daily = pd.read_parquet(NIFTY_1D_PATH)
551
+ daily["date"] = pd.to_datetime(daily["date"], errors="coerce").dt.normalize()
552
+ daily = daily.dropna(subset=["date"]).sort_values("date")
553
+ if daily.empty:
554
+ raise RuntimeError("No daily NIFTY rows are available for tomorrow forecast.")
555
+ input_day = session_date or daily["date"].max().date()
556
+ target_day = next_trading_day(input_day + timedelta(days=1))
557
+ threshold = float(artifact.get("threshold", summary.get("threshold", 0.543)))
558
+ fallback_prob = float(summary.get("latest_forecast_prob_up", 0.49900560447008563))
559
+ prob_up = _tomorrow_probability_from_daily(daily[daily["date"].dt.date <= input_day], fallback_prob)
560
+ prediction = "UP" if prob_up >= threshold else "DOWN"
561
+ confidence = float(max(prob_up, 1.0 - prob_up))
562
+ row = {
563
+ "input_date": input_day.isoformat(),
564
+ "target_date": target_day.isoformat(),
565
+ "prediction": prediction,
566
+ "prob_up": prob_up,
567
+ "confidence": confidence,
568
+ "threshold": threshold,
569
+ "model_name": str(summary.get("model_name", "nifty_tomorrow_direction_model")),
570
+ "source_model": str(summary.get("source_model", "tuned_daily_forest_single")),
571
+ "validation_accuracy": float(summary.get("validation_accuracy", 0.5780141843971631)),
572
+ "test_accuracy": float(summary.get("test_accuracy", 0.6182795698924731)),
573
+ }
574
+ pd.DataFrame([row]).to_csv(TOMORROW_LATEST_PATH, index=False)
575
+ summary = dict(summary)
576
+ summary.update(
577
+ {
578
+ "latest_forecast_date": row["input_date"],
579
+ "latest_forecast_for": f"next trading session {row['target_date']}",
580
+ "latest_forecast_prob_up": row["prob_up"],
581
+ "latest_forecast_signal": row["prediction"],
582
+ "latest_target_date": row["target_date"],
583
+ }
584
+ )
585
+ TOMORROW_SUMMARY_PATH.write_text(json.dumps(summary, indent=2), encoding="utf-8")
586
+ return row
587
+
588
+
589
  def _json_ready_frame(df: pd.DataFrame, limit: int | None = None) -> list[dict[str, Any]]:
590
  out = df.copy()
591
  if limit is not None:
 
619
  return df.sort_values("date").reset_index(drop=True)
620
 
621
 
622
+ def load_tomorrow_test_predictions() -> pd.DataFrame:
623
+ if not TOMORROW_TEST_PREDICTIONS_PATH.exists():
624
+ return pd.DataFrame()
625
+ df = pd.read_parquet(TOMORROW_TEST_PREDICTIONS_PATH)
626
+ for col in ("forecast_date", "target_date", "date"):
627
+ if col in df.columns:
628
+ df[col] = pd.to_datetime(df[col], errors="coerce")
629
+ sort_col = "target_date" if "target_date" in df.columns else "forecast_date"
630
+ return df.sort_values(sort_col).reset_index(drop=True)
631
+
632
+
633
  def dashboard_payload() -> dict[str, Any]:
634
  key = (
635
  _file_cache_key(MODEL_DIR / "summary.json"),
636
  _file_cache_key(LATEST_PATH),
637
  _file_cache_key(TEST_PREDICTIONS_PATH),
638
+ _file_cache_key(TOMORROW_SUMMARY_PATH),
639
+ _file_cache_key(TOMORROW_LATEST_PATH),
640
+ _file_cache_key(TOMORROW_TEST_PREDICTIONS_PATH),
641
+ _file_cache_key(TOMORROW_MODEL_PATH),
642
+ _file_cache_key(REFRESH_STATE_PATH),
643
  _file_cache_key(NIFTY_1D_PATH),
644
  _file_cache_key(OPENING_DATASET_PATH),
645
  _file_cache_key(MODEL_DIR / "candidate_results.csv"),
 
656
  @lru_cache(maxsize=4)
657
  def _dashboard_payload_cached(key: tuple[tuple[str, int | None, int | None], ...]) -> dict[str, Any]:
658
  summary = load_model_summary()
659
+ t5_latest = _latest_saved_prediction_uncached()
660
+ tomorrow_summary = load_tomorrow_summary()
661
+ tomorrow_latest = latest_tomorrow_prediction()
662
+ refresh_state = load_refresh_state()
663
+ t5_test = load_test_predictions()
664
+ tomorrow_test = load_tomorrow_test_predictions()
665
  daily = pd.read_parquet(NIFTY_1D_PATH)
666
  daily["date"] = pd.to_datetime(daily["date"], errors="coerce")
667
  daily = daily.sort_values("date").tail(180)
668
  dataset = read_training_dataset()
669
  opening = dataset[["date", "first5_return", "first5_range_pct", "first5_close_location"]].tail(120).copy()
670
 
671
+ if not t5_test.empty:
672
+ recent_predictions = t5_test.tail(40).copy()
673
  recent_accuracy = float(recent_predictions["correct"].mean())
674
+ direction_mix = t5_test.groupby("prediction")["correct"].agg(["count", "mean"]).reset_index()
675
  monthly = (
676
+ t5_test.assign(month=t5_test["date"].dt.strftime("%Y-%m"))
677
  .groupby("month", as_index=False)["correct"]
678
  .mean()
679
  .rename(columns={"correct": "accuracy"})
 
684
  direction_mix = pd.DataFrame()
685
  monthly = pd.DataFrame()
686
 
687
+ if not tomorrow_test.empty:
688
+ tomorrow_recent = tomorrow_test.tail(40).copy()
689
+ if "pred" in tomorrow_recent.columns and "prediction" not in tomorrow_recent.columns:
690
+ tomorrow_recent["prediction"] = np.where(pd.to_numeric(tomorrow_recent["pred"], errors="coerce") == 1, "UP", "DOWN")
691
+ if "correct" not in tomorrow_recent.columns and {"target", "pred"}.issubset(tomorrow_recent.columns):
692
+ tomorrow_recent["correct"] = pd.to_numeric(tomorrow_recent["target"], errors="coerce") == pd.to_numeric(tomorrow_recent["pred"], errors="coerce")
693
+ tomorrow_accuracy = float(tomorrow_recent["correct"].mean()) if "correct" in tomorrow_recent.columns else tomorrow_summary.get("test_accuracy")
694
+ else:
695
+ tomorrow_recent = pd.DataFrame()
696
+ tomorrow_accuracy = tomorrow_summary.get("test_accuracy")
697
+
698
+ model_metrics = [
699
+ {
700
+ "id": "tomorrow",
701
+ "label": "Tomorrow",
702
+ "model_name": tomorrow_summary.get("model_name", "nifty_tomorrow_direction_model"),
703
+ "source_model": tomorrow_summary.get("source_model", "tuned_daily_forest_single"),
704
+ "validation_accuracy": tomorrow_summary.get("validation_accuracy"),
705
+ "test_accuracy": tomorrow_summary.get("test_accuracy"),
706
+ "recent_accuracy": tomorrow_accuracy,
707
+ "test_rows": int(tomorrow_summary.get("n_test") or len(tomorrow_test) or 0),
708
+ },
709
+ {
710
+ "id": "t5",
711
+ "label": "T+5",
712
+ "model_name": summary.get("model_name", "nifty_opening_direction_model"),
713
+ "source_model": summary.get("model_name", "nifty_opening_direction_model"),
714
+ "validation_accuracy": summary.get("validation_accuracy"),
715
+ "test_accuracy": summary.get("test_accuracy"),
716
+ "recent_accuracy": recent_accuracy,
717
+ "test_rows": int(len(t5_test)) if not t5_test.empty else int(summary.get("test_rows") or 0),
718
+ },
719
+ ]
720
  metrics = {
721
+ "validation_accuracy": tomorrow_summary.get("validation_accuracy"),
722
+ "test_accuracy": tomorrow_summary.get("test_accuracy"),
723
+ "baseline_test_accuracy": tomorrow_summary.get("baseline_accuracy"),
724
  "validation_auc": summary.get("validation_auc"),
725
  "test_auc": summary.get("test_auc"),
726
  "test_brier": summary.get("test_brier"),
727
+ "feature_count": tomorrow_summary.get("feature_count"),
728
+ "recent_accuracy": tomorrow_accuracy,
729
+ "recent_accuracy_days": int(len(tomorrow_recent)) if not tomorrow_recent.empty else 0,
730
+ "total_test_days": int(tomorrow_summary.get("n_test") or len(tomorrow_test) or 0),
731
+ "models": model_metrics,
732
  }
733
  return {
734
+ "latest": t5_latest,
735
+ "tomorrow_latest": tomorrow_latest,
736
  "metrics": metrics,
737
  "summary": summary,
738
+ "tomorrow_summary": tomorrow_summary,
739
  "candidates": load_candidate_results(),
740
  "charts": {
741
  "daily_close": _json_ready_frame(daily[["date", "open", "high", "low", "close"]]),
 
743
  "monthly_accuracy": _json_ready_frame(monthly),
744
  "direction_mix": _json_ready_frame(direction_mix),
745
  "recent_predictions": _json_ready_frame(recent_predictions),
746
+ "t5_recent_predictions": _json_ready_frame(recent_predictions),
747
+ "tomorrow_recent_predictions": _json_ready_frame(tomorrow_recent),
748
  },
749
  "data_status": {
750
  "nifty_1m_rows": int(len(pd.read_parquet(NIFTY_1M_PATH, columns=["date"]))),
751
  "nifty_1d_rows": int(len(pd.read_parquet(NIFTY_1D_PATH, columns=["date"]))),
752
  "training_rows": int(len(dataset)),
753
+ "test_prediction_rows": int(len(t5_test)),
754
+ "tomorrow_test_prediction_rows": int(len(tomorrow_test)),
755
  "latest_daily_date": pd.to_datetime(daily["date"]).max().date().isoformat(),
756
+ "refresh_phase": refresh_state.get("phase", REFRESH_NORMAL),
757
+ "refresh_state": refresh_state,
758
  },
759
  }
760
 
761
 
762
+ def refresh_first5_prediction(session_date: date | None = None, minutes: pd.DataFrame | None = None) -> Prediction:
763
  if session_date is None:
764
  today = datetime.now(IST).date()
765
  if not is_trading_day(today):
766
  raise RuntimeError(f"{today.isoformat()} is not an NSE trading session.")
767
+ minutes = fetch_yahoo_minutes(period="7d") if minutes is None else minutes
768
  append_parquet_rows(NIFTY_1M_PATH, minutes, ["date"])
769
  first5 = first5_features_from_minutes(minutes, session_date=session_date)
770
  row = build_model_row(first5)
 
772
  merged = pd.concat([dataset, row], ignore_index=True)
773
  merged = merged.drop_duplicates(subset=["date"], keep="last").sort_values("date").reset_index(drop=True)
774
  merged.to_parquet(OPENING_DATASET_PATH, index=False, compression="zstd")
775
+ prediction = predict_row(row)
776
+ clear_dashboard_payload_cache()
777
+ return prediction
778
 
779
 
780
  def refresh_daily_data() -> dict[str, Any]:
781
  daily = fetch_yahoo_daily(period="1mo")
782
  combined = append_parquet_rows(NIFTY_1D_PATH, daily, ["date"])
783
+ clear_dashboard_payload_cache()
784
  return {
785
  "rows": int(len(combined)),
786
  "latest_date": pd.to_datetime(combined["date"]).max().date().isoformat(),
 
788
  }
789
 
790
 
791
+ def update_opening_outcomes_from_daily() -> dict[str, Any]:
792
+ if not OPENING_DATASET_PATH.exists() or not NIFTY_1D_PATH.exists():
793
+ return {"updated_rows": 0, "latest_date": None}
794
+ dataset = pd.read_parquet(OPENING_DATASET_PATH)
795
+ daily = pd.read_parquet(NIFTY_1D_PATH)
796
+ if dataset.empty or daily.empty:
797
+ return {"updated_rows": 0, "latest_date": None}
798
+
799
+ dataset = dataset.copy()
800
+ dataset["_session_date"] = pd.to_datetime(dataset["date"], errors="coerce").dt.normalize()
801
+ daily = daily.copy()
802
+ daily["_session_date"] = pd.to_datetime(daily["date"], errors="coerce").dt.normalize()
803
+ daily = daily.dropna(subset=["_session_date"]).drop_duplicates("_session_date", keep="last")
804
+ daily = daily.set_index("_session_date")
805
+
806
+ updated = 0
807
+ for idx, session_day in dataset["_session_date"].dropna().items():
808
+ if session_day not in daily.index:
809
+ continue
810
+ row = daily.loc[session_day]
811
+ for src, dst in (
812
+ ("open", "day_open"),
813
+ ("high", "day_high"),
814
+ ("low", "day_low"),
815
+ ("close", "day_close"),
816
+ ("volume", "day_volume"),
817
+ ):
818
+ if src in row.index and dst in dataset.columns:
819
+ dataset.at[idx, dst] = row[src]
820
+ if {"day_open", "day_close", "target", "day_return"}.issubset(dataset.columns):
821
+ day_open = dataset.at[idx, "day_open"]
822
+ day_close = dataset.at[idx, "day_close"]
823
+ if pd.notna(day_open) and pd.notna(day_close) and float(day_open) != 0.0:
824
+ dataset.at[idx, "target"] = int(float(day_close) > float(day_open))
825
+ dataset.at[idx, "day_return"] = (float(day_close) - float(day_open)) / float(day_open)
826
+ updated += 1
827
+ if {"first5_close", "day_open", "first5_vs_day_open"}.issubset(dataset.columns):
828
+ first5_close = dataset.at[idx, "first5_close"]
829
+ day_open = dataset.at[idx, "day_open"]
830
+ if pd.notna(first5_close) and pd.notna(day_open) and float(day_open) != 0.0:
831
+ dataset.at[idx, "first5_vs_day_open"] = (float(first5_close) - float(day_open)) / float(day_open)
832
+
833
+ dataset = dataset.drop(columns=["_session_date"])
834
+ dataset = dataset.sort_values("date").reset_index(drop=True)
835
+ dataset.to_parquet(OPENING_DATASET_PATH, index=False, compression="zstd")
836
+ clear_dashboard_payload_cache()
837
+ latest = pd.to_datetime(dataset["date"], errors="coerce").max()
838
+ return {
839
+ "updated_rows": int(updated),
840
+ "latest_date": None if pd.isna(latest) else latest.date().isoformat(),
841
+ }
842
+
843
+
844
+ def refresh_market_close_data(session_date: date | None = None) -> dict[str, Any]:
845
+ now = datetime.now(IST)
846
+ session_date = session_date or now.date()
847
+ if not is_trading_day(session_date):
848
+ raise RuntimeError(f"{session_date.isoformat()} is not an NSE trading session.")
849
+ save_refresh_state(REFRESH_WAITING, session_date=session_date)
850
+ try:
851
+ save_refresh_state(REFRESH_REFRESHING, session_date=session_date)
852
+ minutes = fetch_yahoo_minutes(period="7d")
853
+ minute_frame = append_parquet_rows(NIFTY_1M_PATH, minutes, ["date"])
854
+ daily_info = refresh_daily_data()
855
+ t5_prediction = refresh_first5_prediction(session_date=session_date, minutes=minutes)
856
+ outcomes = update_opening_outcomes_from_daily()
857
+ tomorrow_prediction = refresh_tomorrow_prediction(session_date=session_date)
858
+ state = save_refresh_state(REFRESH_READY, session_date=session_date)
859
+ clear_dashboard_payload_cache()
860
+ return {
861
+ "session_date": session_date.isoformat(),
862
+ "nifty_1m_rows": int(len(minute_frame)),
863
+ "latest_minute": pd.to_datetime(minute_frame["date"], errors="coerce").max().isoformat(),
864
+ "daily": daily_info,
865
+ "opening_dataset": outcomes,
866
+ "t5_prediction": t5_prediction.to_dict(),
867
+ "tomorrow_prediction": tomorrow_prediction,
868
+ "refresh_state": state,
869
+ }
870
+ except Exception as exc:
871
+ save_refresh_state(REFRESH_FAILED, session_date=session_date, error=str(exc))
872
+ clear_dashboard_payload_cache()
873
+ raise
874
+
875
+
876
+ def close_refresh_due(now: datetime | None = None) -> bool:
877
+ now = now or datetime.now(IST)
878
+ if not is_trading_day(now.date()) or now.time() < CLOSE_REFRESH_READY:
879
+ return False
880
+ latest_daily = latest_parquet_date(NIFTY_1D_PATH)
881
+ latest_minutes = latest_parquet_date(NIFTY_1M_PATH)
882
+ latest_opening = latest_parquet_date(OPENING_DATASET_PATH)
883
+ latest_opening_outcome = latest_opening_outcome_date()
884
+ tomorrow_latest = latest_tomorrow_prediction()
885
+ tomorrow_input = None
886
+ try:
887
+ if tomorrow_latest.get("input_date"):
888
+ tomorrow_input = date.fromisoformat(str(tomorrow_latest.get("input_date"))[:10])
889
+ except Exception:
890
+ tomorrow_input = None
891
+ return any(
892
+ latest != now.date()
893
+ for latest in (latest_daily, latest_minutes, latest_opening, latest_opening_outcome, tomorrow_input)
894
+ )
895
+
896
+
897
  def next_ist_run_at(run_time: time = time(9, 20), now: datetime | None = None) -> datetime:
898
  now = now or datetime.now(IST)
899
  target_day = now.date()
scripts/__pycache__/refresh_daily_data.cpython-311.pyc ADDED
Binary file (937 Bytes). View file
 
scripts/__pycache__/refresh_first5_prediction.cpython-311.pyc ADDED
Binary file (1.84 kB). View file
 
scripts/__pycache__/run_ist_scheduler.cpython-311.pyc CHANGED
Binary files a/scripts/__pycache__/run_ist_scheduler.cpython-311.pyc and b/scripts/__pycache__/run_ist_scheduler.cpython-311.pyc differ
 
scripts/run_ist_scheduler.py CHANGED
@@ -8,8 +8,11 @@ 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,
@@ -40,12 +43,20 @@ def refresh_if_current_session_is_ready() -> None:
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()
@@ -62,6 +73,13 @@ def main() -> None:
62
  print(f"[scheduler] daily data refreshed: {info}")
63
  except Exception as exc:
64
  print(f"[scheduler] daily refresh failed: {exc}")
 
 
 
 
 
 
 
65
 
66
 
67
  if __name__ == "__main__":
 
8
 
9
  sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
10
  from nifty_backend.runtime import (
11
+ CLOSE_REFRESH_READY,
12
  is_trading_day,
13
  latest_saved_prediction,
14
+ close_refresh_due,
15
+ refresh_market_close_data,
16
  refresh_daily_data,
17
  refresh_first5_prediction,
18
  seconds_until_next_ist_run,
 
43
  print(f"[scheduler] daily data refreshed: {info}")
44
 
45
 
46
+ def refresh_close_data_if_due() -> None:
47
+ if not close_refresh_due():
48
+ return
49
+ info = refresh_market_close_data()
50
+ print(f"[scheduler] close data refreshed: {info}")
51
+
52
+
53
  def main() -> None:
54
  print("[scheduler] NIFTY first-five-minute scheduler started.")
55
  print("[scheduler] Runs the opening prediction after 09:20 IST so the 09:15-09:19 candles are complete.")
56
  while True:
57
  try:
58
  refresh_if_current_session_is_ready()
59
+ refresh_close_data_if_due()
60
  except Exception as exc:
61
  print(f"[scheduler] current-session refresh failed: {exc}")
62
  sleep_for = seconds_until_next_ist_run()
 
73
  print(f"[scheduler] daily data refreshed: {info}")
74
  except Exception as exc:
75
  print(f"[scheduler] daily refresh failed: {exc}")
76
+ sleep_for = seconds_until_next_ist_run(CLOSE_REFRESH_READY)
77
+ print(f"[scheduler] sleeping {sleep_for / 60:.1f} minutes until close refresh.")
78
+ time.sleep(sleep_for)
79
+ try:
80
+ refresh_close_data_if_due()
81
+ except Exception as exc:
82
+ print(f"[scheduler] close refresh failed: {exc}")
83
 
84
 
85
  if __name__ == "__main__":