"""模拟盘 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)。 简化架构:实走用全局 job(register_live_step_job),此函数保留兼容老 per-account 设计。 """ 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) def register_live_step_job(db_path: str, hour: int = 20, minute: int = 30) -> str: """全局每日 step job:20:30 遍历所有 live accounts 调 run_live_step(C-S3 简化架构)。 job_func 用 lambda 闭包 db_path(add_job 只传无参 callable)。 """ from .live_orchestrator import run_live_step # lazy 避循环 import s = start_scheduler() s.add_job( lambda: run_live_step(db_path), "cron", hour=hour, minute=minute, id="live_step_global", replace_existing=True, ) return "live_step_global"