""" Periodic Saxo-priced Watchlist refresh + wavelet recompute — mirrors services/saxo_scheduler.py's pattern (own thread, own config-driven interval, `while not stop.wait(0)` so the first pass runs immediately on startup). Independent of services/auto_cycle.py's once-a-day cycle, which also runs this same computation as one of its steps but only at cycle cadence — this lets the Dashboard's Wavelets Signal card and Instrument Analysis's cached Wavelet tab (services.wavelet_signals. scan_watchlist_wavelet_signals writes both) stay current without waiting for, or manually triggering, a full cycle. """ from __future__ import annotations import logging import threading import uuid logger = logging.getLogger(__name__) _thread: threading.Thread | None = None _stop = threading.Event() DEFAULT_REFRESH_MINUTES = 15 def get_settings() -> dict: from .database import get_config enabled = (get_config("wavelet_refresh_enabled") or "true").lower() == "true" try: minutes = float(get_config("wavelet_refresh_minutes") or str(DEFAULT_REFRESH_MINUTES)) except (TypeError, ValueError): minutes = DEFAULT_REFRESH_MINUTES return {"enabled": enabled, "refresh_minutes": minutes} def set_settings(enabled: bool, refresh_minutes: float) -> None: from .database import set_config set_config("wavelet_refresh_enabled", "true" if enabled else "false") set_config("wavelet_refresh_minutes", str(max(1.0, refresh_minutes))) def run_refresh_pass() -> int: """One pass over the whole Watchlist: Saxo-first price fetch + wavelet recompute (see services.wavelet_signals.scan_watchlist_wavelet_signals) — the same work the daily cycle's own wavelet step does, just callable on its own cadence. Shared by the periodic loop and the manual 'refresh now' button. Returns the number of signal rows written.""" from .wavelet_signals import compute_and_save_wavelet_signals run_id = f"refresh-{uuid.uuid4().hex[:10]}" try: results = compute_and_save_wavelet_signals(run_id) logger.info(f"[Wavelet Scheduler] Refresh pass complete: {len(results)} signal rows ({run_id})") return len(results) except Exception as e: logger.warning(f"[Wavelet Scheduler] Refresh pass failed: {e}") return 0 def _loop(stop: threading.Event): while not stop.wait(0): settings = get_settings() if not settings["enabled"]: stop.wait(timeout=300) # re-check periodically in case it gets enabled without a restart continue run_refresh_pass() stop.wait(timeout=settings["refresh_minutes"] * 60) def start_wavelet_scheduler(): global _thread _stop.clear() if not (_thread and _thread.is_alive()): _thread = threading.Thread(target=_loop, args=(_stop,), name="wavelet-refresh", daemon=True) _thread.start() logger.info("[Wavelet Scheduler] Started") def stop_wavelet_scheduler(): _stop.set()