# 优化市场数据同步并增加完整校验入口 ## Goal 把当前接近四小时的市场数据同步缩短到可接受范围,同时保留六年 qfq 历史修订检查、单股票失败隔离、PostgreSQL/CSV 发布一致性和选股覆盖率契约。 日常同步由外部调度器执行;耗时较长的完整检查改为用户在 Web 页面中主动触发并查看进度与结果。 ## Background - 2026-08-10 生产批次处理 5002 只股票,总耗时 13339.3 秒;行情阶段后半段稳定在 2.525–2.820 秒/只,属于逐股固定成本。 - 当前实现串行调用 5002 次 `pro_bar(..., adj="qfq")`。本机 Tushare 1.4.29 源码表明每次 qfq `pro_bar` 至少调用一次 `daily` 和一次 `adj_factor`。 - `Settings.market_data_max_workers` 当前默认 4,但未被同步用例或 CLI 使用。 - 旧项目 `../zgnb-project` 使用 `ThreadPoolExecutor(max_workers=8)` 逐股拉取六年 `pro_bar`;识别频控错误后按 60/120/180 秒退避,普通错误按 5/10/15 秒退避。 它是八路逐股并发,不是按交易日一次请求全市场。 - 当前 PostgreSQL 适配器在单股 upsert、单股审计记录和覆盖率存在性检查中频繁创建 新连接;bar 阶段完成后仍耗时约 110.2 秒才完成批次。 - 当前 Web 导航已有未启用的“同步任务”入口,后端已有 selection 的 `202 Accepted + BackgroundTasks + polling` 长任务模式,但生产服务重启会中断进程内任务。 ## Requirements ### R1. 八路受控并发 - 需要同步单股票行情时默认使用 8 个 worker,并允许通过 `ZHIXING_MARKET_DATA_MAX_WORKERS` 配置。 - worker 必须保留单股票事务、临时 CSV、数据库提交后原子发布和单股票失败隔离语义。 - Tushare 频控由所有 worker 协同处理,不能让一个 worker 进入长退避时其他 worker 继续无界冲击接口;重试次数、退避和最终失败必须可观测且不泄露凭据。 - 同一环境仍只允许一个市场数据同步或完整检查批次持有 advisory lock。 ### R2. 日常同步与完整检查分离 - 外部调度器继续触发日常同步,日常同步不依赖浏览器或 FastAPI 生命周期定时器。 - 日常同步继续按旧项目的数据口径,对全部目标股票逐股获取六年 qfq 完整快照;性能优化来自 8 路受控并发,而不是改成按目标交易日增量拉取。 - 本任务不引入 `adj_factor` 持久化或按复权因子变化选择性重建历史的增量方案。 - PostgreSQL/CSV 完整性检查不由 cron 自动触发,只允许用户从 HTTP/Web 主动发起;该检查 不调用 Tushare,也不重新下载行情。 - 完整检查需要持久化批次、目标/已检查/问题计数和安全错误,并提供轮询查询契约。 - 用户可在“同步任务”页面触发完整检查;运行中禁止重复触发,并持续展示进度和最终结果。 - 完整检查只报告问题:除检查批次和问题明细外,不修改股票主数据、行情、估值或 CSV;发现的 异常由后续日常同步修复。 ### R3. PostgreSQL 批量化 - 同一个批次复用有限数量的数据库连接,避免每只股票为 upsert、审计记录和覆盖率检查 分别建立新连接。 - 同步 item 审计记录支持批量写入,同时保留失败股票的可定位记录。 - 覆盖率通过集合 SQL 一次计算,不逐股票执行 `has_bar` / `has_daily_basic`。 - 并发写入不得破坏 `(ts_code, trade_date)` 幂等约束或批次汇总计数。 ### R4. 兼容性 - 保留现有 CLI 成功/部分成功/失败退出码、覆盖率阈值和失败重试语义。 - 保留六年滚动窗口、qfq 唯一口径、PostgreSQL 事实源和 CSV 恢复快照边界。 - 日常同步结果仍能作为选股批次的数据来源,完整检查不得让正在使用的最近成功批次 短暂变成不可用。 ## Acceptance Criteria - [ ] 默认并发数为 8,配置覆盖有效;并发测试能证明最多只有配置数量的单股任务同时执行。 - [ ] 模拟 Tushare 频控时,所有 worker 服从共享冷却窗口,随后成功恢复或在重试预算耗尽后记录失败。 - [ ] 单股票失败不发布其临时 CSV,不回滚其他成功股票,重复执行保持幂等。 - [ ] 日常同步与手动完整检查的职责符合最终确认的数据口径。 - [ ] `POST` 完整检查返回可轮询的批次标识;重复触发返回冲突;查询端点展示进度、状态和错误。 - [ ] “同步任务”页面可触发完整检查并展示无记录、运行、通过、发现问题、执行失败状态。 - [ ] 完整检查不调用 Tushare,不修改市场事实表或正式 CSV;有问题时只持久化安全报告。 - [ ] 覆盖率计算不再产生逐股票查询;审计记录使用批量写入;连接创建数量不随股票数线性增长。 - [ ] 5002 只股票的无网络性能测试/受控基准锁定并发调度和数据库调用数量;生产目标为正常频控条件下 完整下载约 30 分钟,结果需通过部署后日志验证,不能用本地 mock 代替生产结论。 - [ ] 后端 Ruff、Pyright、pytest,前端格式、lint、typecheck、Vitest 和构建全部通过。 ## Out of Scope - 不新增分钟级或实时行情。 - 不改变选股公式、覆盖率阈值业务含义或六年保留边界。 - 不自动安排周期性完整检查。 - 不把日常同步改造成按交易日/复权因子增量拉取。 - 不在完整检查中自动修复、重试或重新下载异常对象。 - 不在本任务中引入与市场数据无关的通用任务平台。