Subham9126 commited on
Commit
028beee
Β·
verified Β·
1 Parent(s): af60e73

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +112 -92
app.py CHANGED
@@ -9,7 +9,6 @@ import tzlocal
9
  import logging
10
  import json
11
  import ast
12
- from concurrent.futures import ThreadPoolExecutor
13
 
14
  # --- Basic Setup ---
15
 
@@ -73,9 +72,9 @@ def get_time_range_in_unix_ms(start_date_str: str, end_date_str: str, timezone:
73
  return {"start_timestamp_ms": start_timestamp, "end_timestamp_ms": end_timestamp}
74
 
75
 
76
- # --- OPTIMIZED Async Functions ---
77
 
78
- async def fetch_single_request(session: aiohttp.ClientSession, ticker: str, start: int, end: int, interval: int) -> Dict:
79
  """Fetch data for a single ticker-interval combination."""
80
  url = f"{hist_url}/{ticker}"
81
  params = {"startTimeInMillis": start, "endTimeInMillis": end, "intervalInMinutes": interval}
@@ -85,162 +84,183 @@ async def fetch_single_request(session: aiohttp.ClientSession, ticker: str, star
85
  json_data = await response.json()
86
  return {"ticker": ticker, "interval": interval, "data": json_data, "error": None}
87
  except Exception as e:
88
- logger.error(f"API call failed for {ticker} ({interval}m): {e}")
89
  return {"ticker": ticker, "interval": interval, "data": None, "error": str(e)}
90
 
91
- async def fetch_all_data_optimized(tickers: List[str], start_time: int, end_time: int, intervals: List[int]) -> List[Dict]:
92
- """Fetch all data with maximum concurrency - NO artificial batching."""
 
93
 
94
- # Create ALL tasks at once
95
- all_tasks = []
96
- for ticker in tickers:
97
- for interval in intervals:
98
- all_tasks.append((ticker, start_time, end_time, interval))
99
-
100
- logger.info(f"Creating {len(all_tasks)} concurrent requests...")
101
-
102
- # Configure session for high concurrency
103
- connector = aiohttp.TCPConnector(
104
- limit=100, # Max concurrent connections
105
- limit_per_host=50, # Max per host
106
- keepalive_timeout=30
107
- )
108
-
109
- timeout = aiohttp.ClientTimeout(total=30, connect=10)
110
-
111
- async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
112
- # Create all coroutines
113
  tasks = [
114
- fetch_single_request(session, ticker, start, end, interval)
115
- for ticker, start, end, interval in all_tasks
116
  ]
117
 
118
- # Execute ALL requests concurrently
119
- logger.info("Executing all requests concurrently...")
120
- start_time_fetch = datetime.now()
121
-
122
- results = await asyncio.gather(*tasks, return_exceptions=True)
123
 
124
- fetch_duration = (datetime.now() - start_time_fetch).total_seconds()
125
- logger.info(f"All API calls completed in {fetch_duration:.2f} seconds")
126
-
127
- # Filter out exceptions
128
- valid_results = [r for r in results if not isinstance(r, Exception)]
129
- logger.info(f"Got {len(valid_results)} valid responses out of {len(all_tasks)} requests")
 
 
 
 
 
 
 
 
 
130
 
131
- return valid_results
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
- def process_data_sync(results: List[Dict]) -> str:
134
- """Process data synchronously - this is fast."""
135
  merged_data = {}
136
 
137
  for result in results:
138
- ticker = result.get("ticker")
139
- interval = result.get("interval")
140
- data = result.get("data")
141
- error = result.get("error")
142
 
 
143
  if ticker not in merged_data:
144
  merged_data[ticker] = {"symbol": ticker}
145
-
 
146
  if error:
147
  merged_data[ticker][f"error_{interval}m"] = error
148
  continue
149
-
150
- if data and data.get("candles") and data["candles"]:
151
- first_candle = data["candles"][0]
152
- if len(first_candle) > 4:
 
153
  prefix = "day" if interval == 1440 else "start"
154
- merged_data[ticker][f"{prefix} open"] = first_candle[1]
155
- merged_data[ticker][f"{prefix} high"] = first_candle[2]
156
- merged_data[ticker][f"{prefix} low"] = first_candle[3]
157
- merged_data[ticker][f"{prefix} close"] = first_candle[4]
 
 
158
  else:
159
- merged_data[ticker][f"error_{interval}m"] = "No data or candles found"
160
 
161
- return json.dumps(list(merged_data.values()), indent=4)
162
 
163
 
164
- # --- STREAMLINED Gradio Backend Function ---
165
 
166
  async def run_backend_processing(tickers_text: str, progress=gr.Progress(track_tqdm=True)):
167
- """Streamlined backend processing with minimal yields."""
 
 
168
  if not tickers_text.strip():
169
- yield "Error: Input is empty. Please provide a list of tickers.", "{}"
170
  return
171
 
172
  try:
173
  parsed_input = ast.literal_eval(tickers_text)
174
  if not isinstance(parsed_input, list):
175
- raise TypeError("Input must be a list.")
176
- tickers = [str(item).strip().upper() for item in parsed_input]
177
  if not tickers:
178
- yield "Error: The provided list is empty.", "{}"
179
  return
180
- except (ValueError, SyntaxError, TypeError):
181
- yield 'Invalid input format. Please provide a list of strings, e.g., ["RELIANCE", "INFY"]', "{}"
182
  return
183
 
184
- # Get timestamps
185
  today_str = date.today().strftime("%Y-%m-%d")
186
  time_range = get_time_range_in_unix_ms(today_str, today_str)
187
  start_time, end_time = time_range["start_timestamp_ms"], time_range["end_timestamp_ms"]
188
- intervals = [1440, 15]
189
 
190
  total_requests = len(tickers) * len(intervals)
191
- yield f"Fetching data for {len(tickers)} tickers ({total_requests} total requests)...", "{}"
192
 
193
  try:
194
- # Time the entire operation
195
  overall_start = datetime.now()
196
 
197
- # Fetch all data with maximum concurrency
198
- results = await fetch_all_data_optimized(tickers, start_time, end_time, intervals)
 
 
 
199
 
200
- # Process data (this is fast)
201
- processed_json = process_data_sync(results)
 
 
202
 
203
  total_time = (datetime.now() - overall_start).total_seconds()
204
 
205
- yield f"βœ… Complete! Processed {len(results)} responses in {total_time:.2f} seconds", processed_json
 
206
 
207
  except Exception as e:
208
- logger.error(f"Error: {e}")
209
- yield f"An error occurred: {e}", "{}"
210
 
211
 
212
  # --- Gradio UI ---
213
 
214
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
215
- gr.Markdown("## ⚑ High-Performance Stock Data Processor")
216
- gr.Markdown("Optimized for maximum concurrency - fetches all data simultaneously!")
217
 
218
  with gr.Row():
219
  with gr.Column(scale=1):
220
  tickers_input = gr.Textbox(
221
- lines=3,
222
- label='Enter Tickers as List',
223
- value='["RELIANCE", "INFY", "TCS", "HDFCBANK", "ICICIBANK"]',
224
- placeholder='["TICKER1", "TICKER2", "TICKER3"]'
225
  )
226
- start_button = gr.Button("πŸš€ Start Processing", variant="primary")
227
 
228
- gr.Markdown("""
229
- **Performance Notes:**
230
- - Each ticker = 2 API calls (1440m + 15m intervals)
231
- - All requests execute concurrently
232
- - No artificial batching delays
233
- """)
 
 
 
234
 
235
  with gr.Column(scale=2):
236
- logs_output = gr.Textbox(label="⏱️ Progress & Timing", lines=10, interactive=False)
237
- json_output = gr.JSON(label="πŸ“Š Processed Results")
238
 
 
239
  start_button.click(
240
  fn=run_backend_processing,
241
  inputs=[tickers_input],
242
  outputs=[logs_output, json_output]
243
  )
 
244
  tickers_input.submit(
245
  fn=run_backend_processing,
246
  inputs=[tickers_input],
@@ -248,4 +268,4 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
248
  )
249
 
250
  if __name__ == "__main__":
251
- demo.launch()
 
9
  import logging
10
  import json
11
  import ast
 
12
 
13
  # --- Basic Setup ---
14
 
 
72
  return {"start_timestamp_ms": start_timestamp, "end_timestamp_ms": end_timestamp}
73
 
74
 
75
+ # --- OPTIMIZED API Functions - Interval-Based Batching ---
76
 
77
+ async def fetch_single_ticker_interval(session: aiohttp.ClientSession, ticker: str, start: int, end: int, interval: int) -> Dict:
78
  """Fetch data for a single ticker-interval combination."""
79
  url = f"{hist_url}/{ticker}"
80
  params = {"startTimeInMillis": start, "endTimeInMillis": end, "intervalInMinutes": interval}
 
84
  json_data = await response.json()
85
  return {"ticker": ticker, "interval": interval, "data": json_data, "error": None}
86
  except Exception as e:
 
87
  return {"ticker": ticker, "interval": interval, "data": None, "error": str(e)}
88
 
89
+ async def fetch_interval_batch(session: aiohttp.ClientSession, tickers: List[str], start_time: int, end_time: int, interval: int, batch_size: int = 30) -> List[Dict]:
90
+ """Fetch data for all tickers for a specific interval in batches."""
91
+ results = []
92
 
93
+ for i in range(0, len(tickers), batch_size):
94
+ batch_tickers = tickers[i:i+batch_size]
95
+ logger.info(f"Processing {interval}m batch {i//batch_size + 1}: {len(batch_tickers)} tickers")
96
+
97
+ # Create tasks for this batch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  tasks = [
99
+ fetch_single_ticker_interval(session, ticker, start_time, end_time, interval)
100
+ for ticker in batch_tickers
101
  ]
102
 
103
+ # Execute batch concurrently
104
+ batch_results = await asyncio.gather(*tasks)
105
+ results.extend(batch_results)
 
 
106
 
107
+ # Small delay between batches to be API-friendly
108
+ if i + batch_size < len(tickers):
109
+ await asyncio.sleep(0.05)
110
+
111
+ return results
112
+
113
+ async def fetch_all_data_by_intervals(tickers: List[str], start_time: int, end_time: int, intervals: List[int]) -> List[Dict]:
114
+ """Fetch data by processing each interval separately in batches of 30."""
115
+ all_results = []
116
+
117
+ # Configure session for optimal performance
118
+ connector = aiohttp.TCPConnector(limit=50, limit_per_host=30)
119
+ timeout = aiohttp.ClientTimeout(total=20, connect=5)
120
+
121
+ async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
122
 
123
+ # Process each interval separately
124
+ for interval in intervals:
125
+ logger.info(f"Starting {interval}m interval requests for {len(tickers)} tickers...")
126
+ interval_start = datetime.now()
127
+
128
+ interval_results = await fetch_interval_batch(session, tickers, start_time, end_time, interval)
129
+
130
+ interval_duration = (datetime.now() - interval_start).total_seconds()
131
+ logger.info(f"Completed {interval}m interval in {interval_duration:.2f}s - Got {len(interval_results)} results")
132
+
133
+ all_results.extend(interval_results)
134
+
135
+ return all_results
136
 
137
+ def merge_data_fast(results: List[Dict]) -> str:
138
+ """Fast data merging without unnecessary overhead."""
139
  merged_data = {}
140
 
141
  for result in results:
142
+ ticker = result["ticker"]
143
+ interval = result["interval"]
144
+ data = result["data"]
145
+ error = result["error"]
146
 
147
+ # Initialize ticker entry if not exists
148
  if ticker not in merged_data:
149
  merged_data[ticker] = {"symbol": ticker}
150
+
151
+ # Handle errors
152
  if error:
153
  merged_data[ticker][f"error_{interval}m"] = error
154
  continue
155
+
156
+ # Process successful data
157
+ if data and data.get("candles") and len(data["candles"]) > 0:
158
+ candle = data["candles"][0]
159
+ if len(candle) > 4:
160
  prefix = "day" if interval == 1440 else "start"
161
+ merged_data[ticker].update({
162
+ f"{prefix} open": candle[1],
163
+ f"{prefix} high": candle[2],
164
+ f"{prefix} low": candle[3],
165
+ f"{prefix} close": candle[4]
166
+ })
167
  else:
168
+ merged_data[ticker][f"error_{interval}m"] = "No candles data"
169
 
170
+ return json.dumps(list(merged_data.values()), indent=2)
171
 
172
 
173
+ # --- MAIN Gradio Backend Function ---
174
 
175
  async def run_backend_processing(tickers_text: str, progress=gr.Progress(track_tqdm=True)):
176
+ """Main processing function - optimized for speed."""
177
+
178
+ # Input validation
179
  if not tickers_text.strip():
180
+ yield "❌ Error: Input is empty", "{}"
181
  return
182
 
183
  try:
184
  parsed_input = ast.literal_eval(tickers_text)
185
  if not isinstance(parsed_input, list):
186
+ raise TypeError("Must be a list")
187
+ tickers = [str(item).strip().upper() for item in parsed_input if str(item).strip()]
188
  if not tickers:
189
+ yield "❌ Error: No valid tickers found", "{}"
190
  return
191
+ except:
192
+ yield '❌ Invalid format. Use: ["TICKER1", "TICKER2"]', "{}"
193
  return
194
 
195
+ # Setup
196
  today_str = date.today().strftime("%Y-%m-%d")
197
  time_range = get_time_range_in_unix_ms(today_str, today_str)
198
  start_time, end_time = time_range["start_timestamp_ms"], time_range["end_timestamp_ms"]
199
+ intervals = [15, 1440] # Process 15m first, then 1440m
200
 
201
  total_requests = len(tickers) * len(intervals)
 
202
 
203
  try:
 
204
  overall_start = datetime.now()
205
 
206
+ # Update progress
207
+ yield f"πŸš€ Starting: {len(tickers)} tickers Γ— {len(intervals)} intervals = {total_requests} requests", "{}"
208
+
209
+ # Fetch all data using interval-based batching
210
+ results = await fetch_all_data_by_intervals(tickers, start_time, end_time, intervals)
211
 
212
+ # Merge data quickly
213
+ merge_start = datetime.now()
214
+ final_json = merge_data_fast(results)
215
+ merge_time = (datetime.now() - merge_start).total_seconds()
216
 
217
  total_time = (datetime.now() - overall_start).total_seconds()
218
 
219
+ success_count = len([r for r in results if r.get("error") is None])
220
+ yield f"βœ… Complete! {success_count}/{total_requests} successful in {total_time:.2f}s (merge: {merge_time:.3f}s)", final_json
221
 
222
  except Exception as e:
223
+ logger.error(f"Processing error: {e}")
224
+ yield f"❌ Error: {str(e)}", "{}"
225
 
226
 
227
  # --- Gradio UI ---
228
 
229
+ with gr.Blocks(theme=gr.themes.Soft(), title="Stock Data Processor") as demo:
230
+ gr.Markdown("# ⚑ Stock Data Processor - Interval-Optimized")
231
+ gr.Markdown("**Strategy**: Process 15m requests in batches of 30, then 1440m requests in batches of 30, then merge results.")
232
 
233
  with gr.Row():
234
  with gr.Column(scale=1):
235
  tickers_input = gr.Textbox(
236
+ lines=4,
237
+ label='πŸ“ˆ Stock Tickers (Python List Format)',
238
+ value='["RELIANCE", "INFY", "TCS"]',
239
+ placeholder='["TICKER1", "TICKER2", "TICKER3", ...]'
240
  )
241
+ start_button = gr.Button("πŸš€ Process Data", variant="primary", size="lg")
242
 
243
+ with gr.Accordion("ℹ️ Performance Info", open=False):
244
+ gr.Markdown("""
245
+ **Optimization Strategy:**
246
+ 1. Process all 15m interval requests first (batches of 30)
247
+ 2. Process all 1440m interval requests next (batches of 30)
248
+ 3. Merge all results into final JSON
249
+
250
+ **Expected Speed:** ~1-3 seconds for small ticker lists
251
+ """)
252
 
253
  with gr.Column(scale=2):
254
+ logs_output = gr.Textbox(label="πŸ“Š Processing Logs", lines=8, interactive=False)
255
+ json_output = gr.JSON(label="πŸ“‹ Final Results", show_label=True)
256
 
257
+ # Event handlers
258
  start_button.click(
259
  fn=run_backend_processing,
260
  inputs=[tickers_input],
261
  outputs=[logs_output, json_output]
262
  )
263
+
264
  tickers_input.submit(
265
  fn=run_backend_processing,
266
  inputs=[tickers_input],
 
268
  )
269
 
270
  if __name__ == "__main__":
271
+ demo.launch(share=False, server_name="0.0.0.0")