Jitendra12421 commited on
Commit
60e4002
·
verified ·
1 Parent(s): c213323

Upload 35 files

Browse files
__pycache__/app.cpython-311.pyc CHANGED
Binary files a/__pycache__/app.cpython-311.pyc and b/__pycache__/app.cpython-311.pyc differ
 
app.py CHANGED
@@ -15,10 +15,12 @@ sys.path.insert(0, str(Path(__file__).resolve().parent))
15
  from nifty_backend.runtime import (
16
  CLOSE_REFRESH_READY,
17
  IST,
 
18
  close_refresh_due,
19
  dashboard_payload,
20
  is_trading_day,
21
  latest_saved_prediction,
 
22
  next_trading_day,
23
  refresh_daily_data,
24
  refresh_first5_prediction,
@@ -74,7 +76,13 @@ def current_market_state(now: datetime | None = None) -> dict:
74
  trading_day = is_trading_day(today)
75
  latest_date = latest_prediction_date()
76
  market_is_open_for_t5 = trading_day and FIRST5_READY <= current_time < MARKET_CLOSE
 
77
  has_current_first5 = market_is_open_for_t5 and latest_date == today
 
 
 
 
 
78
  next_session = today if trading_day and current_time < MARKET_CLOSE else next_trading_day(today + timedelta(days=1))
79
 
80
  if not trading_day:
@@ -113,6 +121,9 @@ def current_market_state(now: datetime | None = None) -> dict:
113
  "latest_prediction_date": latest_date.isoformat() if latest_date else None,
114
  "t5_available": has_current_first5,
115
  "market_is_open_for_t5": market_is_open_for_t5,
 
 
 
116
  }
117
 
118
 
@@ -123,7 +134,9 @@ def attach_market_state(payload: dict) -> dict:
123
 
124
  t5_latest = payload.get("latest") or {}
125
  tomorrow_latest = payload.get("tomorrow_latest") or {}
 
126
  t5_available = bool(state["t5_available"] and t5_latest.get("prediction"))
 
127
  market_closed = state["market_status"] == "Market Closed"
128
  unavailable_reason = "Market Closed" if market_closed else state["market_status"]
129
  tomorrow_available = bool(tomorrow_latest.get("prediction"))
@@ -163,6 +176,20 @@ def attach_market_state(payload: dict) -> dict:
163
  "validation_accuracy": (payload.get("summary") or {}).get("validation_accuracy"),
164
  "test_accuracy": (payload.get("summary") or {}).get("test_accuracy"),
165
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  }
167
  return payload
168
 
 
15
  from nifty_backend.runtime import (
16
  CLOSE_REFRESH_READY,
17
  IST,
18
+ TPLUS1_READY,
19
  close_refresh_due,
20
  dashboard_payload,
21
  is_trading_day,
22
  latest_saved_prediction,
23
+ latest_tplus1_prediction,
24
  next_trading_day,
25
  refresh_daily_data,
26
  refresh_first5_prediction,
 
76
  trading_day = is_trading_day(today)
77
  latest_date = latest_prediction_date()
78
  market_is_open_for_t5 = trading_day and FIRST5_READY <= current_time < MARKET_CLOSE
79
+ market_is_open_for_tplus1 = trading_day and TPLUS1_READY <= current_time < MARKET_CLOSE
80
  has_current_first5 = market_is_open_for_t5 and latest_date == today
81
+ try:
82
+ tplus1_latest_date = date.fromisoformat(str(latest_tplus1_prediction().get("input_date"))[:10])
83
+ except Exception:
84
+ tplus1_latest_date = None
85
+ has_current_tplus1 = market_is_open_for_tplus1 and tplus1_latest_date == today
86
  next_session = today if trading_day and current_time < MARKET_CLOSE else next_trading_day(today + timedelta(days=1))
87
 
88
  if not trading_day:
 
121
  "latest_prediction_date": latest_date.isoformat() if latest_date else None,
122
  "t5_available": has_current_first5,
123
  "market_is_open_for_t5": market_is_open_for_t5,
124
+ "tplus1_available": has_current_tplus1,
125
+ "market_is_open_for_tplus1": market_is_open_for_tplus1,
126
+ "latest_tplus1_prediction_date": tplus1_latest_date.isoformat() if tplus1_latest_date else None,
127
  }
128
 
129
 
 
134
 
135
  t5_latest = payload.get("latest") or {}
136
  tomorrow_latest = payload.get("tomorrow_latest") or {}
137
+ tplus1_latest = payload.get("tplus1_latest") or {}
138
  t5_available = bool(state["t5_available"] and t5_latest.get("prediction"))
139
+ tplus1_available = bool(state["tplus1_available"] and tplus1_latest.get("prediction"))
140
  market_closed = state["market_status"] == "Market Closed"
141
  unavailable_reason = "Market Closed" if market_closed else state["market_status"]
142
  tomorrow_available = bool(tomorrow_latest.get("prediction"))
 
176
  "validation_accuracy": (payload.get("summary") or {}).get("validation_accuracy"),
177
  "test_accuracy": (payload.get("summary") or {}).get("test_accuracy"),
178
  },
179
+ "tplus1": {
180
+ "available": tplus1_available,
181
+ "status": "Ready" if tplus1_available else unavailable_reason,
182
+ "reason": None if tplus1_available else state["market_detail"],
183
+ "target_date": tplus1_latest.get("target_date") or state["next_session_date"],
184
+ "input_date": tplus1_latest.get("input_date"),
185
+ "prediction": tplus1_latest.get("prediction") if tplus1_available else None,
186
+ "prob_up": tplus1_latest.get("prob_up") if tplus1_available else None,
187
+ "confidence": tplus1_latest.get("confidence") if tplus1_available else None,
188
+ "threshold": tplus1_latest.get("threshold") if tplus1_available else None,
189
+ "model_name": tplus1_latest.get("model_name"),
190
+ "validation_accuracy": (payload.get("tplus1_summary") or {}).get("validation_accuracy"),
191
+ "test_accuracy": (payload.get("tplus1_summary") or {}).get("test_accuracy"),
192
+ },
193
  }
194
  return payload
195
 
data/tplus1_test_predictions.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bd7f63239d5705969cbe5423169c9fdcb88d59b838b02913d0aa5c455a7ca41c
3
+ size 13681
models/nifty_1420_tplus1_logistic_model.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:77fea2ef4be30e6355377c1badadc94e9cb941fb62a31e731fad83b0f171c63e
3
+ size 5321
models/tplus1_latest_prediction.csv ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ input_date,target_date,forecast_for,prediction,prob_up,confidence,threshold,model_name,decision_overlay,validation_accuracy,test_accuracy,accuracy_goal
2
+ 2026-05-21,2026-05-22,next trading session after 2026-05-21,UP,0.620604654276822,0.620604654276822,0.578,logistic_regression_l1_C0.35_balanced,prev_target_mean10_le_0.4_up;m02_range_1m_ge_0.000479116_up,0.66,0.6368421052631579,0.63
models/tplus1_summary.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "target": "T+1 NIFTY 50 close greater than T 14:20 close",
3
+ "exchange_instrument": "NSE NIFTY 50 index",
4
+ "window_start": "14:00",
5
+ "window_end": "14:20",
6
+ "lookback_days_requested": 1000,
7
+ "supervised_rows": 1000,
8
+ "train_rows": 660,
9
+ "valid_rows": 150,
10
+ "test_rows": 190,
11
+ "train_start": "2022-04-28",
12
+ "train_end": "2024-12-31",
13
+ "valid_start": "2025-01-01",
14
+ "valid_end": "2025-08-06",
15
+ "test_start": "2025-08-07",
16
+ "test_end": "2026-05-20",
17
+ "model_name": "logistic_regression_l1_C0.35_balanced",
18
+ "threshold": 0.578,
19
+ "validation_accuracy": 0.66,
20
+ "test_accuracy": 0.6368421052631579,
21
+ "baseline_test_accuracy": 0.5052631578947369,
22
+ "validation_auc": 0.5085348506401137,
23
+ "test_auc": 0.54864804964539,
24
+ "test_log_loss": 0.715200083567372,
25
+ "test_brier": 0.2580990249203714,
26
+ "accuracy_goal": 0.63,
27
+ "accuracy_goal_met_on_test": true,
28
+ "feature_count": 40,
29
+ "latest_input_date": "2026-05-21",
30
+ "latest_window_rows": 21,
31
+ "latest_forecast_for": "next trading session after 2026-05-21",
32
+ "latest_prob_up": 0.620604654276822,
33
+ "latest_prediction": "UP",
34
+ "latest_confidence": 0.620604654276822,
35
+ "decision_overlay": "prev_target_mean10_le_0.4_up;m02_range_1m_ge_0.000479116_up",
36
+ "top_features": 40
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
@@ -26,6 +26,7 @@ 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"
@@ -39,6 +40,10 @@ 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"
@@ -522,6 +527,194 @@ def latest_tomorrow_prediction() -> dict[str, Any]:
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)
@@ -630,6 +823,16 @@ def load_tomorrow_test_predictions() -> pd.DataFrame:
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"),
@@ -639,6 +842,10 @@ def dashboard_payload() -> dict[str, Any]:
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),
@@ -659,9 +866,12 @@ def _dashboard_payload_cached(key: tuple[tuple[str, int | None, int | None], ...
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)
@@ -706,6 +916,16 @@ def _dashboard_payload_cached(key: tuple[tuple[str, int | None, int | None], ...
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",
@@ -733,9 +953,11 @@ def _dashboard_payload_cached(key: tuple[tuple[str, int | None, int | None], ...
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"]]),
@@ -745,6 +967,7 @@ def _dashboard_payload_cached(key: tuple[tuple[str, int | None, int | None], ...
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"]))),
@@ -752,6 +975,7 @@ def _dashboard_payload_cached(key: tuple[tuple[str, int | None, int | None], ...
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,
@@ -853,6 +1077,7 @@ def refresh_market_close_data(session_date: date | None = None) -> dict[str, Any
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)
@@ -864,6 +1089,7 @@ def refresh_market_close_data(session_date: date | None = None) -> dict[str, Any
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
  }
 
26
  YAHOO_NIFTY_SYMBOL = "^NSEI"
27
  MARKET_CLOSE = time(15, 30)
28
  CLOSE_REFRESH_READY = time(15, 45)
29
+ TPLUS1_READY = time(14, 30)
30
  BACKEND_ROOT = Path(__file__).resolve().parents[1]
31
  DATA_DIR = BACKEND_ROOT / "data"
32
  MODEL_DIR = BACKEND_ROOT / "models"
 
40
  TOMORROW_LATEST_PATH = MODEL_DIR / "tomorrow_latest_prediction.csv"
41
  TOMORROW_SUMMARY_PATH = MODEL_DIR / "tomorrow_summary.json"
42
  TOMORROW_TEST_PREDICTIONS_PATH = DATA_DIR / "tomorrow_test_predictions.parquet"
43
+ TPLUS1_MODEL_PATH = MODEL_DIR / "nifty_1420_tplus1_logistic_model.joblib"
44
+ TPLUS1_LATEST_PATH = MODEL_DIR / "tplus1_latest_prediction.csv"
45
+ TPLUS1_SUMMARY_PATH = MODEL_DIR / "tplus1_summary.json"
46
+ TPLUS1_TEST_PREDICTIONS_PATH = DATA_DIR / "tplus1_test_predictions.parquet"
47
  REFRESH_STATE_PATH = MODEL_DIR / "refresh_state.json"
48
  REFRESH_WAITING = "waiting_second_payload"
49
  REFRESH_REFRESHING = "refreshing"
 
527
  }
528
 
529
 
530
+ def load_tplus1_summary() -> dict[str, Any]:
531
+ if TPLUS1_SUMMARY_PATH.exists():
532
+ return json.loads(TPLUS1_SUMMARY_PATH.read_text(encoding="utf-8"))
533
+ return {
534
+ "model_name": "logistic_regression_l1_C0.35_balanced",
535
+ "target": "T+1 NIFTY 50 close greater than T 14:20 close",
536
+ "window_start": "14:00",
537
+ "window_end": "14:20",
538
+ "threshold": 0.578,
539
+ "validation_accuracy": 0.66,
540
+ "test_accuracy": 0.6368421052631579,
541
+ "baseline_test_accuracy": 0.5052631578947369,
542
+ "test_rows": 190,
543
+ "feature_count": 40,
544
+ }
545
+
546
+
547
+ def latest_tplus1_prediction() -> dict[str, Any]:
548
+ if TPLUS1_LATEST_PATH.exists():
549
+ row = pd.read_csv(TPLUS1_LATEST_PATH).iloc[-1].to_dict()
550
+ return {k: (None if pd.isna(v) else v) for k, v in row.items()}
551
+ summary = load_tplus1_summary()
552
+ return {
553
+ "input_date": summary.get("latest_input_date"),
554
+ "target_date": None,
555
+ "forecast_for": summary.get("latest_forecast_for"),
556
+ "prediction": summary.get("latest_prediction"),
557
+ "prob_up": summary.get("latest_prob_up"),
558
+ "confidence": summary.get("latest_confidence"),
559
+ "threshold": summary.get("threshold"),
560
+ "model_name": summary.get("model_name", "logistic_regression_l1_C0.35_balanced"),
561
+ "validation_accuracy": summary.get("validation_accuracy"),
562
+ "test_accuracy": summary.get("test_accuracy"),
563
+ }
564
+
565
+
566
+ def _minute_frame_for_tplus1() -> pd.DataFrame:
567
+ minute = pd.read_parquet(NIFTY_1M_PATH)
568
+ minute = minute.copy()
569
+ minute["dt"] = pd.to_datetime(minute["date"], errors="coerce")
570
+ for col in ("open", "high", "low", "close", "volume"):
571
+ if col in minute.columns:
572
+ minute[col] = pd.to_numeric(minute[col], errors="coerce")
573
+ minute = minute.dropna(subset=["dt", "open", "high", "low", "close"]).sort_values("dt").reset_index(drop=True)
574
+ minute["session_date"] = minute["dt"].dt.normalize()
575
+ minute["time"] = minute["dt"].dt.strftime("%H:%M")
576
+ return minute
577
+
578
+
579
+ def _build_tplus1_session_features(minute: pd.DataFrame) -> pd.DataFrame:
580
+ window = minute[(minute["time"] >= "14:00") & (minute["time"] <= "14:20")].copy()
581
+ window["minute_offset"] = window.groupby("session_date", sort=True).cumcount()
582
+ grouped = window.groupby("session_date", sort=True)
583
+ base = grouped.agg(
584
+ window_start=("dt", "first"),
585
+ window_end=("dt", "last"),
586
+ window_rows=("close", "size"),
587
+ w_open=("open", "first"),
588
+ w_high=("high", "max"),
589
+ w_low=("low", "min"),
590
+ w_close=("close", "last"),
591
+ w_volume=("volume", "sum") if "volume" in window.columns else ("close", "size"),
592
+ ).reset_index().rename(columns={"session_date": "date"})
593
+ base = base[base["window_rows"] == 21].copy()
594
+ base["w_return"] = safe_div(base["w_close"] - base["w_open"], base["w_open"])
595
+ base["w_range"] = safe_div(base["w_high"] - base["w_low"], base["w_open"])
596
+ base["w_body_to_range"] = safe_div(base["w_close"] - base["w_open"], base["w_high"] - base["w_low"])
597
+ base["w_close_location"] = safe_div(base["w_close"] - base["w_low"], base["w_high"] - base["w_low"])
598
+ window["ret_1m"] = window.groupby("session_date")["close"].pct_change(fill_method=None)
599
+ window["range_1m"] = safe_div(window["high"] - window["low"], window["open"])
600
+ window["body_1m"] = safe_div(window["close"] - window["open"], window["open"])
601
+ minute_features = window.pivot(
602
+ index="session_date",
603
+ columns="minute_offset",
604
+ values=["open", "high", "low", "close", "ret_1m", "range_1m", "body_1m"],
605
+ )
606
+ minute_features.columns = [f"m{int(offset):02d}_{field}" for field, offset in minute_features.columns]
607
+ minute_features = minute_features.reset_index().rename(columns={"session_date": "date"})
608
+ session_close = (
609
+ minute.groupby("session_date", sort=True)
610
+ .agg(day_close=("close", "last"))
611
+ .reset_index()
612
+ .rename(columns={"session_date": "date"})
613
+ )
614
+ frame = base.merge(minute_features, on="date", how="left").merge(session_close, on="date", how="left")
615
+ for offset in range(21):
616
+ close_col = f"m{offset:02d}_close"
617
+ open_col = f"m{offset:02d}_open"
618
+ if close_col in frame.columns:
619
+ frame[f"m{offset:02d}_close_vs_window_open"] = safe_div(frame[close_col] - frame["w_open"], frame["w_open"])
620
+ if open_col in frame.columns and close_col in frame.columns:
621
+ frame[f"m{offset:02d}_close_vs_minute_open"] = safe_div(frame[close_col] - frame[open_col], frame[open_col])
622
+ frame["ret_first_5m"] = safe_div(frame["m04_close"] - frame["m00_open"], frame["m00_open"])
623
+ frame["ret_last_5m"] = safe_div(frame["m20_close"] - frame["m16_open"], frame["m16_open"])
624
+ frame["ret_mid_11m"] = safe_div(frame["m15_close"] - frame["m05_open"], frame["m05_open"])
625
+ frame["last5_minus_first5"] = frame["ret_last_5m"] - frame["ret_first_5m"]
626
+ frame["abs_window_return"] = frame["w_return"].abs()
627
+ frame["dow"] = frame["date"].dt.dayofweek
628
+ frame["dom"] = frame["date"].dt.day
629
+ frame["month"] = frame["date"].dt.month
630
+ return frame.sort_values("date").reset_index(drop=True)
631
+
632
+
633
+ def _add_tplus1_target_features(features: pd.DataFrame) -> pd.DataFrame:
634
+ frame = features.copy()
635
+ frame["target_date"] = frame["date"].shift(-1)
636
+ frame["target_close"] = frame["day_close"].shift(-1)
637
+ frame["target_return_from_1420"] = safe_div(frame["target_close"] - frame["w_close"], frame["w_close"])
638
+ frame["target"] = (frame["target_return_from_1420"] > 0).astype("float64")
639
+ frame.loc[frame["target_close"].isna(), "target"] = np.nan
640
+ for lag in (1, 2, 3, 5, 10):
641
+ frame[f"prev_target_lag{lag}"] = frame["target"].shift(lag)
642
+ frame[f"prev_target_return_lag{lag}"] = frame["target_return_from_1420"].shift(lag)
643
+ for window in (3, 5, 10, 20, 40):
644
+ min_periods = max(2, window // 2)
645
+ frame[f"prev_target_mean{window}"] = frame["target"].shift(1).rolling(window, min_periods=min_periods).mean()
646
+ shifted_return = frame["target_return_from_1420"].shift(1)
647
+ frame[f"prev_target_return_mean{window}"] = shifted_return.rolling(window, min_periods=min_periods).mean()
648
+ frame[f"prev_target_return_std{window}"] = shifted_return.rolling(window, min_periods=min_periods).std()
649
+ return frame
650
+
651
+
652
+ def _apply_tplus1_overlays(pred: np.ndarray, frame: pd.DataFrame, overlays: list[dict[str, Any]]) -> np.ndarray:
653
+ adjusted = np.asarray(pred, dtype="int64").copy()
654
+ for overlay in overlays:
655
+ feature = str(overlay.get("feature", ""))
656
+ if feature not in frame.columns:
657
+ continue
658
+ series = pd.to_numeric(frame[feature], errors="coerce")
659
+ value = float(overlay.get("value", 0.0))
660
+ if overlay.get("op") == "<=":
661
+ mask = (series <= value).fillna(False).to_numpy(dtype=bool)
662
+ else:
663
+ mask = (series >= value).fillna(False).to_numpy(dtype=bool)
664
+ action = overlay.get("action")
665
+ if action == "up":
666
+ adjusted[mask] = 1
667
+ elif action == "down":
668
+ adjusted[mask] = 0
669
+ elif action == "flip":
670
+ adjusted[mask] = 1 - adjusted[mask]
671
+ return adjusted
672
+
673
+
674
+ def refresh_tplus1_prediction(session_date: date | None = None) -> dict[str, Any]:
675
+ if not TPLUS1_MODEL_PATH.exists():
676
+ raise FileNotFoundError(f"Missing T+1 model artifact: {TPLUS1_MODEL_PATH}")
677
+ payload = joblib.load(TPLUS1_MODEL_PATH)
678
+ features = payload["features"]
679
+ threshold = float(payload["threshold"])
680
+ frame = _add_tplus1_target_features(_build_tplus1_session_features(_minute_frame_for_tplus1()))
681
+ if session_date is not None:
682
+ row = frame[pd.to_datetime(frame["date"], errors="coerce").dt.date == session_date].tail(1)
683
+ else:
684
+ row = frame.tail(1)
685
+ if row.empty:
686
+ raise RuntimeError("No complete 14:00-14:20 window is available for T+1 prediction.")
687
+ missing = [col for col in features if col not in row.columns]
688
+ if missing:
689
+ raise RuntimeError(f"T+1 feature row is missing model features: {missing[:5]}")
690
+ prob_up = predict_proba_up(payload["model"], row[features])
691
+ raw_pred = (prob_up >= threshold).astype("int64")
692
+ overlay_payload = payload.get("decision_overlay")
693
+ overlays = overlay_payload.get("overlays", []) if isinstance(overlay_payload, dict) else []
694
+ pred_int = int(_apply_tplus1_overlays(raw_pred, row, overlays)[0])
695
+ prediction = "UP" if pred_int == 1 else "DOWN"
696
+ input_day = pd.to_datetime(row["date"].iloc[0]).date()
697
+ target_day = next_trading_day(input_day + timedelta(days=1))
698
+ summary = load_tplus1_summary()
699
+ out = {
700
+ "input_date": input_day.isoformat(),
701
+ "target_date": target_day.isoformat(),
702
+ "forecast_for": f"next trading session after {input_day.isoformat()}",
703
+ "prediction": prediction,
704
+ "prob_up": float(prob_up[0]),
705
+ "confidence": float(max(prob_up[0], 1.0 - prob_up[0])),
706
+ "threshold": threshold,
707
+ "model_name": str(payload.get("model_name", summary.get("model_name", "nifty_1420_tplus1_logistic_model"))),
708
+ "decision_overlay": summary.get("decision_overlay"),
709
+ "validation_accuracy": summary.get("validation_accuracy"),
710
+ "test_accuracy": summary.get("test_accuracy"),
711
+ "accuracy_goal": summary.get("accuracy_goal"),
712
+ }
713
+ pd.DataFrame([out]).to_csv(TPLUS1_LATEST_PATH, index=False)
714
+ clear_dashboard_payload_cache()
715
+ return out
716
+
717
+
718
  def _tomorrow_probability_from_daily(daily: pd.DataFrame, fallback_prob: float) -> float:
719
  if daily.empty or len(daily) < 5:
720
  return float(fallback_prob)
 
823
  return df.sort_values(sort_col).reset_index(drop=True)
824
 
825
 
826
+ def load_tplus1_test_predictions() -> pd.DataFrame:
827
+ if not TPLUS1_TEST_PREDICTIONS_PATH.exists():
828
+ return pd.DataFrame()
829
+ df = pd.read_parquet(TPLUS1_TEST_PREDICTIONS_PATH)
830
+ for col in ("date", "target_date"):
831
+ if col in df.columns:
832
+ df[col] = pd.to_datetime(df[col], errors="coerce")
833
+ return df.sort_values("date").reset_index(drop=True)
834
+
835
+
836
  def dashboard_payload() -> dict[str, Any]:
837
  key = (
838
  _file_cache_key(MODEL_DIR / "summary.json"),
 
842
  _file_cache_key(TOMORROW_LATEST_PATH),
843
  _file_cache_key(TOMORROW_TEST_PREDICTIONS_PATH),
844
  _file_cache_key(TOMORROW_MODEL_PATH),
845
+ _file_cache_key(TPLUS1_SUMMARY_PATH),
846
+ _file_cache_key(TPLUS1_LATEST_PATH),
847
+ _file_cache_key(TPLUS1_TEST_PREDICTIONS_PATH),
848
+ _file_cache_key(TPLUS1_MODEL_PATH),
849
  _file_cache_key(REFRESH_STATE_PATH),
850
  _file_cache_key(NIFTY_1D_PATH),
851
  _file_cache_key(OPENING_DATASET_PATH),
 
866
  t5_latest = _latest_saved_prediction_uncached()
867
  tomorrow_summary = load_tomorrow_summary()
868
  tomorrow_latest = latest_tomorrow_prediction()
869
+ tplus1_summary = load_tplus1_summary()
870
+ tplus1_latest = latest_tplus1_prediction()
871
  refresh_state = load_refresh_state()
872
  t5_test = load_test_predictions()
873
  tomorrow_test = load_tomorrow_test_predictions()
874
+ tplus1_test = load_tplus1_test_predictions()
875
  daily = pd.read_parquet(NIFTY_1D_PATH)
876
  daily["date"] = pd.to_datetime(daily["date"], errors="coerce")
877
  daily = daily.sort_values("date").tail(180)
 
916
  "recent_accuracy": tomorrow_accuracy,
917
  "test_rows": int(tomorrow_summary.get("n_test") or len(tomorrow_test) or 0),
918
  },
919
+ {
920
+ "id": "tplus1",
921
+ "label": "T+1",
922
+ "model_name": tplus1_summary.get("model_name", "nifty_1420_tplus1_logistic_model"),
923
+ "source_model": "14:00-14:20 logistic forecaster",
924
+ "validation_accuracy": tplus1_summary.get("validation_accuracy"),
925
+ "test_accuracy": tplus1_summary.get("test_accuracy"),
926
+ "recent_accuracy": float(tplus1_test.tail(40)["correct"].mean()) if not tplus1_test.empty and "correct" in tplus1_test.columns else tplus1_summary.get("test_accuracy"),
927
+ "test_rows": int(tplus1_summary.get("test_rows") or len(tplus1_test) or 0),
928
+ },
929
  {
930
  "id": "t5",
931
  "label": "T+5",
 
953
  return {
954
  "latest": t5_latest,
955
  "tomorrow_latest": tomorrow_latest,
956
+ "tplus1_latest": tplus1_latest,
957
  "metrics": metrics,
958
  "summary": summary,
959
  "tomorrow_summary": tomorrow_summary,
960
+ "tplus1_summary": tplus1_summary,
961
  "candidates": load_candidate_results(),
962
  "charts": {
963
  "daily_close": _json_ready_frame(daily[["date", "open", "high", "low", "close"]]),
 
967
  "recent_predictions": _json_ready_frame(recent_predictions),
968
  "t5_recent_predictions": _json_ready_frame(recent_predictions),
969
  "tomorrow_recent_predictions": _json_ready_frame(tomorrow_recent),
970
+ "tplus1_recent_predictions": _json_ready_frame(tplus1_test.tail(40)),
971
  },
972
  "data_status": {
973
  "nifty_1m_rows": int(len(pd.read_parquet(NIFTY_1M_PATH, columns=["date"]))),
 
975
  "training_rows": int(len(dataset)),
976
  "test_prediction_rows": int(len(t5_test)),
977
  "tomorrow_test_prediction_rows": int(len(tomorrow_test)),
978
+ "tplus1_test_prediction_rows": int(len(tplus1_test)),
979
  "latest_daily_date": pd.to_datetime(daily["date"]).max().date().isoformat(),
980
  "refresh_phase": refresh_state.get("phase", REFRESH_NORMAL),
981
  "refresh_state": refresh_state,
 
1077
  minute_frame = append_parquet_rows(NIFTY_1M_PATH, minutes, ["date"])
1078
  daily_info = refresh_daily_data()
1079
  t5_prediction = refresh_first5_prediction(session_date=session_date, minutes=minutes)
1080
+ tplus1_prediction = refresh_tplus1_prediction(session_date=session_date)
1081
  outcomes = update_opening_outcomes_from_daily()
1082
  tomorrow_prediction = refresh_tomorrow_prediction(session_date=session_date)
1083
  state = save_refresh_state(REFRESH_READY, session_date=session_date)
 
1089
  "daily": daily_info,
1090
  "opening_dataset": outcomes,
1091
  "t5_prediction": t5_prediction.to_dict(),
1092
+ "tplus1_prediction": tplus1_prediction,
1093
  "tomorrow_prediction": tomorrow_prediction,
1094
  "refresh_state": state,
1095
  }