topsecrettraders commited on
Commit
17b61e9
·
verified ·
1 Parent(s): f83ba98

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +50 -174
app.py CHANGED
@@ -36,61 +36,30 @@ SYSTEM_STATUS = {
36
  }
37
  UPDATE_LOCK = threading.Lock()
38
 
39
- # Global Cache for Indices/Stocks Spot Mapping
40
- SPOT_MAPPING = {}
41
-
42
- def load_spot_mapping(headers):
43
- """
44
- Downloads Capital Market (CM) files to map Descriptions to exact Spot Symbols.
45
- This replaces hardcoded INDEX_MAP.
46
- """
47
- global SPOT_MAPPING
48
- temp_map = {}
49
-
 
 
50
  try:
51
- # 1. NSE CM (Indices & Equities)
52
- # We need this to find 'NSE:NIFTY50-INDEX' from description 'Nifty 50'
53
- url_nse = "https://public.fyers.in/sym_details/NSE_CM.csv"
54
- df_nse = pd.read_csv(
55
- url_nse, usecols=[1, 9, 13],
56
- names=['Desc', 'Symbol', 'Inst'],
57
- header=0, on_bad_lines='skip', storage_options=headers
58
- )
59
-
60
- # 2. BSE CM (Sensex etc)
61
- url_bse = "https://public.fyers.in/sym_details/BSE_CM.csv"
62
- df_bse = pd.read_csv(
63
- url_bse, usecols=[1, 9, 13],
64
- names=['Desc', 'Symbol', 'Inst'],
65
- header=0, on_bad_lines='skip', storage_options=headers
66
- )
67
-
68
- # Combine and Process
69
- df_all = pd.concat([df_nse, df_bse], ignore_index=True)
70
-
71
- for _, row in df_all.iterrows():
72
- desc = str(row['Desc']).strip().upper()
73
- sym = str(row['Symbol']).strip()
74
-
75
- # Key Logic: Map the Description to the Symbol
76
- # This handles 'NIFTY 50' -> 'NSE:NIFTY50-INDEX'
77
- # And 'RELIANCE INDUSTRIES LTD' -> 'NSE:RELIANCE-EQ'
78
- if desc not in temp_map:
79
- temp_map[desc] = sym
80
-
81
- # Also map the short cleaning name for Indices (e.g., "NIFTY 50")
82
- # Fyers FO Desc often matches the start of CM Desc
83
- if "-INDEX" in sym:
84
- temp_map[desc] = sym
85
-
86
- SPOT_MAPPING = temp_map
87
- print(f">>> [BG TASK] Spot Mapping Loaded: {len(SPOT_MAPPING)} symbols")
88
-
89
- except Exception as e:
90
- print(f"Error loading CM files: {e}")
91
 
92
  def update_master_db():
93
- global SYSTEM_STATUS, MASTER_DB, SEARCH_INDEX, SPOT_MAPPING
94
 
95
  if not UPDATE_LOCK.acquire(blocking=False):
96
  return
@@ -101,10 +70,6 @@ def update_master_db():
101
 
102
  headers = { "User-Agent": "Mozilla/5.0" }
103
 
104
- # Phase 1: Load Spot/Index Definitions (CM Files)
105
- load_spot_mapping(headers)
106
-
107
- # Phase 2: Load Derivatives (FO Files)
108
  # 1. NSE F&O
109
  url_nse = "https://public.fyers.in/sym_details/NSE_FO.csv"
110
  df_nse = pd.read_csv(
@@ -140,95 +105,25 @@ def update_master_db():
140
 
141
  temp_db = {}
142
 
143
- # Iterate over derivatives to group them
144
  for _, row in df.iterrows():
145
- sym = row['Symbol'] # e.g., NSE:NIFTY24JANFUT
146
- desc = row['Desc'] # e.g., NIFTY 50 24 JAN FUT
147
  exch = row['Exch']
148
 
149
- # --- LOGIC TO FIND ROOT EXACTLY AS IT IS ---
150
-
151
- # 1. Try to find underlying based on Description
152
- # Fyers FO Desc: "NIFTY 50 24 JAN..."
153
- # We check if "NIFTY 50" exists in our SPOT_MAPPING
154
- root_spot = ""
155
- desc_upper = desc.strip().upper()
156
-
157
- # Fast Check: Check common index names first or substring match
158
- # This dynamic lookup replaces the INDEX_MAP
159
- matched_spot = None
160
-
161
- # Optimization: Check valid index names
162
- # Extract potential name parts (e.g. "NIFTY 50" from "NIFTY 50 24...")
163
- desc_parts = desc_upper.split(' ')
164
-
165
- # Try 1 word, 2 words, 3 words combinations to find match in SPOT_MAPPING
166
- # e.g. "NIFTY", "NIFTY 50"
167
- for i in range(1, 4):
168
- if i > len(desc_parts): break
169
- check_name = " ".join(desc_parts[:i])
170
- if check_name in SPOT_MAPPING:
171
- matched_spot = SPOT_MAPPING[check_name]
172
- # Prefer Indices over Equities for similar names if needed,
173
- # but usually unique.
174
- if "-INDEX" in matched_spot:
175
- break
176
 
177
- if matched_spot:
178
- root_spot = matched_spot
179
- else:
180
- # Fallback for Stocks (RELIANCE, HDFCBANK)
181
- # If no CM description match (rare), construct standard Equity symbol
182
- # e.g. NSE:RELIANCE24... -> NSE:RELIANCE-EQ
183
- try:
184
- # Strip Exchange
185
- parts = sym.split(':')
186
- raw_sym = parts[1] if len(parts) > 1 else sym
187
-
188
- # Heuristic: Extract alpha prefix before numbers/dates
189
- # Simple regex-like logic manually
190
- import re
191
- # Match standard FO format: SYMBOL + DD + MON...
192
- # We just take the non-digit prefix for most stocks
193
- m = re.match(r"([A-Z\-\&]+)", raw_sym)
194
- if m:
195
- short_name = m.group(1)
196
- if exch == "NSE":
197
- root_spot = f"NSE:{short_name}-EQ"
198
- elif exch == "BSE":
199
- root_spot = f"BSE:{short_name}"
200
- elif exch == "MCX":
201
- # MCX often uses the Future as reference, leave blank to let client decide
202
- root_spot = ""
203
- except:
204
- root_spot = sym # Fallback
205
-
206
- # If we found a valid root spot (e.g. NSE:NIFTY50-INDEX), use that as the Key
207
- # This ensures the Search returns the EXACT Instrument ID.
208
-
209
- # If MCX/Commodity, we often group by the root name (CRUDEOIL)
210
- # because Spot symbols aren't always traded or consistent.
211
- if exch == "MCX":
212
- # Use simple root name for grouping key, but empty spot implies logic in v16.txt handles it
213
- match_mcx = desc_upper.split(' ')[0]
214
- db_key = match_mcx # e.g. CRUDEOIL
215
- # v16.txt logic: "If Spot is missing, default to Future".
216
- # So we leave spot empty for MCX unless we really know it.
217
- if not root_spot: root_spot = ""
218
- else:
219
- # For NSE/BSE, the Key IS the Spot Symbol (The "Exact Value")
220
- if root_spot:
221
- db_key = root_spot
222
  else:
223
- # Fallback to description first word if all else fails
224
- db_key = desc_parts[0]
225
-
226
- if db_key not in temp_db:
227
- temp_db[db_key] = {
228
- "spot": root_spot,
229
- "exch": exch,
230
- "items": []
231
- }
232
 
233
  try:
234
  exp = int(row['Expiry'])
@@ -250,29 +145,24 @@ def update_master_db():
250
  except:
251
  pass
252
 
253
- temp_db[db_key]["items"].append({
254
  "s": sym, "e": exp, "k": strike, "t": opt_type
255
  })
256
 
257
  final_db = {}
258
  search_list = []
259
 
260
- for key, data in temp_db.items():
261
- # Filter out junk groups with no items
262
- if not data["items"]: continue
263
-
264
  sorted_items = sorted(data["items"], key=lambda x: (x['e'], x['k']))
265
- final_db[key] = {
266
  "spot": data["spot"],
267
  "exch": data["exch"],
268
  "items": sorted_items
269
  }
270
-
271
- # Search Result: Display the Key exactly as it is (e.g. NSE:NIFTY50-INDEX)
272
  search_list.append({
273
- "root": key, # This is sent to v16.txt
274
  "exch": data["exch"],
275
- "display": key # User sees this
276
  })
277
 
278
  MASTER_DB = final_db
@@ -369,21 +259,17 @@ def search_symbol(q: str = Query(..., min_length=1)):
369
  return JSONResponse([])
370
 
371
  query = q.upper()
372
- # Filter: Check if query is in Root or Display
373
- results = [x for x in SEARCH_INDEX if query in x['root'] or query in x['display']]
374
-
375
- # Priority sorting: Starts with query first
376
- results.sort(key=lambda x: 0 if x['root'].startswith(query) else 1)
377
-
378
  return JSONResponse(results[:20])
379
 
380
  @app.get("/api/chain")
381
  def get_chain(symbol: str):
382
- root = symbol.upper() # This will now receive "NSE:NIFTY50-INDEX" directly
383
  if root in MASTER_DB:
384
  return JSONResponse(MASTER_DB[root])
385
- # Fallback: Try identifying simple root if full symbol fails (compatibility)
386
- # But usually, v16 sends exactly what search returns
387
  return JSONResponse({"error": "Symbol not found"}, status_code=404)
388
 
389
  @app.get("/api/refresh")
@@ -839,7 +725,7 @@ HTML_TEMPLATE = """
839
  const status = await res.json();
840
  if(status.ready) {
841
  document.getElementById('loader').style.display = 'none';
842
- if(!CURRENT_ROOT) loadSymbol('NIFTY 50');
843
  } else {
844
  document.getElementById('loaderMsg').innerText = status.message.toUpperCase();
845
  setTimeout(checkStatus, 2000);
@@ -878,9 +764,7 @@ HTML_TEMPLATE = """
878
  document.getElementById('loader').style.display = 'flex';
879
 
880
  try {
881
- // Encode root because it may contain characters like ':'
882
- const encRoot = encodeURIComponent(root);
883
- const res = await fetch(`/api/chain?symbol=${encRoot}`);
884
  if(!res.ok) throw new Error("Symbol not found");
885
  const data = await res.json();
886
 
@@ -891,13 +775,9 @@ HTML_TEMPLATE = """
891
 
892
  FUT_SYMS = CHAIN_DATA.filter(x => x.t === 'FUT').sort((a,b) => a.e - b.e).slice(0, 3);
893
 
894
- if(!SPOT_SYM && FUT_SYMS.length > 0) {
895
- // Fallback if spot is empty (e.g. MCX), use Future
896
- SPOT_SYM = FUT_SYMS[0].s;
897
- }
898
 
899
- const isIndex = SPOT_SYM.includes('INDEX') || SPOT_SYM.includes('-EQ') || SPOT_SYM.startsWith('BSE:');
900
- document.getElementById('spotLabel').innerText = isIndex ? "SPOT PRICE" : "FUT REFERENCE";
901
 
902
  setupExpiries();
903
  document.getElementById('loader').style.display = 'none';
@@ -983,11 +863,7 @@ HTML_TEMPLATE = """
983
  IS_POLLING = true;
984
 
985
  try {
986
- // Ensure SPOT_SYM is valid (not empty) before requesting
987
- let symbolsToFetch = [];
988
- if(SPOT_SYM && SPOT_SYM.length > 2) symbolsToFetch.push(SPOT_SYM);
989
- FUT_SYMS.forEach(f => { if(f.s) symbolsToFetch.push(f.s) });
990
-
991
  const kList = Array.from(document.querySelectorAll('.row')).map(r => parseFloat(r.dataset.k));
992
 
993
  if(kList.length > 0) {
 
36
  }
37
  UPDATE_LOCK = threading.Lock()
38
 
39
+ # Manual mapping for Index Spot symbols
40
+ INDEX_MAP = {
41
+ "NIFTY": "NSE:NIFTY50-INDEX",
42
+ "BANKNIFTY": "NSE:NIFTYBANK-INDEX",
43
+ "FINNIFTY": "NSE:FINNIFTY-INDEX",
44
+ "MIDCPNIFTY": "NSE:MIDCPNIFTY-INDEX",
45
+ "SENSEX": "BSE:SENSEX-INDEX",
46
+ "BANKEX": "BSE:BANKEX-INDEX",
47
+ "SENSEX50": "BSE:SENSEX50-INDEX"
48
+ }
49
+
50
+ def get_root_symbol(desc: str, symbol: str) -> str:
51
+ """Extracts root symbol (e.g. 'RELIANCE', 'SENSEX') from description."""
52
  try:
53
+ parts = desc.strip().split(' ')
54
+ root = parts[0].upper()
55
+ return root.replace(':', '').replace('-', '')
56
+ except:
57
+ if ':' in symbol:
58
+ return symbol.split(':')[1]
59
+ return symbol
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
  def update_master_db():
62
+ global SYSTEM_STATUS, MASTER_DB, SEARCH_INDEX
63
 
64
  if not UPDATE_LOCK.acquire(blocking=False):
65
  return
 
70
 
71
  headers = { "User-Agent": "Mozilla/5.0" }
72
 
 
 
 
 
73
  # 1. NSE F&O
74
  url_nse = "https://public.fyers.in/sym_details/NSE_FO.csv"
75
  df_nse = pd.read_csv(
 
105
 
106
  temp_db = {}
107
 
 
108
  for _, row in df.iterrows():
109
+ sym = row['Symbol']
110
+ desc = row['Desc']
111
  exch = row['Exch']
112
 
113
+ root = get_root_symbol(desc, sym)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
+ if root not in temp_db:
116
+ spot_sym = ""
117
+ if root in INDEX_MAP:
118
+ spot_sym = INDEX_MAP[root]
119
+ elif exch == "MCX":
120
+ spot_sym = "MCX"
121
+ elif exch == "BSE":
122
+ spot_sym = f"BSE:{root}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  else:
124
+ spot_sym = f"NSE:{root}-EQ"
125
+
126
+ temp_db[root] = { "spot": spot_sym, "exch": exch, "items": [] }
 
 
 
 
 
 
127
 
128
  try:
129
  exp = int(row['Expiry'])
 
145
  except:
146
  pass
147
 
148
+ temp_db[root]["items"].append({
149
  "s": sym, "e": exp, "k": strike, "t": opt_type
150
  })
151
 
152
  final_db = {}
153
  search_list = []
154
 
155
+ for root, data in temp_db.items():
 
 
 
156
  sorted_items = sorted(data["items"], key=lambda x: (x['e'], x['k']))
157
+ final_db[root] = {
158
  "spot": data["spot"],
159
  "exch": data["exch"],
160
  "items": sorted_items
161
  }
 
 
162
  search_list.append({
163
+ "root": root,
164
  "exch": data["exch"],
165
+ "display": f"{root}"
166
  })
167
 
168
  MASTER_DB = final_db
 
259
  return JSONResponse([])
260
 
261
  query = q.upper()
262
+ results = [x for x in SEARCH_INDEX if x['root'].startswith(query)]
263
+ if len(results) < 10:
264
+ results += [x for x in SEARCH_INDEX if query in x['root'] and x not in results]
265
+
 
 
266
  return JSONResponse(results[:20])
267
 
268
  @app.get("/api/chain")
269
  def get_chain(symbol: str):
270
+ root = symbol.upper()
271
  if root in MASTER_DB:
272
  return JSONResponse(MASTER_DB[root])
 
 
273
  return JSONResponse({"error": "Symbol not found"}, status_code=404)
274
 
275
  @app.get("/api/refresh")
 
725
  const status = await res.json();
726
  if(status.ready) {
727
  document.getElementById('loader').style.display = 'none';
728
+ if(!CURRENT_ROOT) loadSymbol('NIFTY');
729
  } else {
730
  document.getElementById('loaderMsg').innerText = status.message.toUpperCase();
731
  setTimeout(checkStatus, 2000);
 
764
  document.getElementById('loader').style.display = 'flex';
765
 
766
  try {
767
+ const res = await fetch(`/api/chain?symbol=${root}`);
 
 
768
  if(!res.ok) throw new Error("Symbol not found");
769
  const data = await res.json();
770
 
 
775
 
776
  FUT_SYMS = CHAIN_DATA.filter(x => x.t === 'FUT').sort((a,b) => a.e - b.e).slice(0, 3);
777
 
778
+ if(SPOT_SYM === "MCX" && FUT_SYMS.length > 0) SPOT_SYM = FUT_SYMS[0].s;
 
 
 
779
 
780
+ document.getElementById('spotLabel').innerText = (SPOT_SYM.includes('INDEX') || SPOT_SYM.includes('-EQ') || SPOT_SYM.startsWith('BSE:')) ? "SPOT PRICE" : "FUT REFERENCE";
 
781
 
782
  setupExpiries();
783
  document.getElementById('loader').style.display = 'none';
 
863
  IS_POLLING = true;
864
 
865
  try {
866
+ let symbolsToFetch = [SPOT_SYM, ...FUT_SYMS.map(x => x.s)];
 
 
 
 
867
  const kList = Array.from(document.querySelectorAll('.row')).map(r => parseFloat(r.dataset.k));
868
 
869
  if(kList.length > 0) {