topsecrettraders commited on
Commit
bdbf674
·
verified ·
1 Parent(s): 100a1ce

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -64
app.py CHANGED
@@ -7,7 +7,7 @@ import time
7
  import urllib.parse
8
  import os
9
  import threading
10
- from datetime import datetime
11
  from fastapi import FastAPI, Request, Response, Cookie, BackgroundTasks, Query
12
  from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
13
  from typing import Optional, List, Dict
@@ -17,12 +17,11 @@ from typing import Optional, List, Dict
17
  # ==========================================
18
  CLIENT_ID = "J4PNT5QI22-100"
19
  SECRET_KEY = "ABRSWC1PMR"
20
- # IMPORTANT: Ensure this matches your Space URL exactly
21
  REDIRECT_URI = "https://topsecrettraders-depthchainindia.hf.space/callback"
22
 
23
  CACHE_FILE = "master_cache.json"
24
 
25
- # --- SUPABASE CONFIG (FROM v16.txt) ---
26
  SUPABASE_URL = "https://tuyhtqxfvjndbyjazosi.supabase.co"
27
  SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InR1eWh0cXhmdmpuZGJ5amF6b3NpIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc2ODE0NjAyMiwiZXhwIjoyMDgzNzIyMDIyfQ.YsmGS67vL5TghB5OOi6pmsPL6rrgx7SNpARHmQ9b5cw"
28
 
@@ -212,18 +211,29 @@ def startup_event():
212
  # 3. ROUTES & HISTORICAL LOGIC
213
  # ==========================================
214
 
 
 
 
 
 
 
 
 
 
215
  def decompress_payload(rich_data: List[Dict]) -> Dict:
216
  """
217
  Converts the v16.txt rich array format back to Fyers Data format.
218
- Input: [ { "u": "NSE:...", "p": 100, "v": 50, "q": [10, 20], "o": [5, 0] }, ... ]
219
- Output: { "NSE:...": { "ltp": 100, "v": 50, "totalbuyqty": 10, ... } }
220
  """
221
  fyers_data = {}
222
  for item in rich_data:
223
  sym = item.get('u')
224
  if not sym: continue
225
 
226
- # Mapping based on v16.txt logic
 
 
227
  fyers_data[sym] = {
228
  "ltp": item.get('p', 0),
229
  "v": item.get('v', 0),
@@ -231,8 +241,7 @@ def decompress_payload(rich_data: List[Dict]) -> Dict:
231
  "totalsellqty": item.get('q', [0,0])[1],
232
  "oi": item.get('o', [0,0])[0],
233
  "oipercent": item.get('o', [0,0])[1],
234
- # Helper for expiry if stored in payload (v16 handles this)
235
- "expiry": item.get('e', None)
236
  }
237
  return fyers_data
238
 
@@ -311,55 +320,78 @@ def force_refresh(background_tasks: BackgroundTasks):
311
  background_tasks.add_task(update_master_db)
312
  return JSONResponse({"s": "ok"})
313
 
314
- # --- HISTORICAL API ENDPOINTS ---
315
 
316
  @app.get("/api/hist/dates")
317
  def get_hist_dates(symbol: str):
318
- """Fetches unique dates available for a symbol from Supabase"""
319
  try:
320
- # We fetch recorded_at, convert to date, distinct
321
- # Supabase doesn't support easy 'distinct' on derived columns easily in python client without rpc
322
- # So we fetch 'time_signature' which is string "YYYY-MM-DD HH:MM:SS"
323
- # We'll just fetch entries, order by recency, and extract dates.
324
- # Limit to last 500 records to extract unique dates
325
- res = supabase.table('snapshots')\
326
- .select('time_signature')\
327
- .eq('symbol', symbol.upper())\
328
- .order('recorded_at', desc=True)\
329
- .limit(1000)\
330
- .execute()
331
-
332
  dates = set()
333
- for item in res.data:
334
- ts_str = item.get('time_signature', '')
335
- if ts_str:
336
- dates.add(ts_str.split(' ')[0]) # Extract YYYY-MM-DD
337
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  return JSONResponse(sorted(list(dates), reverse=True))
339
  except Exception as e:
340
  return JSONResponse({"error": str(e)}, status_code=500)
341
 
342
  @app.get("/api/hist/minutes")
343
  def get_hist_minutes(symbol: str, date: str):
344
- """Fetches available minutes for a specific date"""
345
  try:
346
- # Filter strings starting with date
347
- res = supabase.table('snapshots')\
348
- .select('time_signature')\
349
- .eq('symbol', symbol.upper())\
350
- .like('time_signature', f'{date}%')\
351
- .order('recorded_at', desc=True)\
352
- .execute()
353
-
354
  minutes = set()
355
- for item in res.data:
356
- ts_str = item.get('time_signature', '')
357
- # Format: YYYY-MM-DD HH:MM:SS -> Extract HH:MM
358
- parts = ts_str.split(' ')
359
- if len(parts) > 1:
360
- time_part = parts[1] # HH:MM:SS
361
- minutes.add(time_part[:5]) # HH:MM
 
 
 
 
362
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  return JSONResponse(sorted(list(minutes), reverse=True))
364
  except Exception as e:
365
  return JSONResponse({"error": str(e)}, status_code=500)
@@ -371,32 +403,45 @@ def get_hist_data(symbol: str, date: str, minute: str):
371
  Returns a list of { time: "SS", data: {...reconstructed...} }
372
  """
373
  try:
374
- # Construct query pattern like "2025-01-01 09:15%"
375
  query_sig = f"{date} {minute}"
376
-
377
- res = supabase.table('snapshots')\
378
- .select('time_signature, payload')\
379
- .eq('symbol', symbol.upper())\
380
- .like('time_signature', f'{query_sig}%')\
381
- .order('recorded_at', desc=False)\
382
- .execute()
383
-
384
  timeline = []
385
- for item in res.data:
386
- ts = item.get('time_signature', '')
387
- sec = ts.split(':')[-1] if ':' in ts else "00"
 
 
 
 
 
 
 
 
388
 
389
- raw_payload = item.get('payload')
390
- # Handle if payload is stringified JSON
391
- if isinstance(raw_payload, str):
392
- raw_payload = json.loads(raw_payload)
393
 
394
- clean_data = decompress_payload(raw_payload)
395
- timeline.append({
396
- "s": sec,
397
- "d": clean_data
398
- })
 
 
 
 
 
 
 
 
 
 
 
399
 
 
 
 
 
400
  return JSONResponse({"s": "ok", "timeline": timeline})
401
 
402
  except Exception as e:
 
7
  import urllib.parse
8
  import os
9
  import threading
10
+ from datetime import datetime, timezone
11
  from fastapi import FastAPI, Request, Response, Cookie, BackgroundTasks, Query
12
  from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
13
  from typing import Optional, List, Dict
 
17
  # ==========================================
18
  CLIENT_ID = "J4PNT5QI22-100"
19
  SECRET_KEY = "ABRSWC1PMR"
 
20
  REDIRECT_URI = "https://topsecrettraders-depthchainindia.hf.space/callback"
21
 
22
  CACHE_FILE = "master_cache.json"
23
 
24
+ # --- SUPABASE CONFIG ---
25
  SUPABASE_URL = "https://tuyhtqxfvjndbyjazosi.supabase.co"
26
  SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InR1eWh0cXhmdmpuZGJ5amF6b3NpIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc2ODE0NjAyMiwiZXhwIjoyMDgzNzIyMDIyfQ.YsmGS67vL5TghB5OOi6pmsPL6rrgx7SNpARHmQ9b5cw"
27
 
 
211
  # 3. ROUTES & HISTORICAL LOGIC
212
  # ==========================================
213
 
214
+ def date_str_to_epoch(date_str: str) -> int:
215
+ """Converts YYYY-MM-DD string to UNIX Timestamp (Seconds)"""
216
+ if not date_str: return 0
217
+ try:
218
+ dt = datetime.strptime(date_str, "%Y-%m-%d")
219
+ return int(dt.replace(tzinfo=timezone.utc).timestamp())
220
+ except:
221
+ return 0
222
+
223
  def decompress_payload(rich_data: List[Dict]) -> Dict:
224
  """
225
  Converts the v16.txt rich array format back to Fyers Data format.
226
+ Input: [ { "u": "NSE:...", "p": 100, "v": 50, "q": [10, 20], "o": [5, 0], "e": "2025-02-26" }, ... ]
227
+ Output: { "NSE:...": { "ltp": 100, "v": 50, "totalbuyqty": 10, "expiry": 1740528000 ... } }
228
  """
229
  fyers_data = {}
230
  for item in rich_data:
231
  sym = item.get('u')
232
  if not sym: continue
233
 
234
+ expiry_str = item.get('e', None)
235
+ expiry_ts = date_str_to_epoch(expiry_str) if expiry_str else 0
236
+
237
  fyers_data[sym] = {
238
  "ltp": item.get('p', 0),
239
  "v": item.get('v', 0),
 
241
  "totalsellqty": item.get('q', [0,0])[1],
242
  "oi": item.get('o', [0,0])[0],
243
  "oipercent": item.get('o', [0,0])[1],
244
+ "expiry": expiry_ts # Converted to Epoch for UI compatibility
 
245
  }
246
  return fyers_data
247
 
 
320
  background_tasks.add_task(update_master_db)
321
  return JSONResponse({"s": "ok"})
322
 
323
+ # --- HISTORICAL API ENDPOINTS WITH FULL PAGINATION ---
324
 
325
  @app.get("/api/hist/dates")
326
  def get_hist_dates(symbol: str):
327
+ """Fetches ALL available dates for a symbol using pagination"""
328
  try:
 
 
 
 
 
 
 
 
 
 
 
 
329
  dates = set()
330
+ offset = 0
331
+ limit = 1000 # Supabase max
332
+
333
+ while True:
334
+ # Loop to fetch ALL records
335
+ res = supabase.table('snapshots')\
336
+ .select('time_signature')\
337
+ .eq('symbol', symbol.upper())\
338
+ .order('recorded_at', desc=True)\
339
+ .range(offset, offset + limit - 1)\
340
+ .execute()
341
+
342
+ data = res.data
343
+ if not data:
344
+ break
345
+
346
+ for item in data:
347
+ ts_str = item.get('time_signature', '')
348
+ if ts_str:
349
+ dates.add(ts_str.split(' ')[0]) # Extract YYYY-MM-DD
350
+
351
+ if len(data) < limit:
352
+ break
353
+
354
+ offset += limit
355
+ # Safety break at 50k rows to prevent timeout if excessive
356
+ if offset > 100000: break
357
+
358
  return JSONResponse(sorted(list(dates), reverse=True))
359
  except Exception as e:
360
  return JSONResponse({"error": str(e)}, status_code=500)
361
 
362
  @app.get("/api/hist/minutes")
363
  def get_hist_minutes(symbol: str, date: str):
364
+ """Fetches ALL available minutes for a specific date using pagination"""
365
  try:
 
 
 
 
 
 
 
 
366
  minutes = set()
367
+ offset = 0
368
+ limit = 1000
369
+
370
+ while True:
371
+ res = supabase.table('snapshots')\
372
+ .select('time_signature')\
373
+ .eq('symbol', symbol.upper())\
374
+ .like('time_signature', f'{date}%')\
375
+ .order('recorded_at', desc=True)\
376
+ .range(offset, offset + limit - 1)\
377
+ .execute()
378
 
379
+ data = res.data
380
+ if not data:
381
+ break
382
+
383
+ for item in data:
384
+ ts_str = item.get('time_signature', '')
385
+ # Format: YYYY-MM-DD HH:MM:SS -> Extract HH:MM
386
+ parts = ts_str.split(' ')
387
+ if len(parts) > 1:
388
+ time_part = parts[1] # HH:MM:SS
389
+ minutes.add(time_part[:5]) # HH:MM
390
+
391
+ if len(data) < limit:
392
+ break
393
+ offset += limit
394
+
395
  return JSONResponse(sorted(list(minutes), reverse=True))
396
  except Exception as e:
397
  return JSONResponse({"error": str(e)}, status_code=500)
 
403
  Returns a list of { time: "SS", data: {...reconstructed...} }
404
  """
405
  try:
 
406
  query_sig = f"{date} {minute}"
 
 
 
 
 
 
 
 
407
  timeline = []
408
+ offset = 0
409
+ limit = 1000
410
+
411
+ while True:
412
+ res = supabase.table('snapshots')\
413
+ .select('time_signature, payload')\
414
+ .eq('symbol', symbol.upper())\
415
+ .like('time_signature', f'{query_sig}%')\
416
+ .order('recorded_at', desc=False)\
417
+ .range(offset, offset + limit - 1)\
418
+ .execute()
419
 
420
+ data = res.data
421
+ if not data:
422
+ break
 
423
 
424
+ for item in data:
425
+ ts = item.get('time_signature', '')
426
+ sec = ts.split(':')[-1] if ':' in ts else "00"
427
+
428
+ raw_payload = item.get('payload')
429
+ if isinstance(raw_payload, str):
430
+ try:
431
+ raw_payload = json.loads(raw_payload)
432
+ except:
433
+ raw_payload = []
434
+
435
+ clean_data = decompress_payload(raw_payload)
436
+ timeline.append({
437
+ "s": sec,
438
+ "d": clean_data
439
+ })
440
 
441
+ if len(data) < limit:
442
+ break
443
+ offset += limit
444
+
445
  return JSONResponse({"s": "ok", "timeline": timeline})
446
 
447
  except Exception as e: