import pandas as pd import requests import uvicorn import hashlib import json import time import urllib.parse import os import threading from datetime import datetime, timedelta, timezone from fastapi import FastAPI, Request, Response, Cookie, BackgroundTasks, Query from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse from typing import Optional, List, Dict # ========================================== # 1. CONFIGURATION # ========================================== CLIENT_ID = "J4PNT5QI22-100" SECRET_KEY = "ABRSWC1PMR" REDIRECT_URI = "https://topsecrettraders-depthchainindia.hf.space/callback" CACHE_FILE = "master_cache.json" # --- SUPABASE CONFIG --- SUPABASE_URL = "https://tuyhtqxfvjndbyjazosi.supabase.co" SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InR1eWh0cXhmdmpuZGJ5amF6b3NpIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc2ODE0NjAyMiwiZXhwIjoyMDgzNzIyMDIyfQ.YsmGS67vL5TghB5OOi6pmsPL6rrgx7SNpARHmQ9b5cw" app = FastAPI() # ========================================== # 2. ROBUST DATA LOADER & SUPABASE # ========================================== from supabase import create_client, Client supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) MASTER_DB = {} SEARCH_INDEX = [] SYSTEM_STATUS = { "ready": False, "message": "Booting..." } UPDATE_LOCK = threading.Lock() # Manual mapping for Index Spot symbols INDEX_MAP = { "NIFTY": "NSE:NIFTY50-INDEX", "BANKNIFTY": "NSE:NIFTYBANK-INDEX", "FINNIFTY": "NSE:FINNIFTY-INDEX", "MIDCPNIFTY": "NSE:MIDCPNIFTY-INDEX", "SENSEX": "BSE:SENSEX-INDEX", "BANKEX": "BSE:BANKEX-INDEX", "SENSEX50": "BSE:SENSEX50-INDEX" } def get_root_symbol(desc: str, symbol: str) -> str: try: parts = desc.strip().split(' ') root = parts[0].upper() return root.replace(':', '').replace('-', '') except: if ':' in symbol: return symbol.split(':')[1] return symbol def update_master_db(): global SYSTEM_STATUS, MASTER_DB, SEARCH_INDEX if not UPDATE_LOCK.acquire(blocking=False): return try: SYSTEM_STATUS["message"] = "Downloading Master Data..." print(">>> [BG TASK] STARTING DOWNLOAD...") headers = { "User-Agent": "Mozilla/5.0" } # 1. NSE F&O url_nse = "https://public.fyers.in/sym_details/NSE_FO.csv" df_nse = pd.read_csv(url_nse, usecols=[0, 1, 8, 9, 13], names=['Token', 'Desc', 'Expiry', 'Symbol', 'Inst'], header=0, on_bad_lines='skip', storage_options=headers) df_nse['Exch'] = 'NSE' # 2. MCX F&O url_mcx = "https://public.fyers.in/sym_details/MCX_COM.csv" df_mcx = pd.read_csv(url_mcx, usecols=[0, 1, 8, 9, 13], names=['Token', 'Desc', 'Expiry', 'Symbol', 'Inst'], header=0, on_bad_lines='skip', storage_options=headers) df_mcx['Exch'] = 'MCX' # 3. BSE F&O url_bse = "https://public.fyers.in/sym_details/BSE_FO.csv" df_bse = pd.read_csv(url_bse, usecols=[0, 1, 8, 9, 13], names=['Token', 'Desc', 'Expiry', 'Symbol', 'Inst'], header=0, on_bad_lines='skip', storage_options=headers) df_bse['Exch'] = 'BSE' SYSTEM_STATUS["message"] = "Processing Data..." df = pd.concat([df_nse, df_mcx, df_bse], ignore_index=True) df['Symbol'] = df['Symbol'].astype(str) df['Desc'] = df['Desc'].astype(str) temp_db = {} for _, row in df.iterrows(): sym = row['Symbol'] desc = row['Desc'] exch = row['Exch'] root = get_root_symbol(desc, sym) if root not in temp_db: spot_sym = "" if root in INDEX_MAP: spot_sym = INDEX_MAP[root] elif exch == "MCX": spot_sym = "MCX" elif exch == "BSE": spot_sym = f"BSE:{root}" else: spot_sym = f"NSE:{root}-EQ" temp_db[root] = { "spot": spot_sym, "exch": exch, "items": [] } try: exp = int(row['Expiry']) except: exp = 0 strike = 0.0 opt_type = "FUT" if "CE" in sym or "PE" in sym: parts = desc.strip().split(' ') if len(parts) >= 2: try: val = parts[-2] val_clean = val.replace('.', '', 1) if val_clean.isdigit(): strike = float(val) opt_type = parts[-1] except: pass temp_db[root]["items"].append({ "s": sym, "e": exp, "k": strike, "t": opt_type }) final_db = {} search_list = [] for root, data in temp_db.items(): sorted_items = sorted(data["items"], key=lambda x: (x['e'], x['k'])) final_db[root] = { "spot": data["spot"], "exch": data["exch"], "items": sorted_items } search_list.append({ "root": root, "exch": data["exch"], "display": f"{root}" }) MASTER_DB = final_db SEARCH_INDEX = sorted(search_list, key=lambda x: x['root']) with open(CACHE_FILE, "w") as f: json.dump({"db": final_db, "idx": SEARCH_INDEX}, f) SYSTEM_STATUS["ready"] = True SYSTEM_STATUS["message"] = "Ready" print(f">>> [BG TASK] COMPLETE. Loaded {len(SEARCH_INDEX)} symbols.") except Exception as e: print(f">>> [BG TASK] ERROR: {e}") SYSTEM_STATUS["message"] = f"Error: {str(e)}" finally: UPDATE_LOCK.release() @app.on_event("startup") def startup_event(): global MASTER_DB, SEARCH_INDEX if os.path.exists(CACHE_FILE): try: print(">>> STARTUP: Loading Cache...") with open(CACHE_FILE, "r") as f: data = json.load(f) MASTER_DB = data["db"] SEARCH_INDEX = data["idx"] SYSTEM_STATUS["ready"] = True SYSTEM_STATUS["message"] = "Ready (Cached)" except: pass if not SYSTEM_STATUS["ready"]: thread = threading.Thread(target=update_master_db) thread.start() # ========================================== # 3. ROUTES & API # ========================================== @app.get("/") def home(access_token: Optional[str] = Cookie(None)): if not access_token: state = "init" enc_redirect = urllib.parse.quote(REDIRECT_URI, safe="") auth_url = ( f"https://api-t1.fyers.in/api/v3/generate-authcode?" f"client_id={CLIENT_ID}&redirect_uri={enc_redirect}&response_type=code&state={state}" ) return HTMLResponse(f"""

Authentication Required

LOGIN WITH FYERS
""") return HTMLResponse(HTML_TEMPLATE.replace("{{USER_TOKEN}}", access_token)) @app.get("/callback") def callback(auth_code: Optional[str] = None, code: Optional[str] = None): final_code = auth_code if auth_code else code if not final_code: return JSONResponse({"error": "No Code Provided"}) app_id_hash = hashlib.sha256(f"{CLIENT_ID}:{SECRET_KEY}".encode()).hexdigest() resp = requests.post("https://api-t1.fyers.in/api/v3/validate-authcode", json={ "grant_type": "authorization_code", "appIdHash": app_id_hash, "code": final_code }) data = resp.json() if "access_token" in data: full_token = f"{CLIENT_ID}:{data['access_token']}" response = RedirectResponse(url="/") response.set_cookie(key="access_token", value=full_token, httponly=True) return response else: return HTMLResponse(f"

Login Failed

{json.dumps(data, indent=2)}
") @app.get("/logout") def logout(): response = RedirectResponse(url="/") response.delete_cookie("access_token") return response @app.get("/api/status") def get_status(): return JSONResponse(SYSTEM_STATUS) @app.get("/api/search") def search_symbol(q: str = Query(..., min_length=1)): if not SYSTEM_STATUS["ready"]: return JSONResponse([]) query = q.upper() results = [x for x in SEARCH_INDEX if x['root'].startswith(query)] if len(results) < 10: results += [x for x in SEARCH_INDEX if query in x['root'] and x not in results] return JSONResponse(results[:20]) @app.get("/api/chain") def get_chain(symbol: str): root = symbol.upper() if root in MASTER_DB: return JSONResponse(MASTER_DB[root]) return JSONResponse({"error": "Symbol not found"}, status_code=404) @app.get("/api/refresh") def force_refresh(background_tasks: BackgroundTasks): SYSTEM_STATUS["ready"] = False SYSTEM_STATUS["message"] = "Refreshing..." background_tasks.add_task(update_master_db) return JSONResponse({"s": "ok"}) # --- HISTORICAL API --- def parse_bucket_data(bucket_rows): slots = [{"s": f"{i*10:02d}", "d": {}} for i in range(6)] for row in bucket_rows: data = row.get('data') if not data or not isinstance(data, dict): continue for symbol, metrics in data.items(): p_arr = metrics.get('p', []) b_arr = metrics.get('b', []) s_arr = metrics.get('s', []) v_arr = metrics.get('v', []) o_arr = metrics.get('o', []) for i in range(6): try: ltp = p_arr[i] if i < len(p_arr) else None if ltp is None: continue slots[i]['d'][symbol] = { "ltp": ltp, "totalbuyqty": b_arr[i] if i < len(b_arr) else 0, "totalsellqty": s_arr[i] if i < len(s_arr) else 0, "v": v_arr[i] if i < len(v_arr) else 0, "oi": o_arr[i] if i < len(o_arr) else 0, "expiry": 0 } except: continue return slots @app.get("/api/hist/dates") def get_hist_dates(symbol: str): try: res = supabase.table('history_buckets').select('minute').ilike('root', f"%{symbol}%").order('minute', desc=True).limit(2000).execute() dates = set() if res.data: for item in res.data: if item.get('minute'): dates.add(item.get('minute').split('T')[0].split(' ')[0]) return JSONResponse(sorted(list(dates), reverse=True)) except Exception as e: return JSONResponse({"error": str(e)}, status_code=500) @app.get("/api/hist/minutes") def get_hist_minutes(symbol: str, date: str): try: res = supabase.table('history_buckets').select('minute').ilike('root', f"%{symbol}%").gte('minute', f"{date}T00:00:00").lte('minute', f"{date}T23:59:59").order('minute', desc=True).limit(1000).execute() minutes = set() if res.data: for item in res.data: clean = item.get('minute').replace('T', ' ').split(' ') if len(clean)>1: minutes.add(clean[1][:5]) return JSONResponse(sorted(list(minutes), reverse=True)) except Exception as e: return JSONResponse({"error": str(e)}, status_code=500) @app.get("/api/hist/expiries") def get_hist_expiries(symbol: str, date: str, minute: str): try: q1 = f"{date} {minute}:00" q2 = f"{date}T{minute}:00" res = supabase.table('history_buckets').select('expiry').ilike('root', f"%{symbol}%").or_(f"minute.eq.{q1},minute.eq.{q2}").neq('expiry', 'MARKET').execute() expiries = set() if res.data: for item in res.data: if item.get('expiry') and item.get('expiry') != 'MARKET': expiries.add(item.get('expiry')) return JSONResponse(sorted(list(expiries))) except Exception as e: return JSONResponse({"error": str(e)}, status_code=500) @app.get("/api/hist/load") def get_hist_data(symbol: str, date: str, minute: str, expiry: Optional[str] = None): try: q1 = f"{date} {minute}:00" q2 = f"{date}T{minute}:00" res = supabase.table('history_buckets').select('data, expiry').ilike('root', f"%{symbol}%").or_(f"minute.eq.{q1},minute.eq.{q2}").execute() rows = res.data if not rows: return JSONResponse({"s": "ok", "timeline": []}) filtered_rows = [] for r in rows: e = r.get('expiry') if not expiry or e == 'MARKET' or e == expiry: filtered_rows.append(r) return JSONResponse({"s": "ok", "timeline": parse_bucket_data(filtered_rows)}) except Exception as e: return JSONResponse({"error": str(e)}, status_code=500) # ========================================== # 4. FRONTEND # ========================================== HTML_TEMPLATE = """ PRO CHAIN V3
CONNECTING...
«
»
SPOT
0.00
NET FLOW
OVERALL FLOW
USE SEARCH BAR TO LOAD DATA
AGGREGATE FLOW
LOADING CONFIG...
FUTURES CONTRACTS
""" if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=7860)