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

Upload 42 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
 
__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,18 +452,6 @@ def kotak_account() -> dict:
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:
 
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:
kotak_neo.py CHANGED
@@ -2,7 +2,6 @@ from __future__ import annotations
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,7 +22,6 @@ DEFAULT_TIMEOUT_SECONDS = 20
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,9 +133,6 @@ class KotakNeoManager:
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,42 +376,6 @@ class KotakNeoManager:
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,143 +491,6 @@ class KotakNeoManager:
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],
 
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
  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
  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
  "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
  timeout=DEFAULT_TIMEOUT_SECONDS,
492
  )
493
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
  def _fetch_quotes_with_context(
495
  self,
496
  context: dict[str, str],