topsecrettraders commited on
Commit
f83ba98
·
verified ·
1 Parent(s): 9c26cef

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -50
app.py CHANGED
@@ -36,30 +36,61 @@ SYSTEM_STATUS = {
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,6 +101,10 @@ def update_master_db():
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,25 +140,95 @@ def update_master_db():
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,24 +250,29 @@ def update_master_db():
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,17 +369,21 @@ def search_symbol(q: str = Query(..., min_length=1)):
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,7 +839,7 @@ HTML_TEMPLATE = """
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,7 +878,9 @@ HTML_TEMPLATE = """
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,9 +891,13 @@ HTML_TEMPLATE = """
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,7 +983,11 @@ HTML_TEMPLATE = """
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) {
 
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
 
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
 
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
  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
  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
  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
  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
 
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
  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) {