# 市场数据同步代码规格 ## Scenario: Tushare qfq 日线与每日指标一次性同步 ### 1. Scope / Trigger - 触发:新增 `modules/market_data` bounded context、Alembic PostgreSQL schema、Tushare/CSV/Compose Job 集成。 - 目标:外部调度器每次启动一个进程,向 PostgreSQL 写入当前沪深非 ST A 股的六年 qfq 日线和按交易日归属的 `daily_basic`,并发布可恢复 CSV。 - 边界:FastAPI 生命周期不启动定时器;本上下文不实现具体选股策略、分钟行情或退市成员资格回溯。 ### 2. Signatures - 应用用例:`SyncMarketData.execute(command: SyncMarketDataCommand | None = None) -> SyncBatchSummary`。 - CLI:`market-data-sync [--initialize | --retry-batch-id ] [--trade-date YYYY-MM-DD]`。 - 数据源端口:`fetch_stocks()`、`fetch_open_dates(start, end)`、`fetch_bars(ts_code, window)`、`fetch_daily_basic(trade_date)`。 - 数据库事实表:`market_stock`、`market_daily_bar(ts_code, trade_date)`、`market_daily_basic(ts_code, trade_date)`;同步审计表:`market_sync_batch`、`market_sync_item`。 ### 3. Contracts - 配置入口只允许 `bootstrap.config.Settings` 读取环境变量: - `ZHIXING_DATABASE_URL` - `ZHIXING_TUSHARE_TOKEN` - `ZHIXING_MARKET_DATA_CSV_ROOT` - `ZHIXING_MARKET_DATA_COVERAGE_THRESHOLD`,默认 `0.99` - `ZHIXING_MARKET_DATA_MAX_RETRIES`、`ZHIXING_MARKET_DATA_RETRY_BACKOFF_SECONDS`、`ZHIXING_MARKET_DATA_REQUEST_INTERVAL_SECONDS` - `ZHIXING_MARKET_DATA_ADVISORY_LOCK_KEY` - `ZHIXING_DATABASE_URL` 使用普通 `postgresql://...` 形式供 Psycopg 3 直接连接;Alembic/SQLAlchemy 在边界处转换为 `postgresql+psycopg://...`,不得丢失 `sslmode` 等 query 参数。 - 开发 Compose 使用项目自带 PostgreSQL;生产 Compose 不声明内部 PostgreSQL 服务,`server`、`migrate` 和 `market-sync` 连接外部 Docker 网络 `1panel-network`。`server` 同时保留 Compose 默认网络以供 web 访问。 - 当前生产 PostgreSQL 在 `1panel-network` 上的稳定别名为 `postgresql`,服务端 SSL 为关闭状态,生产连接串使用 `sslmode=disable`;数据库用户必须具备 `public` schema 的 `CREATE` 权限以执行 Alembic 迁移。 - 股票 qfq 快照路径为 `bars/.csv`;每日指标路径为 `daily-basic//.csv`;当前股票主数据为 `stock-basic/current.csv`。 - `market_daily_bar` 的唯一键是 `(ts_code, trade_date)`,`source_adj` 必须是 `qfq`;所有价格、金额和比率使用有限 `NUMERIC`/`Decimal`。 - `SyncBatchSummary` 至少返回 `batch_id`、目标交易日、窗口、状态、目标数、有效数、覆盖率、策略资格、插入数、更新数、未变化数和失败列表。 - 退出码:`0` 表示覆盖率达标的成功批次;`2` 表示部分成功或覆盖率不足;`1` 表示失败或没有可用成功结果。 - 单股票流程必须保持“临时 CSV → 指纹比较 → PostgreSQL 事务 → `os.replace` 发布”顺序;数据库异常不得替换正式 CSV。 ### 4. Validation & Error Matrix | 条件 | 行为 | | --- | --- | | 非 `SH`/`SZ`、非上市、ST/退市名称或北交所股票 | 从当前目标股票池排除 | | 指定日期不是交易日 | 批次准备失败,不发起行情写入 | | qfq 快照重复键、空快照、OHLC 非法 | 当前股票 item 失败,旧 CSV 和旧库行保持不变 | | 重叠区间指纹相同 | 只批量插入旧上界之后的新交易日 | | 重叠行字段变化、缺失或快照开始边界异常变化 | 当前股票执行六年窗口集合式 upsert/删除 | | `daily_basic` 没有目标股票行 | 当前指标日期 item 失败,不发布空正式快照 | | PostgreSQL/COPY/迁移异常 | 转换为安全的 repository error;成功对象不回滚,失败对象保留旧发布版本 | | 同一环境已有同步锁 | 返回 `failed` 和退出码 `1`,不执行第二个批次 | | `valid / target < coverage_threshold` | 不触发选股,批次显式标记 `strategy_eligible=false` | ### 5. Good/Base/Bad Cases - Good:相同六年 qfq 返回重复执行,指纹相同,数据库无无意义 update,正式 CSV 可原子替换,覆盖率为 `1`。 - Base:新增一个开市日且历史重叠不变,只 COPY 新日期;批次成功后清理窗口起点之前的数据库行和日期文件。 - Bad:一只股票历史 qfq 行变化或数据库事务失败,只修复/失败该股票;其他股票成功结果保留,失败对象正式 CSV 不被替换。 ### 6. Tests Required - 领域单元测试:六年窗口边界、输入顺序稳定性、历史值变化、重叠缺行、窗口提前、ST/北交所过滤;断言 `SnapshotChange` 和股票代码集合。 - CSV 单元测试:固定表头、临时文件、原子发布、异常/丢弃后旧正式文件仍可读;断言正式文件内容和临时文件清理。 - 同步编排测试:`success`、覆盖率、幂等重跑、空指标失败、部分失败和失败 item 重试;断言 batch/item 状态与退出码。 - PostgreSQL 集成测试:设置 `ZHIXING_TEST_DATABASE_URL` 后运行 Alembic upgrade,断言五张业务/审计表、唯一键和迁移头;批量 upsert 测试断言 insert/update/unchanged 计数和事务回滚。 - 质量门禁:`uv lock --check`、后端 Ruff/Pyright/pytest、前端/root `./dev.sh check` 与 `./dev.sh test`、四种 Compose config。 ### 7. Wrong vs Correct #### Wrong ```python # 把最新总市值写入六年前每一根 K 线,产生前视偏差。 for bar in bars: bar.total_mv = latest_total_mv ``` #### Correct ```python # 行情和估值按各自交易日保存;策略只使用同一目标日的两类事实。 market_daily_bar[(ts_code, trade_date)] = bar market_daily_basic[(ts_code, trade_date)] = daily_basic ``` > **Warning**:CSV 发布不是数据库事务的一部分。只有数据库事务成功后才能执行原子 `os.replace`;发布失败必须记录为 item 失败并依靠幂等重试恢复。