Spaces:
Running
Running
| """ | |
| FII/DII flow and Nifty PCR fetcher with JSON cache fallback. | |
| Uses NSE public APIs; falls back to yesterday's cached values on failure. | |
| """ | |
| import json | |
| import os | |
| import time | |
| import datetime | |
| import requests | |
| CACHE_FILE = os.path.join(os.path.dirname(__file__), "fii_pcr_cache.json") | |
| # Browser-like headers required by NSE archives | |
| _HEADERS = { | |
| "User-Agent": ( | |
| "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " | |
| "AppleWebKit/537.36 (KHTML, like Gecko) " | |
| "Chrome/124.0.0.0 Safari/537.36" | |
| ), | |
| "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", | |
| "Accept-Language": "en-US,en;q=0.5", | |
| "Referer": "https://www.nseindia.com/", | |
| } | |
| _NSE_FII_URL = ( | |
| "https://archives.nseindia.com/content/fo/fii_stats_{date}.csv" | |
| ) | |
| _NSE_OPTION_CHAIN_URL = ( | |
| "https://www.nseindia.com/api/option-chain-indices?symbol=NIFTY" | |
| ) | |
| def _load_cache() -> dict: | |
| try: | |
| with open(CACHE_FILE) as f: | |
| return json.load(f) | |
| except Exception: | |
| return {} | |
| def _save_cache(data: dict) -> None: | |
| try: | |
| with open(CACHE_FILE, "w") as f: | |
| json.dump(data, f, indent=2) | |
| except Exception: | |
| pass | |
| def _cache_is_fresh(cache: dict) -> bool: | |
| ts = cache.get("ts", "") | |
| today = datetime.date.today().isoformat() | |
| yesterday = (datetime.date.today() - datetime.timedelta(days=1)).isoformat() | |
| return ts in (today, yesterday) | |
| def _classify_regime(fii_net: float, dii_net: float) -> str: | |
| if fii_net >= 3000: | |
| return "FII_STRONG_BUY" | |
| elif fii_net >= 1000: | |
| return "FII_BUY" | |
| elif fii_net <= -3000: | |
| return "RISK_OFF" | |
| elif fii_net <= -2000 and dii_net >= 1500: | |
| return "FII_SELLING_DII_ABSORBING" | |
| else: | |
| return "NEUTRAL" | |
| def get_fii_dii_flow() -> dict: | |
| """ | |
| Fetch FII/DII net flow from NSE archives CSV. | |
| Returns dict: {fii_net, dii_net, regime, ts}. | |
| Falls back to cached value (up to 1 day old) on any network error. | |
| Units: crore INR (as published by NSE). | |
| """ | |
| cache = _load_cache() | |
| # Try today and yesterday in case today's file isn't published yet | |
| for offset in (0, 1, 2): | |
| date_obj = datetime.date.today() - datetime.timedelta(days=offset) | |
| date_str = date_obj.strftime("%d%m%Y") | |
| url = _NSE_FII_URL.format(date=date_str) | |
| try: | |
| resp = requests.get(url, headers=_HEADERS, timeout=8) | |
| if resp.status_code != 200: | |
| continue | |
| # NSE CSV columns: Type, Buy, Sell, Net (in crore) | |
| lines = [l.strip() for l in resp.text.splitlines() if l.strip()] | |
| fii_net = dii_net = None | |
| for line in lines: | |
| parts = [p.strip().strip('"') for p in line.split(",")] | |
| if len(parts) < 4: | |
| continue | |
| label = parts[0].upper() | |
| try: | |
| net = float(parts[3].replace(",", "")) | |
| except (ValueError, IndexError): | |
| continue | |
| if "FII" in label or "FOREIGN" in label: | |
| fii_net = net | |
| elif "DII" in label or "DOMESTIC" in label: | |
| dii_net = net | |
| if fii_net is None: | |
| continue | |
| dii_net = dii_net or 0.0 | |
| regime = _classify_regime(fii_net, dii_net) | |
| result = { | |
| "fii_net": round(fii_net, 1), | |
| "dii_net": round(dii_net, 1), | |
| "regime": regime, | |
| "ts": date_obj.isoformat(), | |
| } | |
| cache.update(result) | |
| _save_cache(cache) | |
| return result | |
| except Exception: | |
| continue | |
| # Fallback: use cache if fresh enough | |
| if _cache_is_fresh(cache) and "fii_net" in cache: | |
| return {k: cache[k] for k in ("fii_net", "dii_net", "regime", "ts")} | |
| # Last resort: neutral defaults | |
| return {"fii_net": 0.0, "dii_net": 0.0, "regime": "NEUTRAL", | |
| "ts": datetime.date.today().isoformat()} | |
| def get_nifty_pcr() -> float | None: | |
| """ | |
| Fetch Nifty Put-Call Ratio from NSE option chain API. | |
| Returns total put OI / total call OI, or None on failure. | |
| Falls back to cached value (up to 1 day old). | |
| """ | |
| cache = _load_cache() | |
| try: | |
| # NSE option chain requires a cookie first | |
| session = requests.Session() | |
| session.get("https://www.nseindia.com", headers=_HEADERS, timeout=8) | |
| time.sleep(0.3) | |
| resp = session.get(_NSE_OPTION_CHAIN_URL, headers=_HEADERS, timeout=10) | |
| if resp.status_code == 200: | |
| data = resp.json() | |
| records = data.get("records", {}).get("data", []) | |
| total_put_oi = sum( | |
| r.get("PE", {}).get("openInterest", 0) for r in records if r.get("PE") | |
| ) | |
| total_call_oi = sum( | |
| r.get("CE", {}).get("openInterest", 0) for r in records if r.get("CE") | |
| ) | |
| if total_call_oi > 0: | |
| pcr = round(total_put_oi / total_call_oi, 2) | |
| cache["pcr"] = pcr | |
| cache["pcr_ts"] = datetime.date.today().isoformat() | |
| _save_cache(cache) | |
| return pcr | |
| except Exception: | |
| pass | |
| # Fallback | |
| pcr_ts = cache.get("pcr_ts", "") | |
| yesterday = (datetime.date.today() - datetime.timedelta(days=1)).isoformat() | |
| if pcr_ts >= yesterday and "pcr" in cache: | |
| return float(cache["pcr"]) | |
| return None | |
| if __name__ == "__main__": | |
| print("FII/DII flow:", get_fii_dii_flow()) | |
| print("Nifty PCR:", get_nifty_pcr()) | |