65 lines
2.0 KiB
Python
65 lines
2.0 KiB
Python
"""模拟盘 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)
|