# sanguo_data/scheduler.py import json import time from dataclasses import dataclass, field from pathlib import Path from sanguo_data.config import DataConfig from sanguo_data.datafeed import fetch_daily from sanguo_data.validator import validate_daily from sanguo_data.datawriter import write_daily @dataclass class UpdateReport: total: int = 0 success: int = 0 failed: int = 0 skipped: int = 0 failures: list = field(default_factory=list) def run_daily_update(cfg: DataConfig, symbols: list[str] | None = None) -> UpdateReport: progress_path = Path(cfg.data_paths.get("progress_file", "progress.json")) progress = json.loads(progress_path.read_text()) if progress_path.exists() else {} symbols = symbols or _load_stock_list(cfg) report = UpdateReport(total=len(symbols)) fail_window = cfg.performance.get("fail_window", 100) fail_threshold = cfg.performance.get("fail_threshold", 0.8) for sym in symbols: if progress.get(sym) == "done": report.skipped += 1 continue try: df = fetch_daily(sym, _last_date(sym, cfg), _today(), cfg) df = validate_daily(df) if len(df) > 0: write_daily(sym, df, cfg) progress[sym] = "done" progress_path.write_text(json.dumps(progress, ensure_ascii=False)) report.success += 1 except Exception as e: report.failed += 1 report.failures.append({"symbol": sym, "error": str(e)}) checked = report.success + report.failed if checked >= fail_window and report.failed / max(checked, 1) > fail_threshold: report.failures.append({"error": "FAIL_THRESHOLD_REACHED, abort"}) break time.sleep(cfg.performance.get("request_interval", 0.3)) return report def _load_stock_list(cfg: DataConfig) -> list[str]: """从 v1 data_platform/daily_all_update.py copy 全市场股票列表读取""" raise NotImplementedError("copy from v1") def _last_date(symbol: str, cfg: DataConfig) -> str: return "2020-01-01" # 简化,实际读 parquet 最后日期 def _today() -> str: return "2026-07-05"