You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

82 lines
2.4 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
APScheduler entry point for gainer snapshot collection.
Job: every 5 minutes on Mon-Fri ET, guarded to NYSE market hours (9:3016:00 ET).
Note: US market holidays are treated as normal weekdays — Yahoo simply returns
fewer or no results on those days, which is harmless.
"""
import logging
from datetime import datetime, time
logger = logging.getLogger(__name__)
_scheduler = None
def _is_market_open() -> bool:
"""Return True if current ET time is within NYSE regular session (9:3016:00)."""
try:
from zoneinfo import ZoneInfo
now_et = datetime.now(ZoneInfo("America/New_York"))
except ImportError:
import pytz
now_et = datetime.now(pytz.timezone("America/New_York"))
return time(9, 30) <= now_et.time() <= time(16, 0)
def _get_scheduler():
global _scheduler
if _scheduler is None:
try:
from apscheduler.schedulers.asyncio import AsyncIOScheduler
_scheduler = AsyncIOScheduler(timezone="UTC")
except ImportError:
logger.warning("apscheduler not installed; gainer collection disabled")
return None
return _scheduler
async def _run_collect_job() -> None:
if not _is_market_open():
return
try:
from app.services.gainers.collector import collect_gainer_snapshot
await collect_gainer_snapshot()
except Exception as e:
logger.error("[Gainers] collection failed: %s", e)
def start_gainer_scheduler() -> None:
sched = _get_scheduler()
if sched is None:
return
try:
from apscheduler.triggers.cron import CronTrigger
sched.add_job(
_run_collect_job,
trigger=CronTrigger(
minute="*/5",
day_of_week="mon-fri",
timezone="America/New_York",
),
id="gainer_snapshot_collect",
replace_existing=True,
max_instances=1,
misfire_grace_time=120,
coalesce=True,
)
if not sched.running:
sched.start()
logger.info("[Gainers] scheduler started — every 5 min Mon-Fri ET (market hours guard)")
except Exception as e:
logger.error("[Gainers] scheduler start failed: %s", e)
def stop_gainer_scheduler() -> None:
sched = _get_scheduler()
if sched and sched.running:
sched.shutdown(wait=False)
logger.info("[Gainers] scheduler stopped")