Jitendra12421 commited on
Commit
c3d690a
·
verified ·
1 Parent(s): d220c08

Upload 42 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ models/yahoo_history_cache.sqlite3 filter=lfs diff=lfs merge=lfs -text
__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
 
app.py CHANGED
@@ -452,6 +452,18 @@ def kotak_account() -> dict:
452
  raise HTTPException(status_code=502, detail=str(exc)) from exc
453
 
454
 
 
 
 
 
 
 
 
 
 
 
 
 
455
  @app.get("/kotak/activity-log")
456
  def kotak_activity_log() -> dict:
457
  try:
 
452
  raise HTTPException(status_code=502, detail=str(exc)) from exc
453
 
454
 
455
+ @app.get("/kotak/nifty50-quote")
456
+ def kotak_nifty50_quote() -> dict:
457
+ try:
458
+ return kotak_neo_manager.fetch_nifty50_quote()
459
+ except KotakNeoConfigError as exc:
460
+ raise HTTPException(status_code=503, detail=str(exc)) from exc
461
+ except KotakNeoSessionRequired as exc:
462
+ raise HTTPException(status_code=401, detail=str(exc)) from exc
463
+ except KotakNeoError as exc:
464
+ raise HTTPException(status_code=502, detail=str(exc)) from exc
465
+
466
+
467
  @app.get("/kotak/activity-log")
468
  def kotak_activity_log() -> dict:
469
  try:
data/nifty50_1d.parquet CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:be744722b6c72c2fade81cc25551e32edcbda4737d02e6bc6ff8f0dff4b31d90
3
- size 78275
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4d0830f9d3cfa91f02ce717b556983beb12f897bc537a623c67e38f22ce05caf
3
+ size 78366
data/nifty50_1m.parquet CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:216816fb4cb1b022029e3e1ab88b344e2c6dcfb65a51d2e1b70ab76dc3320a45
3
- size 18580743
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b5d6022df273daa1f020676214ec35536426fd84f2d7efb669797287e27ffa2d
3
+ size 18589635
data/opening_direction_training_dataset.parquet CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:5293909811782086d6c16ea35e8b4313dbcea6c928e6ffc07b342502dc94f466
3
- size 4463019
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:722c8225451abcb49463d2a57354bc0c9b5eb519f31a25fa3c5242adc4dbbada
3
+ size 4463631
kotak_neo.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
 
3
  import os
4
  import threading
 
5
  from csv import DictReader
6
  from concurrent.futures import ThreadPoolExecutor, as_completed
7
  from datetime import datetime, timezone
@@ -22,6 +23,7 @@ DEFAULT_TIMEOUT_SECONDS = 20
22
  ACCOUNT_TIMEOUT_SECONDS = 7
23
  DATA_DIR = Path(__file__).resolve().parent / "data"
24
  KOTAK_ACTIVITY_LOG_PATH = DATA_DIR / "kotak_activity_log.txt"
 
25
 
26
 
27
  class KotakNeoError(Exception):
@@ -133,6 +135,9 @@ class KotakNeoManager:
133
  self.activity_log_path.parent.mkdir(parents=True, exist_ok=True)
134
  self._seen_activity_keys: set[str] = set()
135
  self._scrip_cache: dict[str, list[dict[str, str]]] = {}
 
 
 
136
  self._load_existing_activity_keys()
137
  self._clear_session_locked()
138
 
@@ -376,6 +381,42 @@ class KotakNeoManager:
376
  "quotes": list(quote_map.values()),
377
  }
378
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
  def _ensure_authenticated_locked(self) -> None:
380
  if not self.edit_token or not self.edit_sid or not self.base_url:
381
  raise KotakNeoSessionRequired("Kotak Neo session is not authenticated.")
@@ -491,6 +532,143 @@ class KotakNeoManager:
491
  timeout=DEFAULT_TIMEOUT_SECONDS,
492
  )
493
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
  def _fetch_quotes_with_context(
495
  self,
496
  context: dict[str, str],
 
2
 
3
  import os
4
  import threading
5
+ import time
6
  from csv import DictReader
7
  from concurrent.futures import ThreadPoolExecutor, as_completed
8
  from datetime import datetime, timezone
 
23
  ACCOUNT_TIMEOUT_SECONDS = 7
24
  DATA_DIR = Path(__file__).resolve().parent / "data"
25
  KOTAK_ACTIVITY_LOG_PATH = DATA_DIR / "kotak_activity_log.txt"
26
+ NIFTY50_QUOTE_CACHE_SECONDS = 1.0
27
 
28
 
29
  class KotakNeoError(Exception):
 
135
  self.activity_log_path.parent.mkdir(parents=True, exist_ok=True)
136
  self._seen_activity_keys: set[str] = set()
137
  self._scrip_cache: dict[str, list[dict[str, str]]] = {}
138
+ self._quote_cache_lock = threading.Lock()
139
+ self._nifty50_quote_cache: dict[str, Any] | None = None
140
+ self._nifty50_quote_cached_at = 0.0
141
  self._load_existing_activity_keys()
142
  self._clear_session_locked()
143
 
 
381
  "quotes": list(quote_map.values()),
382
  }
383
 
384
+ def fetch_nifty50_quote(self, *, max_age_seconds: float = NIFTY50_QUOTE_CACHE_SECONDS) -> dict[str, Any]:
385
+ if not self._configured():
386
+ raise KotakNeoConfigError("Kotak Neo environment variables are incomplete.")
387
+
388
+ with self._lock:
389
+ context = self._context_locked()
390
+ cached = self._nifty50_quote_cache
391
+ cached_at = self._nifty50_quote_cached_at
392
+ cache_age = time.monotonic() - cached_at
393
+ if cached and cache_age < max_age_seconds:
394
+ return {
395
+ **cached,
396
+ "cache": {"hit": True, "max_age_seconds": max_age_seconds, "age_seconds": round(cache_age, 3)},
397
+ }
398
+
399
+ with self._quote_cache_lock:
400
+ with self._lock:
401
+ context = self._context_locked()
402
+ cached = self._nifty50_quote_cache
403
+ cached_at = self._nifty50_quote_cached_at
404
+ cache_age = time.monotonic() - cached_at
405
+ if cached and cache_age < max_age_seconds:
406
+ return {
407
+ **cached,
408
+ "cache": {"hit": True, "max_age_seconds": max_age_seconds, "age_seconds": round(cache_age, 3)},
409
+ }
410
+
411
+ fresh_quote = self._fetch_nifty50_quote_with_context(context)
412
+ with self._lock:
413
+ self._nifty50_quote_cache = fresh_quote
414
+ self._nifty50_quote_cached_at = time.monotonic()
415
+ return {
416
+ **fresh_quote,
417
+ "cache": {"hit": False, "max_age_seconds": max_age_seconds, "age_seconds": 0.0},
418
+ }
419
+
420
  def _ensure_authenticated_locked(self) -> None:
421
  if not self.edit_token or not self.edit_sid or not self.base_url:
422
  raise KotakNeoSessionRequired("Kotak Neo session is not authenticated.")
 
532
  timeout=DEFAULT_TIMEOUT_SECONDS,
533
  )
534
 
535
+ def _fetch_nifty50_quote_with_context(self, context: dict[str, str]) -> dict[str, Any]:
536
+ candidates = self._nifty50_quote_candidates(context)
537
+ errors: list[str] = []
538
+
539
+ for candidate in candidates:
540
+ try:
541
+ payload = self._fetch_quotes_with_context(
542
+ context,
543
+ [
544
+ {
545
+ "exchange_segment": candidate["exchange_segment"],
546
+ "instrument_token": candidate["instrument_token"],
547
+ }
548
+ ],
549
+ timeout=ACCOUNT_TIMEOUT_SECONDS,
550
+ )
551
+ items = _extract_items(payload)
552
+ if not items:
553
+ errors.append(
554
+ f"{candidate['lookup_source']}: quotes returned no items for {candidate['instrument_token']}"
555
+ )
556
+ continue
557
+ normalized = self._normalize_nifty50_quote(items[0], candidate)
558
+ if normalized.get("price") is None:
559
+ errors.append(
560
+ f"{candidate['lookup_source']}: quote payload did not include a usable last traded price"
561
+ )
562
+ continue
563
+ return normalized
564
+ except KotakNeoError as exc:
565
+ errors.append(f"{candidate['lookup_source']}: {exc}")
566
+
567
+ error_message = "Unable to fetch the NIFTY 50 quote from Kotak Neo."
568
+ if errors:
569
+ error_message = f"{error_message} " + " | ".join(errors)
570
+ raise KotakNeoError(error_message)
571
+
572
+ def _nifty50_quote_candidates(self, context: dict[str, str]) -> list[dict[str, Any]]:
573
+ candidates: list[dict[str, Any]] = []
574
+ master_candidate = self._resolve_nifty50_master_candidate(context)
575
+ if master_candidate:
576
+ candidates.append(master_candidate)
577
+ candidates.append(
578
+ {
579
+ "exchange_segment": "nse_cm",
580
+ "instrument_token": "Nifty 50",
581
+ "display_name": "NIFTY 50",
582
+ "lookup_source": "quotes_doc_fallback",
583
+ "master_match": None,
584
+ }
585
+ )
586
+ return candidates
587
+
588
+ def _resolve_nifty50_master_candidate(self, context: dict[str, str]) -> dict[str, Any] | None:
589
+ candidates = self._load_scrip_candidates(context, "nse_cm")
590
+ for item in candidates:
591
+ normalized_values = {
592
+ self._normalize_scrip_text(item.get("pSymbolName")),
593
+ self._normalize_scrip_text(item.get("pTrdSymbol")),
594
+ self._normalize_scrip_text(item.get("pDesc")),
595
+ self._normalize_scrip_text(item.get("pCombinedSymbol")),
596
+ self._normalize_scrip_text(item.get("pScripRefKey")),
597
+ }
598
+ normalized_values.discard("")
599
+ if not normalized_values:
600
+ continue
601
+ if "NIFTY50" not in normalized_values and not any(
602
+ value.startswith("NIFTY50") or "NIFTY50INDEX" in value for value in normalized_values
603
+ ):
604
+ continue
605
+ instrument_token = _first_text(
606
+ item.get("pSymbol"),
607
+ item.get("pTrdSymbol"),
608
+ item.get("pSymbolName"),
609
+ )
610
+ if not instrument_token:
611
+ continue
612
+ return {
613
+ "exchange_segment": "nse_cm",
614
+ "instrument_token": instrument_token,
615
+ "display_name": _first_text(item.get("pDesc"), item.get("pSymbolName"), item.get("pTrdSymbol"))
616
+ or "NIFTY 50",
617
+ "lookup_source": "masterscrip",
618
+ "master_match": {
619
+ "pSymbol": item.get("pSymbol"),
620
+ "pSymbolName": item.get("pSymbolName"),
621
+ "pTrdSymbol": item.get("pTrdSymbol"),
622
+ "pDesc": item.get("pDesc"),
623
+ },
624
+ }
625
+ return None
626
+
627
+ def _normalize_scrip_text(self, value: Any) -> str:
628
+ return "".join(ch for ch in str(value or "").upper() if ch.isalnum())
629
+
630
+ def _normalize_nifty50_quote(
631
+ self,
632
+ item: dict[str, Any],
633
+ candidate: dict[str, Any],
634
+ ) -> dict[str, Any]:
635
+ price = _first_number(item.get("last_traded_price"), item.get("ltp"), item.get("iv"))
636
+ previous_close = _first_number(item.get("close"), item.get("c"), item.get("ic"))
637
+ change = _first_number(item.get("change"), item.get("cng"))
638
+ if change is None and price is not None and previous_close is not None:
639
+ change = price - previous_close
640
+
641
+ change_pct = None
642
+ if change is not None and previous_close not in (None, 0):
643
+ change_pct = change / previous_close
644
+ if change_pct is None:
645
+ change_pct = _first_number(item.get("net_change_percentage"), item.get("nc"))
646
+
647
+ return {
648
+ "symbol": "NIFTY 50",
649
+ "display_name": candidate.get("display_name") or "NIFTY 50",
650
+ "exchange_segment": candidate.get("exchange_segment") or _first_text(item.get("exchange_segment"), item.get("e")) or "nse_cm",
651
+ "instrument_token": candidate.get("instrument_token") or _first_text(item.get("instrument_token"), item.get("tk")),
652
+ "price": price,
653
+ "previous_close": previous_close,
654
+ "change": change,
655
+ "change_pct": change_pct,
656
+ "quote_time": _first_text(
657
+ item.get("updRecvTm"),
658
+ item.get("hsUpTm"),
659
+ item.get("flDtTm"),
660
+ item.get("exTm"),
661
+ item.get("ltt"),
662
+ ),
663
+ "fetched_at": _utc_now_iso(),
664
+ "source": {
665
+ "provider": "Kotak Neo Quotes API",
666
+ "lookup_source": candidate.get("lookup_source"),
667
+ "master_match": candidate.get("master_match"),
668
+ },
669
+ "raw": item,
670
+ }
671
+
672
  def _fetch_quotes_with_context(
673
  self,
674
  context: dict[str, str],
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-05-26,2026-05-26 09:15:00,2026-05-26 09:19:00,DOWN,0.6058803888947725,0.6808803888947725,0.425,blend_extra_trees_tight_logit_overlay,True
 
1
  input_date,first5_start,first5_end,prediction,prob_up,confidence,threshold,model_name,is_overridden
2
+ 2026-05-27,2026-05-27 09:15:00,2026-05-27 09:19:00,UP,0.6970470349448922,0.7720470349448922,0.425,blend_extra_trees_tight_logit_overlay,False
models/tplus1_latest_prediction.csv CHANGED
@@ -1,2 +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-26,2026-05-27,next trading session after 2026-05-26,UP,0.48291233826421653,0.5170876617357835,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
 
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-27,2026-05-29,next trading session after 2026-05-27,UP,0.47376564177714775,0.5262343582228522,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/yahoo_history_cache.sqlite3 CHANGED
Binary files a/models/yahoo_history_cache.sqlite3 and b/models/yahoo_history_cache.sqlite3 differ