Spaces:
Paused
Paused
fix: auto top-up stale prices from yfinance on startup — chart always shows latest data
Browse files- dashboard/app.py +42 -21
dashboard/app.py
CHANGED
|
@@ -311,39 +311,60 @@ def _ensure_schema():
|
|
| 311 |
|
| 312 |
@st.cache_resource
|
| 313 |
def _ensure_prices():
|
| 314 |
-
"""
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
conn.close()
|
| 319 |
-
if count > 100:
|
| 320 |
-
return
|
| 321 |
try:
|
| 322 |
import yfinance as yf
|
| 323 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
symbols = list(SYMBOL_NAMES.keys())
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
if ticker_data.empty:
|
| 327 |
return
|
| 328 |
-
|
|
|
|
| 329 |
for sym in symbols:
|
| 330 |
try:
|
| 331 |
-
if len(symbols)
|
| 332 |
-
|
| 333 |
-
else:
|
| 334 |
-
df = ticker_data.xs(sym, axis=1, level=1) if sym in ticker_data.columns.get_level_values(1) else None
|
| 335 |
-
if df is None or (hasattr(df, 'empty') and df.empty):
|
| 336 |
continue
|
| 337 |
df = df.reset_index()
|
| 338 |
df.columns = [c.lower() for c in df.columns]
|
| 339 |
-
for _,
|
| 340 |
try:
|
| 341 |
conn2.execute(
|
| 342 |
-
"INSERT OR REPLACE INTO prices
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
float(
|
|
|
|
|
|
|
| 347 |
)
|
| 348 |
except Exception:
|
| 349 |
pass
|
|
|
|
| 311 |
|
| 312 |
@st.cache_resource
|
| 313 |
def _ensure_prices():
|
| 314 |
+
"""Keep prices up to date on every container start.
|
| 315 |
+
- If DB is empty: full 5-year backfill from yfinance.
|
| 316 |
+
- If DB has data but is stale (latest < yesterday): fetch missing days only.
|
| 317 |
+
Runs once per process lifetime."""
|
|
|
|
|
|
|
|
|
|
| 318 |
try:
|
| 319 |
import yfinance as yf
|
| 320 |
+
from datetime import date as _date, timedelta
|
| 321 |
+
|
| 322 |
+
conn = get_conn()
|
| 323 |
+
row = conn.execute(
|
| 324 |
+
"SELECT COUNT(*), MAX(date) FROM prices"
|
| 325 |
+
).fetchone()
|
| 326 |
+
conn.close()
|
| 327 |
+
count, latest_date = row[0], row[1]
|
| 328 |
+
|
| 329 |
+
yesterday = (_date.today() - timedelta(days=1)).isoformat()
|
| 330 |
+
|
| 331 |
+
# Nothing to do if already current
|
| 332 |
+
if count > 100 and latest_date and str(latest_date) >= yesterday:
|
| 333 |
+
return
|
| 334 |
+
|
| 335 |
+
# Full backfill for empty DB, incremental top-up otherwise
|
| 336 |
+
period = "5y" if count < 100 else None
|
| 337 |
+
start = None if period else (str(latest_date) if latest_date else "2020-01-01")
|
| 338 |
+
|
| 339 |
symbols = list(SYMBOL_NAMES.keys())
|
| 340 |
+
kwargs = dict(auto_adjust=True, progress=False)
|
| 341 |
+
if period:
|
| 342 |
+
kwargs["period"] = period
|
| 343 |
+
else:
|
| 344 |
+
kwargs["start"] = start
|
| 345 |
+
|
| 346 |
+
ticker_data = yf.download(symbols, **kwargs)
|
| 347 |
if ticker_data.empty:
|
| 348 |
return
|
| 349 |
+
|
| 350 |
+
conn2 = get_conn()
|
| 351 |
for sym in symbols:
|
| 352 |
try:
|
| 353 |
+
df = ticker_data.xs(sym, axis=1, level=1) if len(symbols) > 1 else ticker_data.copy()
|
| 354 |
+
if df is None or df.empty:
|
|
|
|
|
|
|
|
|
|
| 355 |
continue
|
| 356 |
df = df.reset_index()
|
| 357 |
df.columns = [c.lower() for c in df.columns]
|
| 358 |
+
for _, r in df.iterrows():
|
| 359 |
try:
|
| 360 |
conn2.execute(
|
| 361 |
+
"INSERT OR REPLACE INTO prices "
|
| 362 |
+
"(date,symbol,open,high,low,close,volume,adj_close) "
|
| 363 |
+
"VALUES (?,?,?,?,?,?,?,?)",
|
| 364 |
+
[str(r["date"])[:10], sym,
|
| 365 |
+
float(r.get("open") or 0), float(r.get("high") or 0),
|
| 366 |
+
float(r.get("low") or 0), float(r.get("close") or 0),
|
| 367 |
+
float(r.get("volume") or 0), float(r.get("close") or 0)]
|
| 368 |
)
|
| 369 |
except Exception:
|
| 370 |
pass
|