lhllamlam commited on
Commit
99c4ea5
·
verified ·
1 Parent(s): 5f63009

Add daily auto-scan: runs on startup if stale, then every 24h

Browse files
Files changed (1) hide show
  1. app.py +93 -2
app.py CHANGED
@@ -9,7 +9,8 @@ from __future__ import annotations
9
  import os
10
  import sys
11
  import threading
12
- from datetime import datetime
 
13
  from typing import Optional
14
 
15
  import numpy as np
@@ -77,9 +78,21 @@ RESULTS_STATE: dict = {
77
  "last_run": None,
78
  "last_msg": "No scan run yet.",
79
  "scanned_universe": [],
 
80
  }
81
 
82
 
 
 
 
 
 
 
 
 
 
 
 
83
  # ---------------------------------------------------------------------------
84
  # Helpers
85
  # ---------------------------------------------------------------------------
@@ -87,7 +100,11 @@ RESULTS_STATE: dict = {
87
  def _format_status() -> str:
88
  n = 0 if RESULTS_STATE["df"] is None else len(RESULTS_STATE["df"])
89
  last = RESULTS_STATE["last_run"].strftime("%Y-%m-%d %H:%M:%S") if RESULTS_STATE["last_run"] else "never"
90
- return f"**Last run:** {last} • {n} stocks • {RESULTS_STATE['last_msg']}"
 
 
 
 
91
 
92
 
93
  def _result_columns() -> list[str]:
@@ -305,6 +322,76 @@ def _start_auto_improve(base_weights: dict) -> None:
305
  threading.Thread(target=_worker, daemon=True).start()
306
 
307
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  # ---------------------------------------------------------------------------
309
  # Scan logic
310
  # ---------------------------------------------------------------------------
@@ -771,6 +858,10 @@ def build_ui() -> gr.Blocks:
771
  demo = build_ui()
772
 
773
 
 
 
 
 
774
  if __name__ == "__main__":
775
  demo.queue(max_size=8).launch(
776
  server_name="0.0.0.0",
 
9
  import os
10
  import sys
11
  import threading
12
+ import time
13
+ from datetime import datetime, timedelta
14
  from typing import Optional
15
 
16
  import numpy as np
 
78
  "last_run": None,
79
  "last_msg": "No scan run yet.",
80
  "scanned_universe": [],
81
+ "next_daily_run": None, # datetime of the next scheduled auto-scan
82
  }
83
 
84
 
85
+ # Daily auto-scan configuration
86
+ DAILY_SCAN_INITIAL_DELAY_SEC = 20 # let the app + Gradio queue boot
87
+ DAILY_SCAN_PERIOD_SEC = 24 * 3600 # 24 hours between scheduled scans
88
+
89
+
90
+ class _NoOpProgress:
91
+ """No-op stand-in for ``gr.Progress()`` used by background scans."""
92
+ def __call__(self, frac, desc=None):
93
+ pass
94
+
95
+
96
  # ---------------------------------------------------------------------------
97
  # Helpers
98
  # ---------------------------------------------------------------------------
 
100
  def _format_status() -> str:
101
  n = 0 if RESULTS_STATE["df"] is None else len(RESULTS_STATE["df"])
102
  last = RESULTS_STATE["last_run"].strftime("%Y-%m-%d %H:%M:%S") if RESULTS_STATE["last_run"] else "never"
103
+ nxt = RESULTS_STATE.get("next_daily_run")
104
+ nxt_str = ""
105
+ if nxt is not None:
106
+ nxt_str = f" • **Next auto-scan:** {nxt.strftime('%Y-%m-%d %H:%M:%S')} UTC"
107
+ return f"**Last run:** {last} • {n} stocks • {RESULTS_STATE['last_msg']}{nxt_str}"
108
 
109
 
110
  def _result_columns() -> list[str]:
 
322
  threading.Thread(target=_worker, daemon=True).start()
323
 
324
 
325
+ # ---------------------------------------------------------------------------
326
+ # Daily auto-scan
327
+ # ---------------------------------------------------------------------------
328
+
329
+ def _run_scheduled_scan(reason: str) -> None:
330
+ """Run a single full-universe-Filtered scan with the slider defaults
331
+ and update :data:`RESULTS_STATE`. Used by :func:`_start_daily_scan`.
332
+ """
333
+ weights, _ = _resolve_weights(
334
+ False,
335
+ DEFAULT_WEIGHTS["cmf"],
336
+ DEFAULT_WEIGHTS["obv_slope"],
337
+ DEFAULT_WEIGHTS["big_bar_ratio"],
338
+ DEFAULT_WEIGHTS["vwap_dev"],
339
+ DEFAULT_WEIGHTS["rvol_signed"],
340
+ )
341
+ try:
342
+ df, frames, scanned, msg = _do_scan(
343
+ "Filtered (default)", 5.0, 5_000_000.0, "All", "All",
344
+ weights, _NoOpProgress(),
345
+ )
346
+ except Exception as e:
347
+ RESULTS_STATE["last_msg"] = f"Error ({reason}): {e}"
348
+ return
349
+
350
+ suffix = f" (auto: {reason})"
351
+ RESULTS_STATE["df"] = df
352
+ RESULTS_STATE["frames"] = frames
353
+ RESULTS_STATE["weights"] = weights
354
+ RESULTS_STATE["last_run"] = datetime.utcnow()
355
+ RESULTS_STATE["last_msg"] = f"{msg}{suffix}"
356
+ RESULTS_STATE["scanned_universe"] = scanned
357
+ if df is not None and not df.empty:
358
+ _start_auto_improve(weights)
359
+
360
+
361
+ def _start_daily_scan() -> None:
362
+ """Background thread: run a scan on startup (if the last one is
363
+ older than :data:`DAILY_SCAN_PERIOD_SEC`), then re-run once every
364
+ 24 hours. HF Spaces can sleep after 48h of inactivity, so the
365
+ startup gate is what actually delivers the "one scan per day"
366
+ guarantee: a fresh visit always triggers a scan if the cached one
367
+ is stale.
368
+ """
369
+ def _worker():
370
+ time.sleep(DAILY_SCAN_INITIAL_DELAY_SEC)
371
+ # Initial scan: only if last successful scan is stale
372
+ last = RESULTS_STATE.get("last_run")
373
+ stale = (
374
+ last is None
375
+ or (datetime.utcnow() - last).total_seconds() > DAILY_SCAN_PERIOD_SEC
376
+ )
377
+ if stale:
378
+ try:
379
+ _run_scheduled_scan("startup")
380
+ except Exception as e:
381
+ RESULTS_STATE["last_msg"] = f"Auto-scan (startup) failed: {e}"
382
+ # Then loop, scheduling a scan every 24h
383
+ while True:
384
+ RESULTS_STATE["next_daily_run"] = (
385
+ datetime.utcnow() + timedelta(seconds=DAILY_SCAN_PERIOD_SEC)
386
+ )
387
+ time.sleep(DAILY_SCAN_PERIOD_SEC)
388
+ try:
389
+ _run_scheduled_scan("daily")
390
+ except Exception as e:
391
+ RESULTS_STATE["last_msg"] = f"Auto-scan (daily) failed: {e}"
392
+ threading.Thread(target=_worker, daemon=True).start()
393
+
394
+
395
  # ---------------------------------------------------------------------------
396
  # Scan logic
397
  # ---------------------------------------------------------------------------
 
858
  demo = build_ui()
859
 
860
 
861
+ # Kick off the once-per-day auto-scan thread
862
+ _start_daily_scan()
863
+
864
+
865
  if __name__ == "__main__":
866
  demo.queue(max_size=8).launch(
867
  server_name="0.0.0.0",