Spaces:
Runtime error
Runtime error
Upload 31 files
Browse files- __pycache__/app.cpython-311.pyc +0 -0
- models/nifty_tomorrow_direction_model.joblib +3 -0
- models/refresh_state.json +7 -0
- models/tomorrow_latest_prediction.csv +2 -0
- models/tomorrow_summary.json +37 -0
- nifty_backend/__pycache__/runtime.cpython-311.pyc +0 -0
- nifty_backend/runtime.py +391 -18
- scripts/__pycache__/refresh_daily_data.cpython-311.pyc +0 -0
- scripts/__pycache__/refresh_first5_prediction.cpython-311.pyc +0 -0
- scripts/__pycache__/run_ist_scheduler.cpython-311.pyc +0 -0
- scripts/run_ist_scheduler.py +18 -0
__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 |
-
|
| 441 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 449 |
-
recent_predictions =
|
| 450 |
recent_accuracy = float(recent_predictions["correct"].mean())
|
| 451 |
-
direction_mix =
|
| 452 |
monthly = (
|
| 453 |
-
|
| 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":
|
| 466 |
-
"test_accuracy":
|
| 467 |
-
"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":
|
| 472 |
-
"recent_accuracy":
|
| 473 |
-
"recent_accuracy_days": int(len(
|
| 474 |
-
"total_test_days": int(
|
|
|
|
| 475 |
}
|
| 476 |
return {
|
| 477 |
-
"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(
|
|
|
|
| 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="
|
| 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 |
-
|
|
|
|
|
|
|
| 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__":
|