topsecrettraders commited on
Commit
fd0647d
·
verified ·
1 Parent(s): c8c6ba7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -74
app.py CHANGED
@@ -28,18 +28,28 @@ app = FastAPI()
28
  # 2. HELPER FUNCTIONS
29
  # ==========================================
30
  def sb_get(endpoint: str, params: dict = None):
31
- """Generic Supabase GET helper."""
32
  try:
33
  url = f"{SB_URL}/rest/v1{endpoint}"
34
  r = requests.get(url, headers=HEADERS, params=params)
35
  r.raise_for_status()
36
  return r.json()
37
  except Exception as e:
38
- print(f"DB Error ({endpoint}): {e}")
 
 
 
 
 
 
 
 
 
 
 
39
  return[]
40
 
41
  # ==========================================
42
- # 3. API ROUTES - METADATA
43
  # ==========================================
44
 
45
  @app.get("/")
@@ -48,93 +58,66 @@ def home():
48
 
49
  @app.get("/api/dates")
50
  def get_dates():
51
- """Fetch distinct dates."""
52
- data = sb_get("/history_buckets_fyers", {"select": "minute", "order": "minute.desc", "limit": "50000"})
53
- dates = set()
54
- for row in data:
55
- if row.get('minute'):
56
- dates.add(row['minute'].split('T')[0])
57
- return JSONResponse(sorted(list(dates), reverse=True))
58
 
59
  @app.get("/api/roots")
60
  def get_roots(date: str, q: Optional[str] = None):
61
- """Fetch roots. If q is provided, search specifically for that string."""
62
- start, end = f"{date}T00:00:00", f"{date}T23:59:59"
63
- # Added 50000 limit to ensure we load ALL roots without Supabase 1000 row truncation
64
- endpoint = f"/history_buckets_fyers?select=root&minute=gte.{start}&minute=lte.{end}&limit=50000"
65
  if q:
66
- endpoint += f"&root=ilike.*{q.upper()}*"
67
 
68
- data = sb_get(endpoint)
69
- roots = set()
70
- for row in data:
71
- if row.get('root'):
72
- roots.add(row['root'])
73
- return JSONResponse(sorted(list(roots)))
74
 
75
  @app.get("/api/auto_config")
76
  def get_auto_config(date: str, root: str):
77
- """Smart helper for defaults (Current/Next expiry)."""
78
  root = root.upper()
79
- start, end = f"{date}T00:00:00", f"{date}T23:59:59"
80
- data = sb_get(f"/history_buckets_fyers?select=expiry,data&root=eq.{root}&expiry=neq.MARKET&minute=gte.{start}&minute=lte.{end}&limit=50")
81
 
82
- expiry_map = {}
83
- for row in data:
84
- exp = row.get('expiry')
85
- if not exp: continue
86
- if exp not in expiry_map:
87
- keys = list(row.get('data', {}).keys())
88
- expiry_map[exp] = keys
89
- else:
90
- existing = set(expiry_map[exp])
91
- new_keys = row.get('data', {}).keys()
92
- expiry_map[exp] = list(existing.union(new_keys))
93
-
94
- all_expiries = sorted(expiry_map.keys())
95
  valid_expiries =[e for e in all_expiries if e >= date]
96
  if not valid_expiries:
97
  valid_expiries = all_expiries
98
 
99
  response = {"expiries": all_expiries, "current": None, "next": None}
100
 
101
- def find_fut(instruments):
 
 
102
  for i in instruments:
103
  if 'FUT' in i: return i
104
  return instruments[0] if instruments else None
105
 
106
  if len(valid_expiries) > 0:
107
  exp_c = valid_expiries[0]
108
- inst_c = find_fut(expiry_map[exp_c])
109
- response["current"] = {"expiry": exp_c, "instrument": inst_c}
110
 
111
  if len(valid_expiries) > 1:
112
  exp_n = valid_expiries[1]
113
- inst_n = find_fut(expiry_map[exp_n])
114
- response["next"] = {"expiry": exp_n, "instrument": inst_n}
115
 
116
  return JSONResponse(response)
117
 
118
  @app.get("/api/expiries")
119
  def get_expiries(date: str, root: str):
120
- """Fetch LITERALLY all distinct expiries for a specific root."""
121
  root = root.upper()
122
- start, end = f"{date}T00:00:00", f"{date}T23:59:59"
123
- # Added limit 50000 to ensure all expiries are caught
124
- data = sb_get(f"/history_buckets_fyers?select=expiry&root=eq.{root}&minute=gte.{start}&minute=lte.{end}&limit=50000")
125
- exps = set(row['expiry'] for row in data if row.get('expiry'))
126
- return JSONResponse(sorted(list(exps)))
127
 
128
  @app.get("/api/instruments")
129
  def get_instruments(date: str, root: str, expiry: str):
130
- """Fetch instruments and segregate into spot/future vs options."""
131
  root = root.upper()
132
- start, end = f"{date}T00:00:00", f"{date}T23:59:59"
133
- data = sb_get(f"/history_buckets_fyers?select=data&root=eq.{root}&expiry=eq.{expiry}&minute=gte.{start}&minute=lte.{end}&limit=50")
134
- instruments = set()
135
- for row in data:
136
- keys = row.get('data', {}).keys()
137
- instruments.update(keys)
138
 
139
  spot_fut = []
140
  options =[]
@@ -201,7 +184,7 @@ def fetch_series(
201
  for i in range(6):
202
  p_base = None
203
  if base_key and base_key in data_dict:
204
- p_arr = data_dict[base_key].get('p', [])
205
  if i < len(p_arr) and p_arr[i] is not None:
206
  p_base = float(p_arr[i])
207
 
@@ -255,10 +238,10 @@ def fetch_series(
255
  else:
256
  inst_data = data_dict.get(instrument)
257
  if not inst_data: continue
258
- p_arr = inst_data.get('p', []) or[]
259
  b_arr = inst_data.get('b', []) or[]
260
- s_arr = inst_data.get('s', []) or []
261
- v_arr = inst_data.get('v',[]) or []
262
  o_arr = inst_data.get('o', []) or[]
263
 
264
  for i in range(6):
@@ -644,7 +627,7 @@ document.addEventListener('click', (e) => {
644
  });
645
 
646
  const CustomDD = {
647
- data: { cfgRoot: [], cfgExp: [], cfgInst:[] },
648
  recent: { cfgRoot:[] }, // Persistent memory for symbols
649
 
650
  init() {
@@ -668,10 +651,9 @@ const CustomDD = {
668
  async open(id) {
669
  document.querySelectorAll('.custom-dd').forEach(el => el.style.display = 'none');
670
  const inp = document.getElementById(id);
671
- inp.select(); // Select current text for easy typing over
672
 
673
- // ✨ FIX: Always show ALL items (empty query string) when opening the dropdown.
674
- // It ignores the current value (like "2026-03-24") so you don't have to backspace!
675
  this.render(id, '');
676
 
677
  document.getElementById(`dd-${id}`).style.display = 'block';
@@ -680,8 +662,12 @@ const CustomDD = {
680
  async filter(id) {
681
  const query = document.getElementById(id).value.toUpperCase();
682
 
683
- // FIX: We already load all roots/expiries when the modal opens or root changes.
684
- // We no longer trigger backend calls on every keystroke. It just filters cleanly on frontend.
 
 
 
 
685
 
686
  this.render(id, query);
687
  document.getElementById(`dd-${id}`).style.display = 'block';
@@ -693,14 +679,14 @@ const CustomDD = {
693
  let html = '';
694
  let currentGroup = null;
695
 
696
- // Add Recent Symbols to the top of Root list
697
  let pool = [...this.data[id]];
 
698
  if(id === 'cfgRoot' && q === '') {
699
  const recentItems = this.recent.cfgRoot.map(r => ({val: r, group: 'Recent Search'}));
700
  pool =[...recentItems, ...pool];
701
  }
702
 
703
- const filtered = pool.filter((v, i, a) => a.findIndex(t => t.val === v.val) === i) // Unique
704
  .filter(item => item.val.toLowerCase().includes(q));
705
 
706
  if (filtered.length === 0) {
@@ -723,7 +709,6 @@ const CustomDD = {
723
  document.getElementById(`dd-${id}`).style.display = 'none';
724
 
725
  if(id === 'cfgRoot') {
726
- // Save to recent
727
  if(!this.recent.cfgRoot.includes(val)) {
728
  this.recent.cfgRoot.unshift(val);
729
  if(this.recent.cfgRoot.length > 10) this.recent.cfgRoot.pop();
@@ -1081,8 +1066,10 @@ const ChartLogic = {
1081
  let c = s.color1;
1082
  if(s.cond && s.cond !== 'none') {
1083
  let thresh = parseFloat(s.thresh) || 0;
1084
- if(s.cond === '>') c = v > thresh ? s.color1 : s.color2;
1085
- if(s.cond === '<') c = v < thresh ? s.color1 : s.color2;
 
 
1086
  }
1087
  return { borderColor: c, backgroundColor: c };
1088
  }
@@ -1114,7 +1101,7 @@ const Config = {
1114
 
1115
  this.updateInfoTooltip(mode);
1116
  this.updatePresetsDropdown();
1117
- this.loadPreset("Model 1"); // Model 1 is now the default
1118
  },
1119
 
1120
  updateInfoTooltip(mode) {
@@ -1177,7 +1164,7 @@ const Config = {
1177
 
1178
  this.updatePresetsDropdown();
1179
 
1180
- // Load ALL existing roots once into frontend memory
1181
  CustomDD.clear('cfgRoot');
1182
  const res = await fetch(`/api/roots?date=${App.state.date}`);
1183
  const roots = await res.json();
@@ -1241,7 +1228,7 @@ const Config = {
1241
 
1242
  if (!root) return;
1243
 
1244
- // Fetches ALL available expiries for the root into memory
1245
  const res = await fetch(`/api/expiries?date=${App.state.date}&root=${root}`);
1246
  const exps = await res.json();
1247
  CustomDD.set('cfgExp', exps);
@@ -1266,7 +1253,7 @@ const Config = {
1266
 
1267
  if (!root || !exp) return;
1268
 
1269
- // Fetch ALL instruments for the root/expiry
1270
  const res = await fetch(`/api/instruments?date=${App.state.date}&root=${root}&expiry=${exp}`);
1271
  const data = await res.json();
1272
 
 
28
  # 2. HELPER FUNCTIONS
29
  # ==========================================
30
  def sb_get(endpoint: str, params: dict = None):
 
31
  try:
32
  url = f"{SB_URL}/rest/v1{endpoint}"
33
  r = requests.get(url, headers=HEADERS, params=params)
34
  r.raise_for_status()
35
  return r.json()
36
  except Exception as e:
37
+ print(f"DB Error GET ({endpoint}): {e}")
38
+ return[]
39
+
40
+ def sb_rpc(func_name: str, params: dict = {}):
41
+ """Executes a Supabase Postgres Function (RPC) for Zero Egress"""
42
+ try:
43
+ url = f"{SB_URL}/rest/v1/rpc/{func_name}"
44
+ r = requests.post(url, headers=HEADERS, json=params)
45
+ r.raise_for_status()
46
+ return r.json()
47
+ except Exception as e:
48
+ print(f"DB Error RPC ({func_name}): {e}")
49
  return[]
50
 
51
  # ==========================================
52
+ # 3. API ROUTES - METADATA (NOW USING RPC!)
53
  # ==========================================
54
 
55
  @app.get("/")
 
58
 
59
  @app.get("/api/dates")
60
  def get_dates():
61
+ """Fetch last 30 distinct dates via RPC."""
62
+ data = sb_rpc("get_available_dates", {"p_limit": 30})
63
+ dates = [row['available_date'] for row in data]
64
+ return JSONResponse(dates)
 
 
 
65
 
66
  @app.get("/api/roots")
67
  def get_roots(date: str, q: Optional[str] = None):
68
+ """Fetch roots via RPC. Zero egress!"""
69
+ params = {"p_date": date}
 
 
70
  if q:
71
+ params["p_q"] = q.upper()
72
 
73
+ data = sb_rpc("get_roots_for_date", params)
74
+ roots = [row['root_name'] for row in data]
75
+ return JSONResponse(roots)
 
 
 
76
 
77
  @app.get("/api/auto_config")
78
  def get_auto_config(date: str, root: str):
79
+ """Smart helper for defaults using RPCs."""
80
  root = root.upper()
81
+ exps_data = sb_rpc("get_expiries_for_root", {"p_date": date, "p_root": root})
82
+ all_expiries = [row['expiry_date'] for row in exps_data if row['expiry_date'] != 'MARKET']
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  valid_expiries =[e for e in all_expiries if e >= date]
85
  if not valid_expiries:
86
  valid_expiries = all_expiries
87
 
88
  response = {"expiries": all_expiries, "current": None, "next": None}
89
 
90
+ def find_fut_rpc(exp):
91
+ inst_data = sb_rpc("get_instruments_for_expiry", {"p_date": date, "p_root": root, "p_expiry": exp})
92
+ instruments = [r['instrument_name'] for r in inst_data]
93
  for i in instruments:
94
  if 'FUT' in i: return i
95
  return instruments[0] if instruments else None
96
 
97
  if len(valid_expiries) > 0:
98
  exp_c = valid_expiries[0]
99
+ response["current"] = {"expiry": exp_c, "instrument": find_fut_rpc(exp_c)}
 
100
 
101
  if len(valid_expiries) > 1:
102
  exp_n = valid_expiries[1]
103
+ response["next"] = {"expiry": exp_n, "instrument": find_fut_rpc(exp_n)}
 
104
 
105
  return JSONResponse(response)
106
 
107
  @app.get("/api/expiries")
108
  def get_expiries(date: str, root: str):
109
+ """Fetch distinct expiries via RPC."""
110
  root = root.upper()
111
+ data = sb_rpc("get_expiries_for_root", {"p_date": date, "p_root": root})
112
+ exps = [row['expiry_date'] for row in data]
113
+ return JSONResponse(exps)
 
 
114
 
115
  @app.get("/api/instruments")
116
  def get_instruments(date: str, root: str, expiry: str):
117
+ """Fetch instruments via RPC."""
118
  root = root.upper()
119
+ data = sb_rpc("get_instruments_for_expiry", {"p_date": date, "p_root": root, "p_expiry": expiry})
120
+ instruments = [row['instrument_name'] for row in data]
 
 
 
 
121
 
122
  spot_fut = []
123
  options =[]
 
184
  for i in range(6):
185
  p_base = None
186
  if base_key and base_key in data_dict:
187
+ p_arr = data_dict[base_key].get('p',[])
188
  if i < len(p_arr) and p_arr[i] is not None:
189
  p_base = float(p_arr[i])
190
 
 
238
  else:
239
  inst_data = data_dict.get(instrument)
240
  if not inst_data: continue
241
+ p_arr = inst_data.get('p', []) or []
242
  b_arr = inst_data.get('b', []) or[]
243
+ s_arr = inst_data.get('s', []) or[]
244
+ v_arr = inst_data.get('v', []) or []
245
  o_arr = inst_data.get('o', []) or[]
246
 
247
  for i in range(6):
 
627
  });
628
 
629
  const CustomDD = {
630
+ data: { cfgRoot: [], cfgExp:[], cfgInst:[] },
631
  recent: { cfgRoot:[] }, // Persistent memory for symbols
632
 
633
  init() {
 
651
  async open(id) {
652
  document.querySelectorAll('.custom-dd').forEach(el => el.style.display = 'none');
653
  const inp = document.getElementById(id);
654
+ inp.select();
655
 
656
+ // Always show ALL items when opening
 
657
  this.render(id, '');
658
 
659
  document.getElementById(`dd-${id}`).style.display = 'block';
 
662
  async filter(id) {
663
  const query = document.getElementById(id).value.toUpperCase();
664
 
665
+ // Fetch from DB only when typing roots to save egress
666
+ if(id === 'cfgRoot' && query.length >= 2) {
667
+ const res = await fetch(`/api/roots?date=${App.state.date}&q=${query}`);
668
+ const results = await res.json();
669
+ this.set(id, results);
670
+ }
671
 
672
  this.render(id, query);
673
  document.getElementById(`dd-${id}`).style.display = 'block';
 
679
  let html = '';
680
  let currentGroup = null;
681
 
 
682
  let pool = [...this.data[id]];
683
+
684
  if(id === 'cfgRoot' && q === '') {
685
  const recentItems = this.recent.cfgRoot.map(r => ({val: r, group: 'Recent Search'}));
686
  pool =[...recentItems, ...pool];
687
  }
688
 
689
+ const filtered = pool.filter((v, i, a) => a.findIndex(t => t.val === v.val) === i)
690
  .filter(item => item.val.toLowerCase().includes(q));
691
 
692
  if (filtered.length === 0) {
 
709
  document.getElementById(`dd-${id}`).style.display = 'none';
710
 
711
  if(id === 'cfgRoot') {
 
712
  if(!this.recent.cfgRoot.includes(val)) {
713
  this.recent.cfgRoot.unshift(val);
714
  if(this.recent.cfgRoot.length > 10) this.recent.cfgRoot.pop();
 
1066
  let c = s.color1;
1067
  if(s.cond && s.cond !== 'none') {
1068
  let thresh = parseFloat(s.thresh) || 0;
1069
+ let cTrue = s.color1;
1070
+ let cFalse = s.color2 || s.color1;
1071
+ if(s.cond === '>') c = v > thresh ? cTrue : cFalse;
1072
+ if(s.cond === '<') c = v < thresh ? cTrue : cFalse;
1073
  }
1074
  return { borderColor: c, backgroundColor: c };
1075
  }
 
1101
 
1102
  this.updateInfoTooltip(mode);
1103
  this.updatePresetsDropdown();
1104
+ this.loadPreset("Model 1"); // Default to Model 1 always when switching modes
1105
  },
1106
 
1107
  updateInfoTooltip(mode) {
 
1164
 
1165
  this.updatePresetsDropdown();
1166
 
1167
+ // Load roots logic (RPC)
1168
  CustomDD.clear('cfgRoot');
1169
  const res = await fetch(`/api/roots?date=${App.state.date}`);
1170
  const roots = await res.json();
 
1228
 
1229
  if (!root) return;
1230
 
1231
+ // Fetches ALL available expiries for the root via RPC
1232
  const res = await fetch(`/api/expiries?date=${App.state.date}&root=${root}`);
1233
  const exps = await res.json();
1234
  CustomDD.set('cfgExp', exps);
 
1253
 
1254
  if (!root || !exp) return;
1255
 
1256
+ // Fetch ALL instruments via RPC
1257
  const res = await fetch(`/api/instruments?date=${App.state.date}&root=${root}&expiry=${exp}`);
1258
  const data = await res.json();
1259