fix-dashboard-ui-backend
Browse files- .hfignore +9 -0
- __pycache__/app.cpython-311.pyc +0 -0
- __pycache__/kotak_neo.cpython-311.pyc +0 -0
- data/nifty50_1d.parquet +2 -2
- data/nifty50_1m.parquet +2 -2
- data/opening_direction_training_dataset.parquet +2 -2
- kotak_neo.py +1 -1
- models/latest_prediction.csv +1 -1
- models/yahoo_history_cache.sqlite3 +2 -2
- nifty_backend/__pycache__/runtime.cpython-311.pyc +2 -2
- nifty_backend/runtime.py +130 -0
.hfignore
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
.pytest_cache/
|
| 4 |
+
.mypy_cache/
|
| 5 |
+
.ruff_cache/
|
| 6 |
+
.venv/
|
| 7 |
+
venv/
|
| 8 |
+
.env
|
| 9 |
+
*.log
|
__pycache__/app.cpython-311.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-311.pyc and b/__pycache__/app.cpython-311.pyc differ
|
|
|
__pycache__/kotak_neo.cpython-311.pyc
CHANGED
|
Binary files a/__pycache__/kotak_neo.cpython-311.pyc and b/__pycache__/kotak_neo.cpython-311.pyc differ
|
|
|
data/nifty50_1d.parquet
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e9a3425f3bf8271fb9002f3f770dc8607e401e9e8e5b0066495fe73092210242
|
| 3 |
+
size 78452
|
data/nifty50_1m.parquet
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9df0c2b2820487db987459070243b498551961894fc49bb55f467d1b0466f2f2
|
| 3 |
+
size 18609051
|
data/opening_direction_training_dataset.parquet
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6fa5d8cf0e098429ce3b3c4739b498a5bed3884cd1dbe6c4fda97b4600a26474
|
| 3 |
+
size 4464130
|
kotak_neo.py
CHANGED
|
@@ -439,7 +439,7 @@ class KotakNeoManager:
|
|
| 439 |
"status": self.status(),
|
| 440 |
"as_of": _utc_now_iso(),
|
| 441 |
"neo_behavior": {
|
| 442 |
-
"holdings_note": "
|
| 443 |
"trade_history_note": "Kotak Neo trade history availability is limited by Neo's own order and portfolio tracker behavior.",
|
| 444 |
},
|
| 445 |
"summary": {
|
|
|
|
| 439 |
"status": self.status(),
|
| 440 |
"as_of": _utc_now_iso(),
|
| 441 |
"neo_behavior": {
|
| 442 |
+
"holdings_note": "",
|
| 443 |
"trade_history_note": "Kotak Neo trade history availability is limited by Neo's own order and portfolio tracker behavior.",
|
| 444 |
},
|
| 445 |
"summary": {
|
models/latest_prediction.csv
CHANGED
|
@@ -1,2 +1,2 @@
|
|
| 1 |
input_date,first5_start,first5_end,prediction,prob_up,confidence,threshold,model_name,is_overridden
|
| 2 |
-
2026-
|
|
|
|
| 1 |
input_date,first5_start,first5_end,prediction,prob_up,confidence,threshold,model_name,is_overridden
|
| 2 |
+
2026-06-02,2026-06-02 09:15:00,2026-06-02 09:19:00,DOWN,0.6609583467127472,0.7359583467127473,0.425,blend_extra_trees_tight_logit_overlay,True
|
models/yahoo_history_cache.sqlite3
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1dcb91ddef71b32d679585287872891cb4f4ddc992b8d3c3659989829323a454
|
| 3 |
+
size 225280
|
nifty_backend/__pycache__/runtime.cpython-311.pyc
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:89f2a00f920ffc636fbdd9b8b9b0fb6c6cbe955a5de208b7879183571d861e84
|
| 3 |
+
size 112925
|
nifty_backend/runtime.py
CHANGED
|
@@ -64,6 +64,7 @@ REFRESH_REFRESHING = "refreshing"
|
|
| 64 |
REFRESH_READY = "ready"
|
| 65 |
REFRESH_FAILED = "failed"
|
| 66 |
REFRESH_NORMAL = "normal"
|
|
|
|
| 67 |
|
| 68 |
DECISION_OVERLAYS = [
|
| 69 |
{
|
|
@@ -1094,6 +1095,7 @@ def dashboard_payload() -> dict[str, Any]:
|
|
| 1094 |
_file_cache_key(OPENING_DATASET_PATH),
|
| 1095 |
_file_cache_key(MODEL_DIR / "candidate_results.csv"),
|
| 1096 |
_file_cache_key(NIFTY_1M_PATH),
|
|
|
|
| 1097 |
)
|
| 1098 |
with _dashboard_payload_lock:
|
| 1099 |
return copy.deepcopy(_dashboard_payload_cached(key))
|
|
@@ -1197,6 +1199,7 @@ def _dashboard_payload_cached(key: tuple[tuple[str, int | None, int | None], ...
|
|
| 1197 |
"latest": t5_latest,
|
| 1198 |
"tomorrow_latest": tomorrow_latest,
|
| 1199 |
"tplus1_latest": tplus1_latest,
|
|
|
|
| 1200 |
"metrics": metrics,
|
| 1201 |
"summary": summary,
|
| 1202 |
"tomorrow_summary": tomorrow_summary,
|
|
@@ -1308,6 +1311,128 @@ def update_opening_outcomes_from_daily() -> dict[str, Any]:
|
|
| 1308 |
}
|
| 1309 |
|
| 1310 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1311 |
def refresh_market_close_data(session_date: date | None = None) -> dict[str, Any]:
|
| 1312 |
now = datetime.now(IST)
|
| 1313 |
session_date = session_date or now.date()
|
|
@@ -1319,6 +1444,11 @@ def refresh_market_close_data(session_date: date | None = None) -> dict[str, Any
|
|
| 1319 |
minutes = fetch_yahoo_minutes(period="7d")
|
| 1320 |
minute_frame = append_parquet_rows(NIFTY_1M_PATH, minutes, ["date"])
|
| 1321 |
daily_info = refresh_daily_data()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1322 |
t5_prediction = refresh_first5_prediction(session_date=session_date, minutes=minutes)
|
| 1323 |
tplus1_prediction = refresh_tplus1_prediction(session_date=session_date)
|
| 1324 |
outcomes = update_opening_outcomes_from_daily()
|
|
|
|
| 64 |
REFRESH_READY = "ready"
|
| 65 |
REFRESH_FAILED = "failed"
|
| 66 |
REFRESH_NORMAL = "normal"
|
| 67 |
+
LIVE_ACCURACY_PATH = MODEL_DIR / "live_accuracy.json"
|
| 68 |
|
| 69 |
DECISION_OVERLAYS = [
|
| 70 |
{
|
|
|
|
| 1095 |
_file_cache_key(OPENING_DATASET_PATH),
|
| 1096 |
_file_cache_key(MODEL_DIR / "candidate_results.csv"),
|
| 1097 |
_file_cache_key(NIFTY_1M_PATH),
|
| 1098 |
+
_file_cache_key(LIVE_ACCURACY_PATH),
|
| 1099 |
)
|
| 1100 |
with _dashboard_payload_lock:
|
| 1101 |
return copy.deepcopy(_dashboard_payload_cached(key))
|
|
|
|
| 1199 |
"latest": t5_latest,
|
| 1200 |
"tomorrow_latest": tomorrow_latest,
|
| 1201 |
"tplus1_latest": tplus1_latest,
|
| 1202 |
+
"live_accuracy": load_live_accuracy(),
|
| 1203 |
"metrics": metrics,
|
| 1204 |
"summary": summary,
|
| 1205 |
"tomorrow_summary": tomorrow_summary,
|
|
|
|
| 1311 |
}
|
| 1312 |
|
| 1313 |
|
| 1314 |
+
def load_live_accuracy() -> dict[str, Any]:
|
| 1315 |
+
"""Load the live accuracy ledger from disk."""
|
| 1316 |
+
if LIVE_ACCURACY_PATH.exists():
|
| 1317 |
+
try:
|
| 1318 |
+
return json.loads(LIVE_ACCURACY_PATH.read_text(encoding="utf-8"))
|
| 1319 |
+
except Exception:
|
| 1320 |
+
pass
|
| 1321 |
+
return {
|
| 1322 |
+
"tomorrow": {"entries": [], "accuracy": None, "total": 0, "correct_count": 0},
|
| 1323 |
+
"t5": {"entries": [], "accuracy": None, "total": 0, "correct_count": 0},
|
| 1324 |
+
"tplus1": {"entries": [], "accuracy": None, "total": 0, "correct_count": 0},
|
| 1325 |
+
}
|
| 1326 |
+
|
| 1327 |
+
|
| 1328 |
+
def save_live_accuracy(data: dict[str, Any]) -> None:
|
| 1329 |
+
"""Persist the live accuracy ledger to disk."""
|
| 1330 |
+
LIVE_ACCURACY_PATH.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
| 1331 |
+
|
| 1332 |
+
|
| 1333 |
+
def update_live_accuracy(session_date: date) -> dict[str, Any]:
|
| 1334 |
+
"""Score today's predictions against actual outcomes and update the ledger.
|
| 1335 |
+
|
| 1336 |
+
Must be called AFTER refresh_daily_data() (so today's close is available)
|
| 1337 |
+
but BEFORE refresh_first5_prediction / refresh_tplus1_prediction /
|
| 1338 |
+
refresh_tomorrow_prediction (so the CSV files still hold the predictions
|
| 1339 |
+
we want to score).
|
| 1340 |
+
"""
|
| 1341 |
+
ledger = load_live_accuracy()
|
| 1342 |
+
daily = pd.read_parquet(NIFTY_1D_PATH)
|
| 1343 |
+
daily["_date"] = pd.to_datetime(daily["date"], errors="coerce").dt.normalize()
|
| 1344 |
+
today_rows = daily[daily["_date"].dt.date == session_date]
|
| 1345 |
+
if today_rows.empty:
|
| 1346 |
+
return ledger
|
| 1347 |
+
|
| 1348 |
+
day_open = float(today_rows.iloc[-1]["open"])
|
| 1349 |
+
day_close = float(today_rows.iloc[-1]["close"])
|
| 1350 |
+
if not (np.isfinite(day_open) and np.isfinite(day_close) and day_open != 0):
|
| 1351 |
+
return ledger
|
| 1352 |
+
actual_close_gt_open = "UP" if day_close > day_open else "DOWN"
|
| 1353 |
+
session_iso = session_date.isoformat()
|
| 1354 |
+
|
| 1355 |
+
# --- T+5: today's 9:20 AM prediction vs close > open ---
|
| 1356 |
+
logged_t5 = {e["date"] for e in ledger["t5"]["entries"]}
|
| 1357 |
+
if session_iso not in logged_t5 and LATEST_PATH.exists():
|
| 1358 |
+
try:
|
| 1359 |
+
t5_row = pd.read_csv(LATEST_PATH).iloc[-1].to_dict()
|
| 1360 |
+
if str(t5_row.get("input_date", ""))[:10] == session_iso:
|
| 1361 |
+
pred = str(t5_row.get("prediction", "")).upper()
|
| 1362 |
+
if pred in ("UP", "DOWN"):
|
| 1363 |
+
ledger["t5"]["entries"].append({
|
| 1364 |
+
"date": session_iso,
|
| 1365 |
+
"prediction": pred,
|
| 1366 |
+
"actual": actual_close_gt_open,
|
| 1367 |
+
"correct": pred == actual_close_gt_open,
|
| 1368 |
+
})
|
| 1369 |
+
except Exception:
|
| 1370 |
+
pass
|
| 1371 |
+
|
| 1372 |
+
# --- Tomorrow: yesterday's prediction targeting today vs close > open ---
|
| 1373 |
+
logged_tom = {e["date"] for e in ledger["tomorrow"]["entries"]}
|
| 1374 |
+
if session_iso not in logged_tom and TOMORROW_LATEST_PATH.exists():
|
| 1375 |
+
try:
|
| 1376 |
+
tom_row = pd.read_csv(TOMORROW_LATEST_PATH).iloc[-1].to_dict()
|
| 1377 |
+
if str(tom_row.get("target_date", ""))[:10] == session_iso:
|
| 1378 |
+
pred = str(tom_row.get("prediction", "")).upper()
|
| 1379 |
+
if pred in ("UP", "DOWN"):
|
| 1380 |
+
ledger["tomorrow"]["entries"].append({
|
| 1381 |
+
"date": session_iso,
|
| 1382 |
+
"prediction": pred,
|
| 1383 |
+
"actual": actual_close_gt_open,
|
| 1384 |
+
"correct": pred == actual_close_gt_open,
|
| 1385 |
+
})
|
| 1386 |
+
except Exception:
|
| 1387 |
+
pass
|
| 1388 |
+
|
| 1389 |
+
# --- T+1: yesterday's 14:20 prediction targeting today ---
|
| 1390 |
+
# T+1 target: today's close > yesterday's 14:20 close
|
| 1391 |
+
logged_t1 = {e["date"] for e in ledger["tplus1"]["entries"]}
|
| 1392 |
+
if session_iso not in logged_t1 and TPLUS1_LATEST_PATH.exists():
|
| 1393 |
+
try:
|
| 1394 |
+
t1_row = pd.read_csv(TPLUS1_LATEST_PATH).iloc[-1].to_dict()
|
| 1395 |
+
if str(t1_row.get("target_date", ""))[:10] == session_iso:
|
| 1396 |
+
pred = str(t1_row.get("prediction", "")).upper()
|
| 1397 |
+
input_date_str = str(t1_row.get("input_date", ""))[:10]
|
| 1398 |
+
input_day = date.fromisoformat(input_date_str)
|
| 1399 |
+
# Read the 14:20 close from minute data for the input session
|
| 1400 |
+
minute = pd.read_parquet(NIFTY_1M_PATH, columns=["date", "close"])
|
| 1401 |
+
minute["dt"] = pd.to_datetime(minute["date"], errors="coerce")
|
| 1402 |
+
minute = minute.dropna(subset=["dt"])
|
| 1403 |
+
minute["session_date"] = minute["dt"].dt.normalize()
|
| 1404 |
+
minute["time_str"] = minute["dt"].dt.strftime("%H:%M")
|
| 1405 |
+
window = minute[
|
| 1406 |
+
(minute["session_date"].dt.date == input_day)
|
| 1407 |
+
& (minute["time_str"] >= "14:00")
|
| 1408 |
+
& (minute["time_str"] <= "14:20")
|
| 1409 |
+
].sort_values("dt")
|
| 1410 |
+
if not window.empty and pred in ("UP", "DOWN"):
|
| 1411 |
+
w_close = float(window.iloc[-1]["close"])
|
| 1412 |
+
t1_actual = "UP" if day_close > w_close else "DOWN"
|
| 1413 |
+
ledger["tplus1"]["entries"].append({
|
| 1414 |
+
"date": session_iso,
|
| 1415 |
+
"prediction": pred,
|
| 1416 |
+
"actual": t1_actual,
|
| 1417 |
+
"correct": pred == t1_actual,
|
| 1418 |
+
})
|
| 1419 |
+
except Exception:
|
| 1420 |
+
pass
|
| 1421 |
+
|
| 1422 |
+
# Recompute summary stats
|
| 1423 |
+
for model_id in ("t5", "tomorrow", "tplus1"):
|
| 1424 |
+
entries = ledger[model_id]["entries"]
|
| 1425 |
+
total = len(entries)
|
| 1426 |
+
correct = sum(1 for e in entries if e.get("correct"))
|
| 1427 |
+
ledger[model_id]["total"] = total
|
| 1428 |
+
ledger[model_id]["correct_count"] = correct
|
| 1429 |
+
ledger[model_id]["accuracy"] = correct / total if total > 0 else None
|
| 1430 |
+
|
| 1431 |
+
save_live_accuracy(ledger)
|
| 1432 |
+
clear_dashboard_payload_cache()
|
| 1433 |
+
return ledger
|
| 1434 |
+
|
| 1435 |
+
|
| 1436 |
def refresh_market_close_data(session_date: date | None = None) -> dict[str, Any]:
|
| 1437 |
now = datetime.now(IST)
|
| 1438 |
session_date = session_date or now.date()
|
|
|
|
| 1444 |
minutes = fetch_yahoo_minutes(period="7d")
|
| 1445 |
minute_frame = append_parquet_rows(NIFTY_1M_PATH, minutes, ["date"])
|
| 1446 |
daily_info = refresh_daily_data()
|
| 1447 |
+
# Score live predictions BEFORE they get overwritten by fresh ones
|
| 1448 |
+
try:
|
| 1449 |
+
update_live_accuracy(session_date)
|
| 1450 |
+
except Exception as exc:
|
| 1451 |
+
print(f"[close-refresh] live accuracy update failed: {exc}", flush=True)
|
| 1452 |
t5_prediction = refresh_first5_prediction(session_date=session_date, minutes=minutes)
|
| 1453 |
tplus1_prediction = refresh_tplus1_prediction(session_date=session_date)
|
| 1454 |
outcomes = update_opening_outcomes_from_daily()
|