Jitendra12421 commited on
Commit
3a89854
·
verified ·
1 Parent(s): 5eba2b0

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,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/quote/nifty50")
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:
kotak_neo.py CHANGED
@@ -6,6 +6,7 @@ from csv import DictReader
6
  from concurrent.futures import ThreadPoolExecutor, as_completed
7
  from datetime import datetime, timezone
8
  from pathlib import Path
 
9
  from typing import Any
10
  from urllib.parse import quote
11
 
@@ -133,6 +134,7 @@ 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 +378,67 @@ 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.")
@@ -710,6 +773,34 @@ class KotakNeoManager:
710
  self._scrip_cache[key] = rows
711
  return rows
712
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
713
  def _build_quote_map(self, payload: dict[str, Any]) -> dict[str, dict[str, Any]]:
714
  items = _extract_items(payload)
715
  quote_map: dict[str, dict[str, Any]] = {}
 
6
  from concurrent.futures import ThreadPoolExecutor, as_completed
7
  from datetime import datetime, timezone
8
  from pathlib import Path
9
+ from time import monotonic
10
  from typing import Any
11
  from urllib.parse import quote
12
 
 
134
  self.activity_log_path.parent.mkdir(parents=True, exist_ok=True)
135
  self._seen_activity_keys: set[str] = set()
136
  self._scrip_cache: dict[str, list[dict[str, str]]] = {}
137
+ self._quote_cache: dict[str, dict[str, Any]] = {}
138
  self._load_existing_activity_keys()
139
  self._clear_session_locked()
140
 
 
378
  "quotes": list(quote_map.values()),
379
  }
380
 
381
+ def fetch_nifty50_quote(self, *, force_refresh: bool = False) -> dict[str, Any]:
382
+ cache_key = "nifty50_quote"
383
+ with self._lock:
384
+ if not force_refresh:
385
+ cached = self._quote_cache.get(cache_key)
386
+ if cached and (monotonic() - float(cached.get("stored_at_monotonic") or 0.0)) < 1.0:
387
+ return dict(cached["payload"])
388
+ context = self._context_locked()
389
+
390
+ reference = self._resolve_nifty50_reference(context)
391
+ quote_payload = self._fetch_quotes_with_context(
392
+ context,
393
+ [
394
+ {
395
+ "exchange_segment": "nse_cm",
396
+ "instrument_token": str(reference["quote_instrument_token"]),
397
+ }
398
+ ],
399
+ timeout=ACCOUNT_TIMEOUT_SECONDS,
400
+ )
401
+ items = _extract_items(quote_payload)
402
+ if not items:
403
+ raise KotakNeoError("Kotak Neo did not return a NIFTY 50 quote.")
404
+
405
+ item = items[0]
406
+ payload = {
407
+ "symbol": "NIFTY 50",
408
+ "exchange_segment": "nse_cm",
409
+ "instrument_token": _first_text(
410
+ item.get("instrument_token"),
411
+ item.get("instrumentToken"),
412
+ item.get("tk"),
413
+ reference.get("master_instrument_token"),
414
+ reference["quote_instrument_token"],
415
+ ),
416
+ "display_name": _first_text(item.get("trading_symbol"), item.get("ts"), item.get("name"), "NIFTY 50"),
417
+ "last_traded_price": _first_number(item.get("last_traded_price"), item.get("ltp"), item.get("iv")),
418
+ "close": _first_number(item.get("close"), item.get("c"), item.get("ic")),
419
+ "change": _first_number(item.get("change"), item.get("cng")),
420
+ "change_pct": _first_number(item.get("net_change_percentage"), item.get("nc")),
421
+ "open": _first_number(item.get("openingPrice"), item.get("open"), item.get("o")),
422
+ "high": _first_number(item.get("highPrice"), item.get("high"), item.get("h")),
423
+ "low": _first_number(item.get("lowPrice"), item.get("low"), item.get("l")),
424
+ "exchange_feed_time": _first_text(item.get("tvalue"), item.get("updRecvTm"), item.get("hsUpTm")),
425
+ "as_of": _utc_now_iso(),
426
+ "source": {
427
+ "quote_api": QUOTE_PATH_TEMPLATE,
428
+ "master_scrip_verified": bool(reference.get("master_record_found")),
429
+ "instrument_lookup": reference.get("lookup_mode"),
430
+ "master_symbol_name": reference.get("master_symbol_name"),
431
+ "master_trading_symbol": reference.get("master_trading_symbol"),
432
+ },
433
+ }
434
+
435
+ with self._lock:
436
+ self._quote_cache[cache_key] = {
437
+ "stored_at_monotonic": monotonic(),
438
+ "payload": payload,
439
+ }
440
+ return payload
441
+
442
  def _ensure_authenticated_locked(self) -> None:
443
  if not self.edit_token or not self.edit_sid or not self.base_url:
444
  raise KotakNeoSessionRequired("Kotak Neo session is not authenticated.")
 
773
  self._scrip_cache[key] = rows
774
  return rows
775
 
776
+ def _resolve_nifty50_reference(self, context: dict[str, str]) -> dict[str, Any]:
777
+ candidates = self._load_scrip_candidates(context, "nse_cm")
778
+ match = None
779
+ aliases = {
780
+ "NIFTY 50",
781
+ "NIFTY50",
782
+ "NIFTY 50 INDEX",
783
+ "NIFTY",
784
+ }
785
+ for item in candidates:
786
+ candidate_values = {
787
+ str(item.get("pSymbolName") or "").strip().upper(),
788
+ str(item.get("pTrdSymbol") or "").strip().upper(),
789
+ str(item.get("pSymbol") or "").strip().upper(),
790
+ }
791
+ if aliases & candidate_values:
792
+ match = item
793
+ break
794
+
795
+ return {
796
+ "quote_instrument_token": "Nifty 50",
797
+ "lookup_mode": "index-name-direct" if match is None else "master-scrip-verified-index-name-direct",
798
+ "master_record_found": match is not None,
799
+ "master_instrument_token": _first_text(match.get("pSymbol")) if match else None,
800
+ "master_symbol_name": _first_text(match.get("pSymbolName")) if match else None,
801
+ "master_trading_symbol": _first_text(match.get("pTrdSymbol")) if match else None,
802
+ }
803
+
804
  def _build_quote_map(self, payload: dict[str, Any]) -> dict[str, dict[str, Any]]:
805
  items = _extract_items(payload)
806
  quote_map: dict[str, dict[str, Any]] = {}