topsecrettraders commited on
Commit
fc2bfaa
·
verified ·
1 Parent(s): dacd67a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +282 -237
app.py CHANGED
@@ -2,10 +2,10 @@ import requests
2
  import uvicorn
3
  import hashlib
4
  import json
5
- import threading
6
  import time
 
 
7
  from datetime import datetime, timedelta
8
- from dateutil.relativedelta import relativedelta
9
  from fastapi import FastAPI, Request, Response, Cookie
10
  from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
11
  from typing import Optional
@@ -20,71 +20,122 @@ REDIRECT_URI = "https://topsecrettraders-depthchainindiaq.hf.space/callback"
20
  app = FastAPI()
21
 
22
  # ==========================================
23
- # 2. DATA ENGINE
24
  # ==========================================
25
 
26
- SYSTEM_STATUS = { "ready": False, "msg": "Initializing..." }
27
  DB = { "index": [], "stocks": [] }
28
  UPDATE_LOCK = threading.Lock()
29
 
30
- # Fallback List
31
- NIFTY_50_ROOTS = [
32
- "HDFCBANK", "RELIANCE", "ICICIBANK", "INFY", "ITC", "TCS", "LT", "AXISBANK",
33
- "KOTAKBANK", "SBIN", "BHARTIARTL", "BAJFINANCE", "ASIANPAINT", "MARUTI", "HCLTECH",
34
- "TITAN", "SUNPHARMA", "TATASTEEL", "NTPC", "ULTRACEMCO", "TATAMOTORS", "POWERGRID",
35
- "INDUSINDBK", "BAJAJFINSV", "HINDUNILVR", "NESTLEIND", "GRASIM", "JSWSTEEL", "TECHM",
36
- "ADANIENT", "HINDALCO", "CIPLA", "DRREDDY", "WIPRO", "SBILIFE", "COALINDIA",
37
- "TATACONSUM", "HDFCLIFE", "ADANIPORTS", "BAJAJ-AUTO", "APOLLOHOSP", "EICHERMOT",
38
- "DIVISLAB", "BPCL", "ONGC", "HEROMOTOCO", "BRITANNIA", "UPL", "LTIM", "M&M"
39
- ]
 
 
 
 
40
 
41
  def fetch_nse_weights():
 
42
  try:
43
- headers = { 'User-Agent': 'Mozilla/5.0' }
 
 
 
44
  s = requests.Session()
45
- s.get("https://www.nseindia.com", headers=headers, timeout=5)
46
  r = s.get("https://www.nseindia.com/api/equity-stockIndices?index=NIFTY 50", headers=headers, timeout=5)
 
47
  if r.status_code == 200:
48
  data = r.json().get('data', [])
49
  stocks = [x for x in data if x['priority'] == 0]
50
  tot = sum(x.get('ffmc', 0) for x in stocks)
51
- # Return rounded weights
52
- return {x['symbol']: round((x.get('ffmc', 0)/tot)*100, 2) for x in stocks}
53
- except: pass
54
- return {}
 
55
 
56
  def get_expiry_tag(offset=0):
57
- dt = datetime.now()
58
- if dt.day > 26 and offset == 0:
59
- dt = dt + relativedelta(months=1)
60
- tgt = dt + relativedelta(months=offset)
61
- return f"{tgt.strftime('%y')}{tgt.strftime('%b').upper()}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  def update_db():
64
  global SYSTEM_STATUS, DB
65
  if not UPDATE_LOCK.acquire(blocking=False): return
 
66
  try:
 
 
 
67
  weights = fetch_nse_weights()
68
- roots = list(weights.keys()) if weights else NIFTY_50_ROOTS
69
 
 
70
  final_stocks = []
71
  for root in roots:
72
  futs = []
73
- for i in range(3):
74
  tag = get_expiry_tag(i)
 
75
  futs.append({"s": f"NSE:{root}{tag}FUT"})
76
- final_stocks.append({"r": root, "w": weights.get(root, 0), "f": futs})
 
 
 
 
 
77
 
 
78
  idx_futs = []
79
  for i in range(3):
80
  tag = get_expiry_tag(i)
81
  idx_futs.append({"s": f"NSE:NIFTY{tag}FUT"})
82
 
 
83
  final_stocks.sort(key=lambda x: x['w'], reverse=True)
 
84
  DB = { "index": idx_futs, "stocks": final_stocks }
85
  SYSTEM_STATUS["ready"] = True
86
- except: pass
87
- finally: UPDATE_LOCK.release()
 
 
 
 
 
 
88
 
89
  @app.on_event("startup")
90
  def startup():
@@ -92,7 +143,7 @@ def startup():
92
  t.start()
93
 
94
  # ==========================================
95
- # 3. ROUTES
96
  # ==========================================
97
 
98
  @app.get("/")
@@ -101,7 +152,13 @@ def home(access_token: Optional[str] = Cookie(None)):
101
  state = "init"
102
  enc_redirect = urllib.parse.quote(REDIRECT_URI, safe="")
103
  auth_url = (f"https://api-t1.fyers.in/api/v3/generate-authcode?client_id={CLIENT_ID}&redirect_uri={enc_redirect}&response_type=code&state={state}")
104
- return HTMLResponse(f"""<body style="background:#000;color:#fff;display:flex;justify-content:center;align-items:center;height:100vh;"><div><h2>NIFTY PRO v6</h2><a href="{auth_url}" style="color:#2979ff">LOGIN</a></div></body>""")
 
 
 
 
 
 
105
  return HTMLResponse(FRONTEND.replace("{{USER_TOKEN}}", access_token))
106
 
107
  @app.get("/callback")
@@ -115,23 +172,37 @@ def callback(auth_code: Optional[str]=None, code: Optional[str]=None):
115
  return resp
116
  return JSONResponse(r.json())
117
 
118
- @app.get("/api/config")
119
- def get_conf(expiry: int = 0):
120
- idx = max(0, min(expiry, 2))
121
- if not DB["index"]: return JSONResponse({"s":"wait"})
122
- stk_list = []
123
- for s in DB["stocks"]:
124
- stk_list.append({"r": s["r"], "w": s["w"], "s": s["f"][idx]["s"]})
125
- return JSONResponse({"s": "ok", "idx": DB["index"][idx]["s"], "stocks": stk_list})
126
-
127
  @app.get("/logout")
128
  def logout():
129
  r = RedirectResponse("/")
130
  r.delete_cookie("access_token")
131
  return r
132
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  # ==========================================
134
- # 4. FRONTEND (V6 COMPACT UI)
135
  # ==========================================
136
 
137
  FRONTEND = """
@@ -141,142 +212,112 @@ FRONTEND = """
141
  <meta charset="UTF-8">
142
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
143
  <title>NIFTY 50 PRO</title>
144
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700;800&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
145
  <style>
146
  :root {
147
- --bg: #000000;
148
- --panel: #0d0d0d;
149
  --border: #222;
150
  --accent: #2962ff;
151
- --text-main: #fff;
152
- --text-sub: #666;
153
  --green: #00c853;
154
  --red: #d50000;
155
- --card-bull: linear-gradient(180deg, rgba(0, 50, 20, 0.9), rgba(0, 25, 10, 0.9));
156
- --card-bear: linear-gradient(180deg, rgba(50, 0, 0, 0.9), rgba(25, 0, 0, 0.9));
 
157
  }
158
  * { box-sizing: border-box; margin: 0; padding: 0; outline: none; }
159
- body { background: var(--bg); color: var(--text-main); font-family: 'Inter', sans-serif; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
160
 
161
- /* HEADER */
162
- .head { height: 40px; background: var(--panel); border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; padding: 0 15px; flex-shrink: 0; }
163
- .logo { font-weight: 900; font-size: 13px; letter-spacing: 0.5px; } .logo span { color: var(--accent); }
164
-
165
  .ctrls { display: flex; gap: 8px; }
166
- .btn { background: #1a1a1a; color: #ccc; border: 1px solid #333; padding: 3px 10px; border-radius: 4px; font-size: 10px; font-weight: 700; cursor: pointer; transition: 0.2s; }
167
- .btn:hover { border-color: var(--accent); color: #fff; }
168
- .btn.active { background: var(--accent); color: white; border-color: var(--accent); }
169
- .btn-exp { width: 100px; }
170
-
171
- /* LAYOUT */
172
- .layout { display: flex; flex: 1; overflow: hidden; }
173
 
174
- /* SIDEBAR (LEFT) */
175
- .side { width: 220px; background: var(--panel); border-right: 1px solid var(--border); display: flex; flex-direction: column; flex-shrink: 0; }
176
- .side-head { padding: 10px; font-size: 10px; font-weight: 800; color: var(--text-sub); border-bottom: 1px solid var(--border); letter-spacing: 1px; }
177
  .agg-list { flex: 1; overflow-y: auto; padding: 10px; }
178
- .agg-card { background: #161616; border-radius: 4px; padding: 8px; margin-bottom: 6px; border: 1px solid #222; }
179
- .agg-name { font-size: 9px; font-weight: 700; color: #888; display: flex; justify-content: space-between; }
180
- .agg-val { font-family: 'JetBrains Mono'; font-size: 13px; font-weight: 800; margin-top: 2px; }
181
 
182
- /* CONTENT AREA */
183
  .content { flex: 1; display: flex; flex-direction: column; overflow: hidden; background: var(--bg); }
 
 
 
 
184
 
185
- /* INDEX STRIP */
186
- .idx-strip { height: 40px; background: #080808; border-bottom: 1px solid var(--border); display: flex; align-items: center; padding: 0 15px; gap: 20px; flex-shrink: 0; font-family: 'JetBrains Mono'; }
187
- .idx-l { font-size: 11px; font-weight: 800; color: var(--accent); }
188
- .idx-v { font-size: 14px; font-weight: 800; color: #fff; }
189
- .idx-c { font-size: 11px; font-weight: 700; }
190
- .idx-n { font-size: 11px; font-weight: 800; margin-left: auto; color: #888; }
191
-
192
- /* GRID */
193
  .grid-wrap { flex: 1; overflow-y: auto; padding: 10px; }
194
- /* Auto-fit small cards to maximize space */
195
- .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 6px; }
196
-
197
- /* CARD DESIGN (COMPACT MODE) */
198
- .card {
199
- background: #111;
200
- border: 1px solid #222;
201
- border-radius: 4px;
202
- padding: 8px;
203
- height: 65px; /* Compact Fixed Height */
204
- display: flex;
205
- flex-direction: column;
206
- justify-content: space-between;
207
- position: relative;
208
- transition: 0.1s;
209
- overflow: hidden;
210
- }
211
-
212
- .card.bull { background: var(--card-bull); border-color: #004d20; }
213
- .card.bear { background: var(--card-bear); border-color: #4d0000; }
214
-
215
- .c-head { display: flex; justify-content: space-between; align-items: center; z-index: 2; }
216
- .sym { font-size: 11px; font-weight: 800; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 90px; }
217
- /* Subtle Weightage pill */
218
- .wgt { font-family: 'JetBrains Mono'; font-size: 8px; color: rgba(255,255,255,0.5); background: rgba(0,0,0,0.3); padding: 1px 3px; border-radius: 2px; }
219
 
220
- /* The Big Value */
221
- .c-main { text-align: right; z-index: 2; margin-top: auto; }
222
- .calc-res { font-family: 'JetBrains Mono'; font-size: 13px; font-weight: 800; color: #fff; }
223
 
224
- /* Details (Hidden by default) */
225
- .c-det { display: none; margin-top: 5px; padding-top: 5px; border-top: 1px solid rgba(255,255,255,0.1); font-size: 9px; font-family: 'JetBrains Mono'; color: #ccc; }
226
- .det-row { display: flex; justify-content: space-between; margin-bottom: 2px; }
 
227
 
228
- /* EXPANDED MODE CSS overrides */
229
- .grid.expanded .card { height: 110px; }
230
- .grid.expanded .c-det { display: block; }
 
 
231
 
232
- /* UTILS */
233
  .up { color: var(--green); } .dn { color: var(--red); }
234
- ::-webkit-scrollbar { width: 4px; } ::-webkit-scrollbar-thumb { background: #333; }
235
 
236
- /* Modal */
237
- .modal { position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);z-index:99;display:none;justify-content:center;align-items:center; }
238
- .mbox { background:#111;border:1px solid #333;width:400px;padding:20px;border-radius:6px; }
239
- textarea { width:100%;height:120px;background:#000;color:#ccc;border:1px solid #333;font-family:monospace;font-size:11px;padding:5px; margin-top:5px; }
240
  </style>
241
  </head>
242
  <body>
243
 
244
- <!-- SETTINGS MODAL -->
245
- <div class="modal" id="mod">
246
- <div class="mbox">
247
- <h4 style="margin-bottom:10px;color:#fff;">FORMULA CONFIG (JSON)</h4>
248
- <input id="cf" type="text" style="width:100%;background:#000;color:#fff;border:1px solid #333;padding:5px;margin-bottom:10px;font-family:monospace;">
249
- <textarea id="ag"></textarea>
250
- <div style="margin-top:10px;display:flex;gap:5px;">
251
- <button class="btn active" style="flex:1" onclick="save()">SAVE</button>
252
- <button class="btn" style="flex:1" onclick="document.getElementById('mod').style.display='none'">CLOSE</button>
 
253
  </div>
254
  </div>
255
  </div>
256
 
257
  <div class="head">
258
- <div class="logo">NIFTY<span>50</span> V6</div>
259
  <div class="ctrls">
260
- <button class="btn" id="btnMode" onclick="toggleMode()">SHOW DETAILS</button>
261
- <select class="btn btn-exp" id="sort" onchange="render()"><option value="w">WEIGHT</option><option value="v">VALUE</option></select>
262
- <select class="btn btn-exp" id="exp" onchange="init()"><option value="0">NEAR</option><option value="1">NEXT</option></select>
263
- <button class="btn" onclick="document.getElementById('mod').style.display='flex'">⚙</button>
264
- <button class="btn" style="color:var(--red)" onclick="location.href='/logout'">X</button>
265
  </div>
266
  </div>
267
 
268
  <div class="layout">
269
  <div class="side">
270
- <div class="side-head">LIVE AGGREGATES</div>
271
- <div class="agg-list" id="agg"></div>
272
  </div>
273
-
274
  <div class="content">
275
  <div class="idx-strip">
276
- <span class="idx-l">NIFTY FUT</span>
277
- <span class="idx-v" id="ixP">--</span>
278
- <span class="idx-c" id="ixC">--%</span>
279
- <span class="idx-n" id="ixN">NET: --</span>
 
 
 
 
280
  </div>
281
  <div class="grid-wrap">
282
  <div id="grid" class="grid"></div>
@@ -285,71 +326,78 @@ FRONTEND = """
285
  </div>
286
 
287
  <script>
288
- const T = "{{USER_TOKEN}}";
289
- let C = {
290
- cf: "(TBQ - TSQ) * LTP * WGT",
291
- ag: [
292
  { "name": "NET MONEY FLOW", "formula": "(TBQ - TSQ) * LTP * WGT" },
293
  { "name": "WEIGHTED DELTA", "formula": "(TBQ - TSQ) * LTP * WGT" },
294
  { "name": "BUY PRESSURE", "formula": "TBQ * LTP * WGT" },
295
  { "name": "SELL PRESSURE", "formula": "-1 * TSQ * LTP * WGT" }
296
  ]
297
  };
298
-
299
- let API = null, D = {}, P = null;
300
- let EXPANDED = false;
301
 
302
  window.onload = () => {
303
- const s = localStorage.getItem('v6_cfg');
304
- if(s) C = JSON.parse(s);
305
- document.getElementById('cf').value = C.cf;
306
- document.getElementById('ag').value = JSON.stringify(C.ag, null, 2);
307
  init();
308
  };
309
 
310
- function toggleMode() {
311
- EXPANDED = !EXPANDED;
312
- document.getElementById('grid').className = EXPANDED ? "grid expanded" : "grid";
313
- document.getElementById('btnMode').className = EXPANDED ? "btn active" : "btn";
314
- document.getElementById('btnMode').innerText = EXPANDED ? "HIDE DETAILS" : "SHOW DETAILS";
315
  }
316
 
317
  async function init() {
318
- document.getElementById('grid').innerHTML = '<div style="color:#666;padding:20px;">CONNECTING...</div>';
 
319
  try {
320
- let r = await fetch(`/api/config?expiry=${document.getElementById('exp').value}`);
321
- let j = await r.json();
322
- if(j.s === 'ok') { API = j; render(); poll(); }
323
- } catch(e){}
 
324
  }
325
 
326
- function poll() {
327
- if(P) clearInterval(P);
328
- fetchD();
329
- P = setInterval(fetchD, 1000);
330
  }
331
 
332
- async function fetchD() {
333
- if(!API) return;
334
- const s = [API.idx, ...API.stocks.map(x=>x.s)];
335
  const b1 = s.slice(0,40); const b2 = s.slice(40);
336
- let nd = {};
337
- const call = async (l) => {
338
- if(!l.length) return;
339
- const q = l.map(x=>encodeURIComponent(x)).join(',');
340
- let r = await fetch(`https://api-t1.fyers.in/data/depth?symbol=${q}&ohlcv_flag=1`,{headers:{'Authorization':T}});
341
- let j = await r.json();
342
- if(j.s==='ok') Object.assign(nd, j.d);
343
- };
344
- await Promise.all([call(b1), call(b2)]);
345
- D = nd;
346
- upd();
 
 
347
  }
348
 
349
- function evalF(f, d, w) {
350
  if(!d) return 0;
351
- try { return new Function('TBQ','TSQ','LTP','WGT','return '+f)(d.totalbuyqty||0, d.totalsellqty||0, d.ltp||0, w||0); }
352
- catch(e){ return 0; }
 
 
 
 
 
353
  }
354
 
355
  function fmt(n) {
@@ -361,94 +409,91 @@ FRONTEND = """
361
  return s+(a/1000).toFixed(0)+" k";
362
  }
363
 
364
- function upd() {
365
- // Index
366
- const i = D[API.idx];
367
  if(i) {
368
  document.getElementById('ixP').innerText = i.ltp;
369
  const c = i.chp||0;
370
  const ce = document.getElementById('ixC');
371
- ce.innerText = c.toFixed(2)+"%"; ce.style.color = c>=0?"var(--green)":"var(--red)";
372
- const n = (i.totalbuyqty||0)-(i.totalsellqty||0);
373
- const ne = document.getElementById('ixN');
374
- ne.innerText = "NET: "+fmt(n); ne.style.color = n>=0?"var(--green)":"var(--red)";
375
  }
376
 
377
- // Sidebar
378
- const sums = new Array(C.ag.length).fill(0);
379
 
380
- API.stocks.forEach(stk => {
381
- const d = D[stk.s];
382
  if(!d) return;
383
-
384
- C.ag.forEach((ag, k) => sums[k] += evalF(ag.formula, d, stk.w));
385
-
386
  const el = document.getElementById(`c-${stk.r}`);
387
  if(el) {
388
- // Colorize Background
389
- const val = evalF(C.cf, d, stk.w);
390
- el.className = "card "+(val>=0?"bull":"bear");
391
- el.querySelector('.calc-res').innerText = fmt(val);
392
-
393
- // Update Details if Visible
394
- if(EXPANDED) {
395
- el.querySelector('.ltp').innerText = d.ltp;
396
  const c = d.chp||0;
397
- el.querySelector('.chg').innerText = c.toFixed(2)+"%";
398
- el.querySelector('.chg').className = "chg "+(c>=0?"up":"dn");
399
- el.querySelector('.bv').innerText = fmt(d.totalbuyqty);
400
- el.querySelector('.sv').innerText = fmt(d.totalsellqty);
401
  }
402
  }
403
  });
404
 
405
- // Sidebar Render
406
- document.getElementById('agg').innerHTML = C.ag.map((ag, k) => `
407
  <div class="agg-card">
408
- <div class="agg-name"><span>${ag.name}</span></div>
409
- <div class="agg-val" style="color:${sums[k]>=0?'var(--green)':'var(--red)'}">${fmt(sums[k])}</div>
410
  </div>
411
  `).join('');
412
  }
413
 
414
  function render() {
415
- if(!API) return;
416
- const sort = document.getElementById('sort').value;
417
- let l = [...API.stocks];
418
- if(sort === 'v') {
419
- l.sort((a,b) => {
420
- const va = evalF(C.cf, D[a.s], a.w);
421
- const vb = evalF(C.cf, D[b.s], b.w);
422
- return Math.abs(vb) - Math.abs(va); // Sort by Magnitude
423
  });
424
  }
425
-
426
- document.getElementById('grid').innerHTML = l.map(s => `
427
- <div class="card" id="c-${s.r}">
428
- <div class="c-head">
429
- <span class="sym">${s.r}</span>
430
- <span class="wgt">${s.w.toFixed(2)}%</span>
431
- </div>
432
  <div class="c-main">
433
- <span class="calc-res">WAIT</span>
 
 
 
 
434
  </div>
435
- <div class="c-det">
436
- <div class="det-row"><span class="ltp">--</span><span class="chg">--%</span></div>
437
- <div class="det-row"><span>B: <span class="bv">--</span></span><span>S: <span class="sv">--</span></span></div>
 
 
438
  </div>
439
  </div>
440
  `).join('');
441
-
442
- if(Object.keys(D).length>0) upd();
443
  }
444
 
445
- function save() {
 
 
446
  try {
447
- C = { cf: document.getElementById('cf').value, ag: JSON.parse(document.getElementById('ag').value) };
448
- localStorage.setItem('v6_cfg', JSON.stringify(C));
449
- document.getElementById('mod').style.display='none';
450
- render(); upd();
451
- } catch(e){ alert('Error'); }
 
452
  }
453
  </script>
454
  </body>
 
2
  import uvicorn
3
  import hashlib
4
  import json
 
5
  import time
6
+ import threading
7
+ import urllib.parse
8
  from datetime import datetime, timedelta
 
9
  from fastapi import FastAPI, Request, Response, Cookie
10
  from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
11
  from typing import Optional
 
20
  app = FastAPI()
21
 
22
  # ==========================================
23
+ # 2. ROBUST DATA ENGINE
24
  # ==========================================
25
 
26
+ SYSTEM_STATUS = { "ready": False, "msg": "Booting..." }
27
  DB = { "index": [], "stocks": [] }
28
  UPDATE_LOCK = threading.Lock()
29
 
30
+ # BACKUP DATA (Used if NSE blocks the connection)
31
+ BACKUP_NIFTY_50 = {
32
+ "HDFCBANK": 13.52, "RELIANCE": 9.81, "ICICIBANK": 7.89, "INFY": 5.80, "ITC": 4.41,
33
+ "TCS": 4.02, "L&T": 3.66, "AXISBANK": 3.23, "KOTAKBANK": 2.92, "SBIN": 2.89,
34
+ "BHARTIARTL": 2.68, "BAJFINANCE": 2.30, "ASIANPAINT": 1.76, "MARUTI": 1.57,
35
+ "HCLTECH": 1.54, "TITAN": 1.48, "SUNPHARMA": 1.40, "TATASTEEL": 1.34, "NTPC": 1.18,
36
+ "ULTRACEMO": 1.11, "TATAMOTORS": 1.09, "POWERGRID": 1.04, "INDUSINDBK": 0.98,
37
+ "BAJAJFINSV": 0.86, "HINDUNILVR": 2.50, "NESTLEIND": 0.91, "GRASIM": 0.83,
38
+ "JSWSTEEL": 0.79, "TECHM": 0.78, "ADANIENT": 0.75, "HINDALCO": 0.72, "CIPLA": 0.69,
39
+ "DRREDDY": 0.65, "WIPRO": 0.64, "SBILIFE": 0.63, "COALINDIA": 0.60, "TATACONSUM": 0.59,
40
+ "HDFCLIFE": 0.58, "ADANIPORTS": 0.57, "BAJAJ-AUTO": 0.56, "APOLLOHOSP": 0.55,
41
+ "EICHERMOT": 0.52, "DIVISLAB": 0.47, "BPCL": 0.44, "ONGC": 0.42, "HEROMOTOCO": 0.38,
42
+ "BRITANNIA": 0.36, "UPL": 0.35, "LTIM": 0.35, "M&M": 2.6
43
+ }
44
 
45
  def fetch_nse_weights():
46
+ """Tries to get live weights, returns fallback if fails"""
47
  try:
48
+ headers = {
49
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
50
+ 'Referer': 'https://www.nseindia.com/'
51
+ }
52
  s = requests.Session()
53
+ s.get("https://www.nseindia.com", headers=headers, timeout=3) # Fast timeout
54
  r = s.get("https://www.nseindia.com/api/equity-stockIndices?index=NIFTY 50", headers=headers, timeout=5)
55
+
56
  if r.status_code == 200:
57
  data = r.json().get('data', [])
58
  stocks = [x for x in data if x['priority'] == 0]
59
  tot = sum(x.get('ffmc', 0) for x in stocks)
60
+ if tot > 0:
61
+ return {x['symbol']: (x.get('ffmc', 0)/tot)*100 for x in stocks}
62
+ except Exception as e:
63
+ print(f"NSE Fetch Error (Using Backup): {e}")
64
+ return BACKUP_NIFTY_50
65
 
66
  def get_expiry_tag(offset=0):
67
+ """
68
+ Generates '26FEB' style tag.
69
+ Logic: If today > 26th, we assume the 'current' month is actually next month.
70
+ """
71
+ now = datetime.now()
72
+ # Move to the target month based on offset
73
+ # If today is late in the month (e.g. 30th), 'Current' expiry is likely next month
74
+ if now.day > 26:
75
+ offset += 1
76
+
77
+ # Calculate Year and Month safely without dateutil
78
+ year = now.year
79
+ month = now.month + offset
80
+
81
+ # Handle year rollover
82
+ while month > 12:
83
+ month -= 12
84
+ year += 1
85
+
86
+ # Create date object to get Month Name
87
+ d = datetime(year, month, 1)
88
+
89
+ # Format: 26FEB (YYMON)
90
+ yy = d.strftime("%y")
91
+ mon = d.strftime("%b").upper()
92
+ return f"{yy}{mon}"
93
 
94
  def update_db():
95
  global SYSTEM_STATUS, DB
96
  if not UPDATE_LOCK.acquire(blocking=False): return
97
+
98
  try:
99
+ SYSTEM_STATUS["msg"] = "Updating Data..."
100
+
101
+ # 1. Get Weights (Live or Backup)
102
  weights = fetch_nse_weights()
103
+ roots = list(weights.keys())
104
 
105
+ # 2. Construct Stocks
106
  final_stocks = []
107
  for root in roots:
108
  futs = []
109
+ for i in range(3): # Current, Next, Far
110
  tag = get_expiry_tag(i)
111
+ # Correctly format M&M to match Fyers (NSE:M&M...)
112
  futs.append({"s": f"NSE:{root}{tag}FUT"})
113
+
114
+ final_stocks.append({
115
+ "r": root,
116
+ "w": weights.get(root, 0),
117
+ "f": futs
118
+ })
119
 
120
+ # 3. Construct Index
121
  idx_futs = []
122
  for i in range(3):
123
  tag = get_expiry_tag(i)
124
  idx_futs.append({"s": f"NSE:NIFTY{tag}FUT"})
125
 
126
+ # 4. Sort by Weightage
127
  final_stocks.sort(key=lambda x: x['w'], reverse=True)
128
+
129
  DB = { "index": idx_futs, "stocks": final_stocks }
130
  SYSTEM_STATUS["ready"] = True
131
+ SYSTEM_STATUS["msg"] = "Ready"
132
+ print(f"DB Updated: {len(final_stocks)} Stocks Loaded")
133
+
134
+ except Exception as e:
135
+ print(f"DB Error: {e}")
136
+ SYSTEM_STATUS["msg"] = "DB Error"
137
+ finally:
138
+ UPDATE_LOCK.release()
139
 
140
  @app.on_event("startup")
141
  def startup():
 
143
  t.start()
144
 
145
  # ==========================================
146
+ # 3. ROUTES & API
147
  # ==========================================
148
 
149
  @app.get("/")
 
152
  state = "init"
153
  enc_redirect = urllib.parse.quote(REDIRECT_URI, safe="")
154
  auth_url = (f"https://api-t1.fyers.in/api/v3/generate-authcode?client_id={CLIENT_ID}&redirect_uri={enc_redirect}&response_type=code&state={state}")
155
+ return HTMLResponse(f"""
156
+ <body style="background:#050505; color:#fff; display:flex; height:100vh; justify-content:center; align-items:center; font-family:sans-serif;">
157
+ <div style="text-align:center; border:1px solid #333; padding:40px; border-radius:10px; background:#111;">
158
+ <h2 style="color:#2962ff; letter-spacing:1px; margin-bottom:20px;">NIFTY 50 PRO</h2>
159
+ <a href="{auth_url}" style="padding:12px 30px; background:#2962ff; color:white; text-decoration:none; border-radius:6px; font-weight:bold;">LOGIN WITH FYERS</a>
160
+ </div>
161
+ </body>""")
162
  return HTMLResponse(FRONTEND.replace("{{USER_TOKEN}}", access_token))
163
 
164
  @app.get("/callback")
 
172
  return resp
173
  return JSONResponse(r.json())
174
 
 
 
 
 
 
 
 
 
 
175
  @app.get("/logout")
176
  def logout():
177
  r = RedirectResponse("/")
178
  r.delete_cookie("access_token")
179
  return r
180
 
181
+ @app.get("/api/config")
182
+ def get_conf(expiry: int = 0):
183
+ """Safely returns config without crashing"""
184
+ try:
185
+ if not SYSTEM_STATUS["ready"] or not DB["index"]:
186
+ # Trigger update if empty
187
+ if not UPDATE_LOCK.locked(): threading.Thread(target=update_db).start()
188
+ return JSONResponse({"s":"wait", "msg": SYSTEM_STATUS["msg"]})
189
+
190
+ idx = max(0, min(expiry, 2))
191
+
192
+ stk_list = []
193
+ for s in DB["stocks"]:
194
+ # Safe Access
195
+ sym = s["f"][idx]["s"] if len(s["f"]) > idx else s["f"][0]["s"]
196
+ stk_list.append({"r": s["r"], "w": s["w"], "s": sym})
197
+
198
+ idx_sym = DB["index"][idx]["s"] if len(DB["index"]) > idx else DB["index"][0]["s"]
199
+
200
+ return JSONResponse({"s": "ok", "idx": idx_sym, "stocks": stk_list})
201
+ except Exception as e:
202
+ return JSONResponse({"s": "error", "msg": str(e)})
203
+
204
  # ==========================================
205
+ # 4. FRONTEND (V6 Compact)
206
  # ==========================================
207
 
208
  FRONTEND = """
 
212
  <meta charset="UTF-8">
213
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
214
  <title>NIFTY 50 PRO</title>
215
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700;800&family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet">
216
  <style>
217
  :root {
218
+ --bg: #050505;
219
+ --panel: #0f0f0f;
220
  --border: #222;
221
  --accent: #2962ff;
222
+ --text: #e0e0e0;
 
223
  --green: #00c853;
224
  --red: #d50000;
225
+ --card-bg: #121212;
226
+ --bull-bg: rgba(0, 200, 83, 0.1);
227
+ --bear-bg: rgba(213, 0, 0, 0.1);
228
  }
229
  * { box-sizing: border-box; margin: 0; padding: 0; outline: none; }
230
+ body { background: var(--bg); color: var(--text); font-family: 'Inter', sans-serif; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
231
 
232
+ .head { height: 45px; background: var(--panel); border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; padding: 0 15px; flex-shrink: 0; }
233
+ .logo { font-weight: 800; font-size: 14px; letter-spacing: 0.5px; } .logo span { color: var(--accent); }
 
 
234
  .ctrls { display: flex; gap: 8px; }
235
+ .btn { background: #1a1a1a; color: #ccc; border: 1px solid #333; padding: 4px 10px; border-radius: 4px; font-size: 10px; font-weight: 700; cursor: pointer; font-family: 'Inter'; transition: 0.2s; }
236
+ .btn:hover { border-color: var(--accent); color: #fff; background: #252525; }
237
+ .btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
 
 
 
 
238
 
239
+ .layout { display: flex; flex: 1; overflow: hidden; }
240
+ .side { width: 200px; background: var(--panel); border-right: 1px solid var(--border); display: flex; flex-direction: column; flex-shrink: 0; }
241
+ .side-head { padding: 12px; font-size: 10px; font-weight: 800; color: #666; border-bottom: 1px solid var(--border); letter-spacing: 1px; }
242
  .agg-list { flex: 1; overflow-y: auto; padding: 10px; }
243
+ .agg-card { background: #181818; border-radius: 4px; padding: 8px; margin-bottom: 8px; border: 1px solid #2a2a2a; }
244
+ .agg-name { font-size: 9px; font-weight: 700; color: #888; margin-bottom: 2px; }
245
+ .agg-val { font-family: 'JetBrains Mono'; font-size: 14px; font-weight: 800; }
246
 
 
247
  .content { flex: 1; display: flex; flex-direction: column; overflow: hidden; background: var(--bg); }
248
+ .idx-strip { height: 50px; background: #0b0b0b; border-bottom: 1px solid var(--border); display: flex; align-items: center; padding: 0 20px; gap: 30px; flex-shrink: 0; }
249
+ .idx-lbl { font-size: 10px; font-weight: 800; color: var(--accent); letter-spacing: 1px; }
250
+ .idx-val { font-family: 'JetBrains Mono'; font-size: 18px; font-weight: 800; color: #fff; }
251
+ .idx-meta { display: flex; gap: 15px; font-family: 'JetBrains Mono'; font-size: 12px; font-weight: 700; align-items: center; }
252
 
 
 
 
 
 
 
 
 
253
  .grid-wrap { flex: 1; overflow-y: auto; padding: 10px; }
254
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 5px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
+ .card { background: var(--card-bg); border: 1px solid #222; border-radius: 4px; padding: 8px; display: flex; flex-direction: column; justify-content: center; min-height: 50px; position: relative; transition: all 0.2s; cursor: default; }
257
+ .card.bull { background: var(--bull-bg); border-color: rgba(0, 200, 83, 0.3); }
258
+ .card.bear { background: var(--bear-bg); border-color: rgba(213, 0, 0, 0.3); }
259
 
260
+ .c-main { display: flex; justify-content: space-between; align-items: center; }
261
+ .sym { font-size: 11px; font-weight: 800; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 70px; }
262
+ .wgt-badge { font-size: 8px; font-family: 'JetBrains Mono'; color: #666; background: rgba(255,255,255,0.05); padding: 1px 3px; border-radius: 2px; }
263
+ .calc-big { font-family: 'JetBrains Mono'; font-size: 12px; font-weight: 800; text-align: right; }
264
 
265
+ .c-details { display: none; margin-top: 8px; padding-top: 6px; border-top: 1px solid rgba(255,255,255,0.1); font-family: 'JetBrains Mono'; }
266
+ .card.expanded { min-height: 90px; justify-content: space-between; }
267
+ .card.expanded .c-details { display: block; }
268
+ .det-row { display: flex; justify-content: space-between; font-size: 9px; margin-bottom: 2px; color: #aaa; }
269
+ .det-val { font-weight: 700; color: #eee; }
270
 
 
271
  .up { color: var(--green); } .dn { color: var(--red); }
272
+ ::-webkit-scrollbar { width: 5px; } ::-webkit-scrollbar-thumb { background: #333; border-radius: 2px; }
273
 
274
+ .modal { position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.85); z-index: 100; display: none; justify-content: center; align-items: center; }
275
+ .modal-box { background: var(--panel); width: 450px; padding: 20px; border-radius: 6px; border: 1px solid var(--border); }
276
+ textarea { width: 100%; height: 150px; background: #000; color: #fff; border: 1px solid #333; padding: 10px; font-family: 'JetBrains Mono'; font-size: 11px; margin-top: 5px; }
 
277
  </style>
278
  </head>
279
  <body>
280
 
281
+ <div class="modal" id="setModal">
282
+ <div class="modal-box">
283
+ <h3 style="color:#fff; font-size:12px; margin-bottom:10px;">FORMULA CONFIG</h3>
284
+ <label style="font-size:9px; font-weight:700; color:var(--accent);">CARD FORMULA</label>
285
+ <input type="text" id="cardForm" style="width:100%; background:#000; border:1px solid #333; color:#fff; padding:6px; font-family:'JetBrains Mono'; font-size:11px; margin-bottom:10px;">
286
+ <label style="font-size:9px; font-weight:700; color:var(--accent);">AGGREGATES (JSON)</label>
287
+ <textarea id="aggJson"></textarea>
288
+ <div style="display:flex; gap:8px; margin-top:15px;">
289
+ <button class="btn active" style="flex:1" onclick="saveConfig()">SAVE</button>
290
+ <button class="btn" style="flex:1" onclick="closeModal()">CANCEL</button>
291
  </div>
292
  </div>
293
  </div>
294
 
295
  <div class="head">
296
+ <div class="logo">NIFTY<span>50</span> PRO</div>
297
  <div class="ctrls">
298
+ <button class="btn" id="togBtn" onclick="toggleDetails()">SHOW DETAILS: OFF</button>
299
+ <select id="sortSel" class="btn" onchange="render()"><option value="w">SORT: WEIGHT</option><option value="calc_h">SORT: VALUE HIGH</option><option value="calc_l">SORT: VALUE LOW</option></select>
300
+ <select id="expSel" class="btn" onchange="init()"><option value="0">NEAR</option><option value="1">NEXT</option></select>
301
+ <button class="btn" onclick="openModal()">⚙</button>
302
+ <button class="btn" style="color:var(--red)" onclick="location.href='/logout'">EXIT</button>
303
  </div>
304
  </div>
305
 
306
  <div class="layout">
307
  <div class="side">
308
+ <div class="side-head">AGGREGATES</div>
309
+ <div class="agg-list" id="aggList"></div>
310
  </div>
 
311
  <div class="content">
312
  <div class="idx-strip">
313
+ <div>
314
+ <div class="idx-lbl">NIFTY FUT</div>
315
+ <div class="idx-val" id="ixP">--.--</div>
316
+ </div>
317
+ <div class="idx-meta">
318
+ <span id="ixC">--%</span>
319
+ <span id="ixQ" style="color:#888; border-left:1px solid #333; padding-left:15px;">NET: --</span>
320
+ </div>
321
  </div>
322
  <div class="grid-wrap">
323
  <div id="grid" class="grid"></div>
 
326
  </div>
327
 
328
  <script>
329
+ const TOKEN = "{{USER_TOKEN}}";
330
+ let CONFIG = {
331
+ cardFormula: "(TBQ - TSQ) * LTP * WGT",
332
+ aggregates: [
333
  { "name": "NET MONEY FLOW", "formula": "(TBQ - TSQ) * LTP * WGT" },
334
  { "name": "WEIGHTED DELTA", "formula": "(TBQ - TSQ) * LTP * WGT" },
335
  { "name": "BUY PRESSURE", "formula": "TBQ * LTP * WGT" },
336
  { "name": "SELL PRESSURE", "formula": "-1 * TSQ * LTP * WGT" }
337
  ]
338
  };
339
+ let API_CONF = null, DATA = {}, POLLER = null, SHOW_DETAILS = false;
 
 
340
 
341
  window.onload = () => {
342
+ const stored = localStorage.getItem('n50_v6_conf');
343
+ if(stored) CONFIG = JSON.parse(stored);
344
+ document.getElementById('cardForm').value = CONFIG.cardFormula;
345
+ document.getElementById('aggJson').value = JSON.stringify(CONFIG.aggregates, null, 2);
346
  init();
347
  };
348
 
349
+ function toggleDetails() {
350
+ SHOW_DETAILS = !SHOW_DETAILS;
351
+ document.getElementById('togBtn').innerText = "SHOW DETAILS: " + (SHOW_DETAILS ? "ON" : "OFF");
352
+ document.getElementById('togBtn').classList.toggle('active', SHOW_DETAILS);
353
+ render();
354
  }
355
 
356
  async function init() {
357
+ document.getElementById('grid').innerHTML = '<div style="padding:20px; color:#555">LOADING CONFIG...</div>';
358
+ const exp = document.getElementById('expSel').value;
359
  try {
360
+ const r = await fetch(`/api/config?expiry=${exp}`);
361
+ const j = await r.json();
362
+ if(j.s === 'ok') { API_CONF = j; render(); startPoll(); }
363
+ else { document.getElementById('grid').innerHTML = '<div style="padding:20px; color:#888">'+j.msg+'</div>'; setTimeout(init, 2000); }
364
+ } catch(e) { setTimeout(init, 2000); }
365
  }
366
 
367
+ function startPoll() {
368
+ if(POLLER) clearInterval(POLLER);
369
+ fetchData();
370
+ POLLER = setInterval(fetchData, 1000);
371
  }
372
 
373
+ async function fetchData() {
374
+ if(!API_CONF) return;
375
+ const s = [API_CONF.idx, ...API_CONF.stocks.map(x=>x.s)];
376
  const b1 = s.slice(0,40); const b2 = s.slice(40);
377
+ try {
378
+ let nd = {};
379
+ const call = async (list) => {
380
+ if(!list.length) return;
381
+ const q = list.map(x=>encodeURIComponent(x)).join(',');
382
+ const r = await fetch(`https://api-t1.fyers.in/data/depth?symbol=${q}&ohlcv_flag=1`, {headers:{'Authorization':TOKEN}});
383
+ const j = await r.json();
384
+ if(j.s==='ok') Object.assign(nd, j.d);
385
+ };
386
+ await Promise.all([call(b1), call(b2)]);
387
+ DATA = nd;
388
+ update();
389
+ } catch(e){}
390
  }
391
 
392
+ function evalF(form, d, wgt) {
393
  if(!d) return 0;
394
+ try {
395
+ const TBQ = d.totalbuyqty || 0;
396
+ const TSQ = d.totalsellqty || 0;
397
+ const LTP = d.ltp || 0;
398
+ const WGT = wgt || 0;
399
+ return new Function('TBQ','TSQ','LTP','WGT','return '+form)(TBQ, TSQ, LTP, WGT);
400
+ } catch(e){ return 0; }
401
  }
402
 
403
  function fmt(n) {
 
409
  return s+(a/1000).toFixed(0)+" k";
410
  }
411
 
412
+ function update() {
413
+ const i = DATA[API_CONF.idx];
 
414
  if(i) {
415
  document.getElementById('ixP').innerText = i.ltp;
416
  const c = i.chp||0;
417
  const ce = document.getElementById('ixC');
418
+ ce.innerText = c.toFixed(2)+"%"; ce.className = c>=0?"up":"dn";
419
+ const nq = (i.totalbuyqty||0) - (i.totalsellqty||0);
420
+ const qe = document.getElementById('ixQ');
421
+ qe.innerText = "NET: " + fmt(nq); qe.style.color = nq>=0?"var(--green)":"var(--red)";
422
  }
423
 
424
+ const sums = new Array(CONFIG.aggregates.length).fill(0);
 
425
 
426
+ API_CONF.stocks.forEach(stk => {
427
+ const d = DATA[stk.s];
428
  if(!d) return;
429
+ CONFIG.aggregates.forEach((ag, idx) => sums[idx] += evalF(ag.formula, d, stk.w));
 
 
430
  const el = document.getElementById(`c-${stk.r}`);
431
  if(el) {
432
+ const cVal = evalF(CONFIG.cardFormula, d, stk.w);
433
+ const cv = el.querySelector('.calc-big');
434
+ cv.innerText = fmt(cVal);
435
+ cv.style.color = cVal>=0 ? "var(--green)" : "var(--red)";
436
+ el.className = "card " + (cVal>=0 ? "bull" : "bear") + (SHOW_DETAILS ? " expanded" : "");
437
+ if(SHOW_DETAILS) {
438
+ el.querySelector('.v-prc').innerText = d.ltp;
 
439
  const c = d.chp||0;
440
+ const ec = el.querySelector('.v-chg');
441
+ ec.innerText = c.toFixed(2)+"%"; ec.style.color = c>=0?"var(--green)":"var(--red)";
442
+ el.querySelector('.v-buy').innerText = fmt(d.totalbuyqty);
443
+ el.querySelector('.v-sell').innerText = fmt(d.totalsellqty);
444
  }
445
  }
446
  });
447
 
448
+ document.getElementById('aggList').innerHTML = CONFIG.aggregates.map((ag, i) => `
 
449
  <div class="agg-card">
450
+ <div class="agg-name">${ag.name}</div>
451
+ <div class="agg-val" style="color:${sums[i]>=0?'var(--green)':'var(--red)'}">${fmt(sums[i])}</div>
452
  </div>
453
  `).join('');
454
  }
455
 
456
  function render() {
457
+ if(!API_CONF) return;
458
+ const sort = document.getElementById('sortSel').value;
459
+ let lst = [...API_CONF.stocks];
460
+ if(sort.includes('calc')) {
461
+ lst.sort((a,b) => {
462
+ const va = evalF(CONFIG.cardFormula, DATA[a.s], a.w);
463
+ const vb = evalF(CONFIG.cardFormula, DATA[b.s], b.w);
464
+ return sort==='calc_h' ? vb-va : va-vb;
465
  });
466
  }
467
+ document.getElementById('grid').innerHTML = lst.map(s => `
468
+ <div class="card ${SHOW_DETAILS?'expanded':''}" id="c-${s.r}">
 
 
 
 
 
469
  <div class="c-main">
470
+ <div>
471
+ <div class="sym">${s.r}</div>
472
+ <div class="wgt-badge">${s.w.toFixed(2)}%</div>
473
+ </div>
474
+ <div class="calc-big">--</div>
475
  </div>
476
+ <div class="c-details">
477
+ <div class="det-row"><span>PRICE</span> <span class="det-val v-prc">--</span></div>
478
+ <div class="det-row"><span>CHANGE</span> <span class="det-val v-chg">--%</span></div>
479
+ <div class="det-row"><span>BUY Q</span> <span class="det-val v-buy" style="color:var(--green)">--</span></div>
480
+ <div class="det-row"><span>SELL Q</span> <span class="det-val v-sell" style="color:var(--red)">--</span></div>
481
  </div>
482
  </div>
483
  `).join('');
484
+ if(Object.keys(DATA).length>0) update();
 
485
  }
486
 
487
+ function openModal() { document.getElementById('setModal').style.display='flex'; }
488
+ function closeModal() { document.getElementById('setModal').style.display='none'; }
489
+ function saveConfig() {
490
  try {
491
+ const cf = document.getElementById('cardForm').value;
492
+ const ag = JSON.parse(document.getElementById('aggJson').value);
493
+ CONFIG = { cardFormula: cf, aggregates: ag };
494
+ localStorage.setItem('n50_v6_conf', JSON.stringify(CONFIG));
495
+ closeModal(); render(); update();
496
+ } catch(e) { alert("Invalid JSON"); }
497
  }
498
  </script>
499
  </body>