Karan6124 commited on
Commit
491dfdd
·
1 Parent(s): 51b2069

feat: implement dynamic historical backfilling from yfinance in get_stock_history

Browse files
Files changed (1) hide show
  1. backend/app/database/crud.py +66 -1
backend/app/database/crud.py CHANGED
@@ -201,8 +201,73 @@ async def get_stock_history(db: AsyncSession, ticker: str, limit: int = 100) ->
201
  .order_by(models.StockHistory.timestamp.desc())
202
  .limit(limit)
203
  )
204
- # Return in chronological order (oldest to newest) for indicators
205
  history = list(result.scalars().all())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  history.reverse()
207
  return history
208
 
 
201
  .order_by(models.StockHistory.timestamp.desc())
202
  .limit(limit)
203
  )
 
204
  history = list(result.scalars().all())
205
+
206
+ # If database has under 100 records for this ticker, dynamically backfill from yfinance
207
+ if len(history) < 100:
208
+ import yfinance as yf
209
+ import asyncio
210
+
211
+ try:
212
+ # yfinance allows fetching 1m interval historical data up to 30 days. We fetch last 5 days.
213
+ yf_ticker = yf.Ticker(ticker.upper())
214
+ df = await asyncio.get_event_loop().run_in_executor(
215
+ None,
216
+ lambda: yf_ticker.history(period="5d", interval="1m")
217
+ )
218
+
219
+ if not df.empty:
220
+ df = df.reset_index()
221
+
222
+ # Identify timestamp column
223
+ time_col = None
224
+ for col in ['Date', 'Datetime', 'index', 'timestamp']:
225
+ if col in df.columns:
226
+ time_col = col
227
+ break
228
+
229
+ if time_col:
230
+ candles_to_insert = []
231
+ for _, row in df.iterrows():
232
+ ts = row[time_col]
233
+ if hasattr(ts, 'to_pydatetime'):
234
+ ts_dt = ts.to_pydatetime()
235
+ elif isinstance(ts, str):
236
+ ts_dt = datetime.datetime.fromisoformat(ts)
237
+ else:
238
+ ts_dt = ts
239
+
240
+ if ts_dt.tzinfo is not None:
241
+ ts_dt = ts_dt.replace(tzinfo=None)
242
+
243
+ candles_to_insert.append({
244
+ "ticker": ticker.upper(),
245
+ "timestamp": ts_dt,
246
+ "open": float(row["Open"]),
247
+ "high": float(row["High"]),
248
+ "low": float(row["Low"]),
249
+ "close": float(row["Close"]),
250
+ "volume": int(row["Volume"]) if "Volume" in row else 0
251
+ })
252
+
253
+ if candles_to_insert:
254
+ # Perform batch insert ignoring duplicates
255
+ stmt = pg_insert(models.StockHistory).values(candles_to_insert)
256
+ await db.execute(stmt.on_conflict_do_nothing(index_elements=["ticker", "timestamp"]))
257
+ await db.commit()
258
+
259
+ # Re-query the database with the fully backfilled candles
260
+ result = await db.execute(
261
+ select(models.StockHistory)
262
+ .where(models.StockHistory.ticker == ticker.upper())
263
+ .order_by(models.StockHistory.timestamp.desc())
264
+ .limit(limit)
265
+ )
266
+ history = list(result.scalars().all())
267
+ except Exception as e:
268
+ print(f"MLOps Dynamic Backfill: Failed to populate history for {ticker}: {e}")
269
+
270
+ # Return in chronological order (oldest to newest) for indicators
271
  history.reverse()
272
  return history
273