Jitendra12421 commited on
Commit
d900e54
·
verified ·
1 Parent(s): cb8ac4b

Upload 42 files

Browse files
__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
 
kotak_neo.py CHANGED
@@ -5,6 +5,7 @@ import threading
5
  from csv import DictReader
6
  from concurrent.futures import ThreadPoolExecutor, as_completed
7
  from datetime import date, datetime, time, timezone
 
8
  from pathlib import Path
9
  from time import monotonic
10
  from typing import Any
@@ -16,6 +17,11 @@ import json
16
  import pandas as pd
17
  import requests
18
 
 
 
 
 
 
19
 
20
  SESSION_BASE_URL = "https://mis.kotaksecurities.com"
21
  QUOTE_PATH_TEMPLATE = "script-details/1.0/quotes/neosymbol/{neo_symbols}/{quote_type}"
@@ -28,6 +34,8 @@ KOTAK_ACTIVITY_LOG_PATH = DATA_DIR / "kotak_activity_log.txt"
28
  NIFTY_1M_PATH = DATA_DIR / "nifty50_1m.parquet"
29
  NIFTY_1D_PATH = DATA_DIR / "nifty50_1d.parquet"
30
  IST = ZoneInfo("Asia/Kolkata")
 
 
31
 
32
 
33
  class KotakNeoError(Exception):
@@ -85,6 +93,50 @@ def _normalize_frame_dates(frame: pd.DataFrame) -> pd.Series:
85
  return values
86
 
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  def _first_text(*values: Any) -> str | None:
89
  for value in values:
90
  if isinstance(value, dict):
@@ -432,7 +484,10 @@ class KotakNeoManager:
432
  item = items[0]
433
  now_ist = datetime.now(IST)
434
  last_traded_price = _first_market_number(item.get("last_traded_price"), item.get("ltp"), item.get("iv"))
435
- live_stats = self._load_nifty50_reference_stats(now_ist, last_traded_price)
 
 
 
436
  close = live_stats["previous_close"]
437
  change_base = live_stats["return_base"]
438
  change = None
@@ -442,7 +497,7 @@ class KotakNeoManager:
442
  change_pct = (change / change_base) * 100.0
443
  high = live_stats["range_high"]
444
  low = live_stats["range_low"]
445
- open_price = _first_market_number(item.get("openingPrice"), item.get("open"), item.get("o"))
446
 
447
  payload = {
448
  "symbol": "NIFTY 50",
@@ -464,6 +519,7 @@ class KotakNeoManager:
464
  "low": low,
465
  "return_basis": live_stats["return_basis"],
466
  "market_open": live_stats["market_open"],
 
467
  "exchange_feed_time": _first_text(item.get("tvalue"), item.get("updRecvTm"), item.get("hsUpTm")),
468
  "as_of": _utc_now_iso(),
469
  "source": {
@@ -483,46 +539,62 @@ class KotakNeoManager:
483
  }
484
  return payload
485
 
486
- def _load_nifty50_reference_stats(self, now_ist: datetime, last_traded_price: float | None) -> dict[str, Any]:
 
 
 
 
 
 
 
487
  today = now_ist.date()
488
- market_open = time(9, 15) <= now_ist.time() < time(15, 30)
 
489
 
490
- daily = pd.read_parquet(NIFTY_1D_PATH, columns=["date", "open", "high", "low", "close"]).copy()
491
- daily["date"] = pd.to_datetime(daily["date"], errors="coerce").dt.date
492
- daily = daily.dropna(subset=["date"]).sort_values("date")
493
 
494
  previous_close = None
 
 
495
  today_daily = daily[daily["date"] == today]
496
  previous_daily = daily[daily["date"] < today]
497
  if not previous_daily.empty:
498
- previous_close = _to_float(previous_daily.iloc[-1]["close"])
 
 
 
499
 
500
  today_open = _to_float(today_daily.iloc[-1]["open"]) if not today_daily.empty else None
501
  today_high = _to_float(today_daily.iloc[-1]["high"]) if not today_daily.empty else None
502
  today_low = _to_float(today_daily.iloc[-1]["low"]) if not today_daily.empty else None
503
  today_close = _to_float(today_daily.iloc[-1]["close"]) if not today_daily.empty else None
504
 
505
- minute = pd.read_parquet(NIFTY_1M_PATH, columns=["date", "open", "high", "low", "close"]).copy()
506
- minute["date"] = _normalize_frame_dates(minute)
507
- minute = minute.dropna(subset=["date"]).sort_values("date")
508
- today_minute = minute[minute["date"].dt.date == today]
509
-
510
- if not today_minute.empty:
511
- today_open = _to_float(today_minute.iloc[0]["open"]) or today_open
512
- minute_high = pd.to_numeric(today_minute["high"], errors="coerce").max()
513
- minute_low = pd.to_numeric(today_minute["low"], errors="coerce").min()
514
- today_high = _to_float(minute_high) or today_high
515
- today_low = _to_float(minute_low) or today_low
516
- today_close = _to_float(today_minute.iloc[-1]["close"]) or today_close
517
-
518
- if market_open:
 
 
 
 
 
519
  range_high = max([value for value in [today_high, last_traded_price] if value is not None], default=None)
520
  range_low = min([value for value in [today_low, last_traded_price] if value is not None], default=None)
521
  return_base = today_open
522
  return_basis = "open"
523
  else:
524
- range_high = today_high
525
- range_low = today_low
526
  return_base = previous_close
527
  return_basis = "previous_close"
528
  if last_traded_price is None:
@@ -535,6 +607,7 @@ class KotakNeoManager:
535
  "range_high": range_high,
536
  "range_low": range_low,
537
  "market_open": market_open,
 
538
  }
539
 
540
  def _ensure_authenticated_locked(self) -> None:
 
5
  from csv import DictReader
6
  from concurrent.futures import ThreadPoolExecutor, as_completed
7
  from datetime import date, datetime, time, timezone
8
+ from functools import lru_cache
9
  from pathlib import Path
10
  from time import monotonic
11
  from typing import Any
 
17
  import pandas as pd
18
  import requests
19
 
20
+ try:
21
+ import pandas_market_calendars as mcal
22
+ except Exception: # pragma: no cover - deployed environments may fall back to weekdays
23
+ mcal = None
24
+
25
 
26
  SESSION_BASE_URL = "https://mis.kotaksecurities.com"
27
  QUOTE_PATH_TEMPLATE = "script-details/1.0/quotes/neosymbol/{neo_symbols}/{quote_type}"
 
34
  NIFTY_1M_PATH = DATA_DIR / "nifty50_1m.parquet"
35
  NIFTY_1D_PATH = DATA_DIR / "nifty50_1d.parquet"
36
  IST = ZoneInfo("Asia/Kolkata")
37
+ MARKET_OPEN_TIME = time(9, 15)
38
+ MARKET_CLOSE_TIME = time(15, 30)
39
 
40
 
41
  class KotakNeoError(Exception):
 
93
  return values
94
 
95
 
96
+ @lru_cache(maxsize=1)
97
+ def _nse_calendar():
98
+ if mcal is None:
99
+ return None
100
+ for name in ("XNSE", "NSE", "BSE"):
101
+ try:
102
+ return mcal.get_calendar(name)
103
+ except Exception:
104
+ continue
105
+ return None
106
+
107
+
108
+ @lru_cache(maxsize=64)
109
+ def _is_nse_trading_day(day: date) -> bool:
110
+ calendar = _nse_calendar()
111
+ if calendar is None:
112
+ return day.weekday() < 5
113
+ return not calendar.schedule(start_date=day, end_date=day).empty
114
+
115
+
116
+ def _file_version(path: Path) -> tuple[str, int | None, int | None]:
117
+ try:
118
+ stat = path.stat()
119
+ return (str(path), stat.st_mtime_ns, stat.st_size)
120
+ except OSError:
121
+ return (str(path), None, None)
122
+
123
+
124
+ @lru_cache(maxsize=4)
125
+ def _load_nifty_daily_frame(file_version: tuple[str, int | None, int | None]) -> pd.DataFrame:
126
+ path = Path(file_version[0])
127
+ daily = pd.read_parquet(path, columns=["date", "open", "high", "low", "close"]).copy()
128
+ daily["date"] = pd.to_datetime(daily["date"], errors="coerce").dt.date
129
+ return daily.dropna(subset=["date"]).sort_values("date")
130
+
131
+
132
+ @lru_cache(maxsize=4)
133
+ def _load_nifty_minute_frame(file_version: tuple[str, int | None, int | None]) -> pd.DataFrame:
134
+ path = Path(file_version[0])
135
+ minute = pd.read_parquet(path, columns=["date", "open", "high", "low", "close"]).copy()
136
+ minute["date"] = _normalize_frame_dates(minute)
137
+ return minute.dropna(subset=["date"]).sort_values("date")
138
+
139
+
140
  def _first_text(*values: Any) -> str | None:
141
  for value in values:
142
  if isinstance(value, dict):
 
484
  item = items[0]
485
  now_ist = datetime.now(IST)
486
  last_traded_price = _first_market_number(item.get("last_traded_price"), item.get("ltp"), item.get("iv"))
487
+ quote_open = _first_market_number(item.get("openingPrice"), item.get("open"), item.get("o"))
488
+ quote_high = _first_market_number(item.get("high"), item.get("highPrice"), item.get("h"))
489
+ quote_low = _first_market_number(item.get("low"), item.get("lowPrice"), item.get("l"))
490
+ live_stats = self._load_nifty50_reference_stats(now_ist, last_traded_price, quote_open, quote_high, quote_low)
491
  close = live_stats["previous_close"]
492
  change_base = live_stats["return_base"]
493
  change = None
 
497
  change_pct = (change / change_base) * 100.0
498
  high = live_stats["range_high"]
499
  low = live_stats["range_low"]
500
+ open_price = quote_open
501
 
502
  payload = {
503
  "symbol": "NIFTY 50",
 
519
  "low": low,
520
  "return_basis": live_stats["return_basis"],
521
  "market_open": live_stats["market_open"],
522
+ "is_trading_session": live_stats["is_trading_session"],
523
  "exchange_feed_time": _first_text(item.get("tvalue"), item.get("updRecvTm"), item.get("hsUpTm")),
524
  "as_of": _utc_now_iso(),
525
  "source": {
 
539
  }
540
  return payload
541
 
542
+ def _load_nifty50_reference_stats(
543
+ self,
544
+ now_ist: datetime,
545
+ last_traded_price: float | None,
546
+ live_open: float | None = None,
547
+ live_high: float | None = None,
548
+ live_low: float | None = None,
549
+ ) -> dict[str, Any]:
550
  today = now_ist.date()
551
+ is_trading_session = _is_nse_trading_day(today)
552
+ market_open = is_trading_session and MARKET_OPEN_TIME <= now_ist.time() < MARKET_CLOSE_TIME
553
 
554
+ daily = _load_nifty_daily_frame(_file_version(NIFTY_1D_PATH))
 
 
555
 
556
  previous_close = None
557
+ previous_high = None
558
+ previous_low = None
559
  today_daily = daily[daily["date"] == today]
560
  previous_daily = daily[daily["date"] < today]
561
  if not previous_daily.empty:
562
+ previous_row = previous_daily.iloc[-1]
563
+ previous_close = _to_float(previous_row["close"])
564
+ previous_high = _to_float(previous_row["high"])
565
+ previous_low = _to_float(previous_row["low"])
566
 
567
  today_open = _to_float(today_daily.iloc[-1]["open"]) if not today_daily.empty else None
568
  today_high = _to_float(today_daily.iloc[-1]["high"]) if not today_daily.empty else None
569
  today_low = _to_float(today_daily.iloc[-1]["low"]) if not today_daily.empty else None
570
  today_close = _to_float(today_daily.iloc[-1]["close"]) if not today_daily.empty else None
571
 
572
+ session_started = is_trading_session and now_ist.time() >= MARKET_OPEN_TIME
573
+
574
+ if session_started:
575
+ today_open = live_open or today_open
576
+ today_high = live_high or today_high
577
+ today_low = live_low or today_low
578
+
579
+ if today_open is None or today_high is None or today_low is None:
580
+ minute = _load_nifty_minute_frame(_file_version(NIFTY_1M_PATH))
581
+ today_minute = minute[minute["date"].dt.date == today]
582
+ if not today_minute.empty:
583
+ today_open = _to_float(today_minute.iloc[0]["open"]) or today_open
584
+ minute_high = pd.to_numeric(today_minute["high"], errors="coerce").max()
585
+ minute_low = pd.to_numeric(today_minute["low"], errors="coerce").min()
586
+ today_high = _to_float(minute_high) or today_high
587
+ today_low = _to_float(minute_low) or today_low
588
+ today_close = _to_float(today_minute.iloc[-1]["close"]) or today_close
589
+
590
+ if session_started:
591
  range_high = max([value for value in [today_high, last_traded_price] if value is not None], default=None)
592
  range_low = min([value for value in [today_low, last_traded_price] if value is not None], default=None)
593
  return_base = today_open
594
  return_basis = "open"
595
  else:
596
+ range_high = previous_high
597
+ range_low = previous_low
598
  return_base = previous_close
599
  return_basis = "previous_close"
600
  if last_traded_price is None:
 
607
  "range_high": range_high,
608
  "range_low": range_low,
609
  "market_open": market_open,
610
+ "is_trading_session": is_trading_session,
611
  }
612
 
613
  def _ensure_authenticated_locked(self) -> None: