"""模拟盘 C 实走调度(APScheduler 定时 + 启动恢复,spec §9.2)。 每日 20:30 触发 step(拉当日 bar + engine.step)。APScheduler 是 pip 依赖, lazy import;本机无则单测 mock get_scheduler。 """ import logging import sqlite3 logger = logging.getLogger(__name__) _scheduler = None def get_scheduler(): global _scheduler if _scheduler is None: from apscheduler.schedulers.background import BackgroundScheduler _scheduler = BackgroundScheduler() return _scheduler def start_scheduler(): s = get_scheduler() if not s.running: s.start() return s def register_paper_job(account_id: int, hour: int = 20, minute: int = 30, job_func=None) -> str: """注册每日定时 job,返回 job_id(存 paper_accounts.scheduler_job_id)。""" s = start_scheduler() job_id = f"paper_{account_id}" s.add_job( job_func or _default_step, "cron", # 字符串 trigger,避免函数内 import CronTrigger hour=hour, minute=minute, args=[account_id], id=job_id, replace_existing=True, ) return job_id def remove_paper_job(account_id: int) -> None: s = get_scheduler() job_id = f"paper_{account_id}" try: s.remove_job(job_id) except Exception: logger.warning("remove_job %s not found", job_id) def _default_step(account_id: int) -> None: """部署时注入真 step(akshare 拉当日 + engine.step);默认 stub。""" logger.info("paper account %s daily step (stub)", account_id) def restore_live_jobs(db_path: str, job_func=None) -> int: """容器启动:遍历 status=running & mode=live 重建 job(spec §9.2 H-3)。""" with sqlite3.connect(db_path) as conn: rows = conn.execute( "SELECT id FROM paper_accounts WHERE status='running' AND mode='live'" ).fetchall() for (aid,) in rows: register_paper_job(aid, job_func=job_func) return len(rows)