Files
OpenFin/backend/services/wavelet_scheduler.py
2026-07-24 16:14:06 +02:00

77 lines
2.9 KiB
Python

"""
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()