# K线同步到本地数据库全景说明 更新时间:2026-06-11 ## 1. 背景 `data` 目录下已有两份前复权 K 线补数服务文档: - `HTTP与MySQL使用说明.md` - `HTTP与MySQL使用说明.local.md` 这两份文档说明了外部前复权 K 线服务如何通过 HTTP 查询任务/覆盖率,以及如何通过外部 MySQL 读取前复权日线、分钟线和 60 分钟线。 当前目标不是改造对方服务,也不是直接覆盖本地现有行情主表,而是在本地建立一个可定期运行的增量同步入口,把外部前复权 K 线读到本地 MySQL 的独立镜像表里。 ## 2. 用户硬要求 1. 不要改对方数据库的任何东西。 2. 咱们这边增量存储,不覆盖、不替换、不改旧行;允许新增表,也允许对本地现有主表做 append-only 追加。 3. 本文档要描述整个事件全景,并把两份原始连接/使用文档内容复制进来。 ## 3. 当前实现 同步脚本: ```text scripts/kline_front_incremental_sync.py ``` 脚本边界: - 只读外部 MySQL。 - 不调用外部 HTTP `/jobs`、`/repair`、`/derive_60m`,因此不会触发对方库写入。 - 默认先写本地镜像表;如用户确认,可把镜像表中主表不存在的新日期/新主键 append 到本地现有主行情表。 - 只写本地新增镜像表和同步日志表。 本地新增表: ```text a_share_daily_price_front_sync a_share_minute_price_front_sync a_share_kline_front_sync_run ``` 外部读取表: ```text cn_stock_kline_1d_front cn_stock_kline_1m_front ``` 当前暂不自动同步 `cn_stock_kline_60m_front`。如果后续需要 60 分钟线,应按同样模式新增 `a_share_60m_price_front_sync`,仍然保持只读外部库、本地增量镜像的边界。 ## 4. 同步策略 ### 4.1 源端策略 源端只读: - 使用外部 MySQL 查询 `cn_stock_kline_1d_front` 和 `cn_stock_kline_1m_front`。 - 不执行 `INSERT / UPDATE / DELETE / CREATE / DROP`。 - 不通过 HTTP 提交补数任务。 如果外部源缺数据,脚本只报告源端无数据或覆盖不足,不负责补外部库。 ### 4.2 本地策略 本地增量镜像: - 日线写入 `a_share_daily_price_front_sync`。 - 分钟线写入 `a_share_minute_price_front_sync`。 - 同步过程写入 `a_share_kline_front_sync_run`。 主表保护: - 不覆盖、不替换、不更新 `a_share_daily_price` 旧行。 - 不覆盖、不替换、不更新 `a_share_minute_price` 旧行。 - 允许把镜像表中主表不存在的新主键 append 到主表。 - 不写 `a_share_trading_calendar`。 - 不替换现有交易系统、局系统、实验系统正在读取的表。 以后如果要切换主流程读取新数据,必须另起需求和迁移步骤,不应由本同步脚本直接完成。 ### 4.3 增量起点 如果没有显式传入 `--start-date`: - 日线优先从 `a_share_daily_price_front_sync` 的最大 `trade_date + 1` 开始。 - 如果镜像表为空,则从当前主表 `a_share_daily_price` 的最大 `trade_date + 1` 开始。 - 分钟线同理,优先看 `a_share_minute_price_front_sync`,再看 `a_share_minute_price`。 如果显式传入 `--start-date / --end-date`,以 CLI 参数为准。 ## 5. 运行方式 ### 5.1 环境变量 建议使用环境变量传密码,不在命令行里明文写密码。 ```powershell $env:KLINE_SOURCE_MYSQL_PASSWORD='外部源密码' $env:TIANXIA_MYSQL_PASSWORD='本地MySQL密码' ``` 可选连接变量: ```powershell $env:KLINE_SOURCE_MYSQL_HOST='317w7246e5.vicp.fun' $env:KLINE_SOURCE_MYSQL_PORT='50176' $env:KLINE_SOURCE_MYSQL_USER='root' $env:KLINE_SOURCE_MYSQL_DB='trading_xuntou' $env:TIANXIA_MYSQL_HOST='127.0.0.1' $env:TIANXIA_MYSQL_PORT='3306' $env:TIANXIA_MYSQL_USER='root' $env:TIANXIA_MYSQL_DB='tianxia' ``` ### 5.2 初始化表结构 ```powershell python scripts/kline_front_incremental_sync.py ` --run-id kline_front_sync_init_schema ` --init-schema-only ``` ### 5.3 干跑检查 ```powershell python scripts/kline_front_incremental_sync.py ` --run-id kline_front_sync_dryrun ` --periods 1d 1m ` --dry-run ``` ### 5.4 同步日线和分钟线 ```powershell python scripts/kline_front_incremental_sync.py ` --run-id kline_front_sync_20260529 ` --periods 1d 1m ` --append-to-main ``` `--append-to-main` 的含义: - 先写镜像表。 - 每个成功批次完成后,把主表不存在的新主键追加进主表。 - 不更新、不覆盖、不删除主表旧行。 - 默认只允许全 A 同步追加主表;如果传了 `--symbols`,脚本会拒绝主表追加,除非显式加 `--allow-partial-main-append`。 ### 5.5 指定日期范围 ```powershell python scripts/kline_front_incremental_sync.py ` --run-id kline_front_sync_20260509_20260529 ` --periods 1d 1m ` --start-date 2026-05-09 ` --end-date 2026-05-29 ` --append-to-main ``` ### 5.6 小范围验证 ```powershell python scripts/kline_front_incremental_sync.py ` --run-id kline_front_sync_one_symbol_check ` --periods 1d 1m ` --start-date 2026-05-08 ` --end-date 2026-05-08 ` --symbols 600519.SH ``` 单股/局部检查默认不追加主表,避免把主表变成某天只有少数股票的稀疏数据。只有明确做局部修复时,才允许加: ```powershell --append-to-main --allow-partial-main-append ``` ## 6. 周期调度建议 Windows Task Scheduler 可以每天盘后运行一次。 建议节奏: - 日线:每天盘后同步一次。 - 分钟线:每天盘后同步当日,或按周补齐。 - 大范围分钟线:不要一次多年全 A,同步脚本虽然支持日期范围,但建议按小日期窗口跑。 推荐命令形态: ```powershell powershell.exe -ExecutionPolicy Bypass -Command "$env:KLINE_SOURCE_MYSQL_PASSWORD='***'; $env:TIANXIA_MYSQL_PASSWORD='***'; python scripts/kline_front_incremental_sync.py --periods 1d 1m" ``` ## 7. 已做自检 2026-05-29 已完成脚本级自检: - `python -m py_compile scripts/kline_front_incremental_sync.py` 通过。 - 单股日线实际同步:`600519.SH`,`2026-05-08`,源端 1 行,本地镜像写入 1 行。 - 单股分钟线实际同步:`600519.SH`,`2026-05-08`,源端 241 行,本地镜像写入 241 行。 自检只证明脚本链路可用,不代表已完成全 A 全量同步。 ## 7.1 当前覆盖差距快照 2026-05-29 查询到的日期范围: | 数据面 | 源端范围 | 本地主表范围 | 读法 | | --- | --- | --- | --- | | 日线 `1d` | `2023-01-03` 到 `2026-05-26` | `2023-01-03` 到 `2026-05-08` | 本地日线可从 `2026-05-09` 起增量补到源端最新。 | | 分钟线 `1m` | `2026-01-05` 到 `2026-05-26` | `2023-03-24` 到 `2026-04-02` | 本地分钟线可从 `2026-04-03` 起增量补到源端最新;源端当前不是完整 2023-2025 分钟历史源。 | 该快照只记录日期范围,不代表覆盖率全量通过。正式切换前仍需按覆盖率和数据质量单独校验。 ## 7.2 2026-05-29 实际同步结果 已完成: - 日线 `1d`:已从源端同步 `2026-05-09` 到 `2026-05-26` 的全 A 数据到本地镜像表 `a_share_daily_price_front_sync`。 - 同步行数:`65,953`。 - 同步 run_id:`20260529_kline_front_sync_daily_20260509_20260526_codex`。 - 按 append-only 口径,已将镜像表中主表不存在的 `65,953` 行追加到本地主表 `a_share_daily_price`。 - 追加后 `a_share_daily_price` 范围:`2023-01-03` 到 `2026-05-26`。 - 追加后待追加剩余数:`0`。 - 追加范围重复主键数:`0`。 未完成: - 分钟线 `1m`:源端 `2026-04-03` 到 `2026-05-26` 共有约 `44,967,408` 行需要补到本地。 - 通过“远端 MySQL 明细查询 -> 本地 TSV -> LOAD DATA”的跨网方式,按全市场时间窗口直接扫描会很慢;已改为按真实 A 股交易时段 + 每批 50 只股票同步,避免全市场大窗口扫描。 - 已中断早期慢查询尝试,并标记为 `INTERRUPTED_*`,避免半成品被误读。 - `20260529_ds_minute_append_20260403_20260526_opt4_symbolbatch_sessions` 为当前正式后台同步 run:按 `09:30-11:31 / 13:00-15:01` 交易时段、30 分钟窗口、50 只股票一批拉取;每个成功批次写入镜像表后 append-only 写入 `a_share_minute_price`。 - 本地当前主表 `a_share_minute_price` 已开始由该 run 推进;截至启动后首轮检查,最大日期已从 `2026-04-02` 推进到 `2026-04-03`,后续仍在后台继续补齐到源端 `2026-05-26`。 当前判断: - 日线可以按该脚本定期增量同步,并在确认后 append 到本地主表。 - 分钟线全 A 大范围同步不适合用当前前台直拉方式硬跑;需要换成压缩文件交付、服务端导出、内网直连、后台长任务,或按策略实际需要的股票/日期切片同步。 ## 7.2.1 2026-06-11 源端补数与本地日线同步结果 已完成: - 先按源端覆盖表确认 `2026-05-27` 到 `2026-06-10` 日线缺口:补数前源端覆盖表仅有 `2026-06-03` 的 `1` 行异常稀疏记录。 - 已通过 HTTP `POST /jobs` 提交全 A 日线补数任务,参数口径为 `periods=["1d"]`、`download_policy="missing"`、`batch_size=500`、`workers=1`。 - 源端补数 job_id:`FKL_20260611_022420_b86d6421`。 - 源端任务状态:`success`;`total_batches=24`,`completed_batches=24`,`failed_batches=0`,任务返回 `rows_written=60604`。 - 补数后源端主表 `cn_stock_kline_1d_front` 在 `2026-05-27` 到 `2026-06-10` 范围内共有 `60,603` 行,最大日期为 `2026-06-10`。 - 已执行本地日线同步 run_id:`ds_daily_after_upstream_backfill_20260611_023303`。 - 本地镜像表 `a_share_daily_price_front_sync` 写入 `60,603` 行。 - 按 append-only 口径,已将主表不存在的 `60,603` 行追加到本地主表 `a_share_daily_price`。 - 追加后 `a_share_daily_price` 范围:`2023-01-03` 到 `2026-06-10`;总行数 `4,394,085`。 核对结果: - 本地主表 `2026-05-27` 到 `2026-06-10` 范围内共有 `60,603` 行。 - 分日行数:`2026-05-27=5506`、`2026-05-28=5505`、`2026-05-29=5505`、`2026-06-01=5507`、`2026-06-02=5506`、`2026-06-03=5510`、`2026-06-04=5510`、`2026-06-05=5513`、`2026-06-08=5514`、`2026-06-09=5515`、`2026-06-10=5512`。 - 源端覆盖统计表在本次检查时已登记到 `2026-06-09`,源端主表已包含 `2026-06-10`;该差异按覆盖统计表滞后处理,后续可再次复核。 - 未触发分钟线补数;未覆盖、更新或删除本地主表旧行。 ## 7.3 2026-05-29 脚本口径更新 同步脚本已新增: ```text --append-to-main --allow-partial-main-append ``` 后续 `ds` 默认口径: - 日线:同步后自动 append 到 `a_share_daily_price`。 - 分钟线:按真实交易时段、30 分钟窗口、每批 50 只股票同步;每个成功批次同步后自动 append 到 `a_share_minute_price`。 - 不覆盖任何旧行。 - 不允许默认把单股/局部数据 append 到主表,除非显式声明是局部修复。 已回归: - 使用 `2026-05-26` 日线全 A 重跑 `--append-to-main`。 - 镜像表同步 `5,501` 行。 - 主表已存在对应主键,因此主表追加 `0` 行。 - 证明 append-only 逻辑不会覆盖旧数据。 ## 8. 不做的事 本同步脚本不做这些事: - 不修改外部 `trading_xuntou` 数据库。 - 不提交外部 HTTP 补数任务。 - 不覆盖本地 `a_share_daily_price`。 - 不覆盖本地 `a_share_minute_price`。 - 不自动切换交易系统或局系统的数据源。 - 不证明外部数据质量完全可靠。 ## 9. 后续如果要正式切换主数据源 必须另起一轮明确步骤: 1. 对 `a_share_daily_price_front_sync / a_share_minute_price_front_sync` 做覆盖率、重复主键、OHLC 空值、交易日历、分钟完整性校验。 2. 抽样比对旧主表和新镜像表。 3. 生成数据源切换说明。 4. 再决定是否把实验或交易系统读取源切到镜像表。 当前脚本只负责“安全增量镜像”,不负责“正式切换主源”。 --- # 附录 A:`HTTP与MySQL使用说明.md` 原文复制 # 前复权 K 线补数服务 HTTP 与 MySQL 使用说明 - 适用对象:内部同事通过 HTTP 触发补数任务,并通过 MySQL 读取前复权 K 线数据。 - HTTP 外部地址:`http://317w7246e5.vicp.fun:12181` - MySQL 外部地址:`317w7246e5.vicp.fun:50176` - MySQL 数据库:`trading_xuntou` - MySQL 用户:`root` - MySQL 密码:请向服务负责人获取,正式文档不写明文密码。 ## 1. 使用原则 1. HTTP 接口只用于提交任务、查询任务状态、查询覆盖率、触发修复和触发 60 分钟线派生。 2. 行情数据读取以 MySQL 为主,不建议通过 HTTP 查询大批量行情。 3. 当前 K 线主表均为前复权口径,优先使用 `*_front` 表。 4. 分钟线任务数据量很大,禁止随意提交全 A 多年分钟线任务。 5. 大范围分钟线补数建议按月份执行,参数使用 `download_policy=missing`。 6. 轮询任务状态建议间隔 `10-30` 秒,不要高频请求。 ## 2. HTTP 接口清单 | 方法 | 路径 | 用途 | | --- | --- | --- | | `GET` | `/health` | 检查服务是否在线 | | `POST` | `/jobs` | 提交补数或校验任务 | | `GET` | `/jobs` | 查询最近任务列表 | | `GET` | `/jobs/{job_id}` | 查询单个任务状态 | | `GET` | `/coverage` | 查询覆盖统计 | | `POST` | `/repair` | 提交缺口修复任务 | | `POST` | `/derive_60m` | 从本地 `1m` 派生 `60m` | ## 3. 常用 HTTP 示例 检查服务: ```bash curl "http://317w7246e5.vicp.fun:12181/health" ``` 提交单股日线补数: ```bash curl -X POST "http://317w7246e5.vicp.fun:12181/jobs" \ -H "Content-Type: application/json" \ -d '{ "mode": "history_backfill", "periods": ["1d"], "start_date": "2026-05-01", "end_date": "2026-05-26", "symbols": ["600519.SH"], "batch_size": 1, "download_policy": "missing", "reason": "manual_single_symbol_daily" }' ``` 提交小范围分钟线补数: ```bash curl -X POST "http://317w7246e5.vicp.fun:12181/jobs" \ -H "Content-Type: application/json" \ -d '{ "mode": "history_backfill", "periods": ["1m"], "start_date": "2026-05-20", "end_date": "2026-05-20", "symbols": ["600519.SH", "000001.SZ"], "batch_size": 50, "workers": 1, "download_policy": "missing", "reason": "manual_small_minute_patch" }' ``` 提交全 A 单月分钟线补数: ```bash curl -X POST "http://317w7246e5.vicp.fun:12181/jobs" \ -H "Content-Type: application/json" \ -d '{ "mode": "history_backfill", "periods": ["1m"], "start_date": "2026-05-01", "end_date": "2026-05-31", "batch_size": 50, "workers": 2, "download_policy": "missing", "reason": "manual_full_a_month_1m" }' ``` 查询任务状态: ```bash curl "http://317w7246e5.vicp.fun:12181/jobs/FKL_xxx" ``` 查询最近任务: ```bash curl "http://317w7246e5.vicp.fun:12181/jobs?limit=20" ``` 查询覆盖统计: ```bash curl "http://317w7246e5.vicp.fun:12181/coverage?period=1m&start_date=2026-05-01&end_date=2026-05-31&limit=200" ``` 触发本地 `1m -> 60m` 派生: ```bash curl -X POST "http://317w7246e5.vicp.fun:12181/derive_60m" \ -H "Content-Type: application/json" \ -d '{ "start_date": "2026-05-20", "end_date": "2026-05-20", "symbols": ["600519.SH"], "batch_size": 50, "reason": "manual_derive_60m" }' ``` ## 4. `POST /jobs` 参数说明 | 参数 | 类型 | 说明 | 推荐值 | | --- | --- | --- | --- | | `mode` | string | 任务模式 | `history_backfill`、`incremental`、`validate` | | `periods` | array | 粒度 | `["1d"]`、`["1m"]`、`["60m"]` | | `start_date` | string | 开始日期 | `YYYY-MM-DD` | | `end_date` | string | 结束日期 | `YYYY-MM-DD` | | `symbols` | array/string | 股票列表;不传表示全 A | 小任务建议显式传 | | `batch_size` | int | 每批股票数 | `1m` 建议 `50` | | `workers` | int | 批次并发数 | `1` 或 `2` | | `download_policy` | string | 下载策略 | 建议 `missing` | | `reason` | string | 任务说明 | 建议填写 | `download_policy` 说明: | 值 | 含义 | | --- | --- | | `none` | 不主动下载,直接读取迅投本地已有数据 | | `force` | 强制调用迅投下载 | | `missing` | 先查本地 MySQL,已有足够数据就跳过,不足再下载 | ## 5. 任务状态说明 | 字段 | 说明 | | --- | --- | | `job_id` | 任务 ID,后续查询状态使用 | | `status` | `pending`、`running`、`success`、`partial_success`、`failed`、`cancelled` | | `total_batches` | 总批次数 | | `completed_batches` | 已完成批次数 | | `failed_batches` | 失败批次数 | | `rows_written` | 写入影响行数 | | `last_error` | 最近错误 | | `batch_errors` | 批次错误列表 | ## 6. MySQL 主表说明 ### 6.1 日线表 表名:`cn_stock_kline_1d_front` 用途:前复权日 K 线。 核心字段: | 字段 | 说明 | | --- | --- | | `symbol` | 股票代码,例如 `600519.SH` | | `trade_date` | 交易日 | | `open` | 前复权开盘价 | | `high` | 前复权最高价 | | `low` | 前复权最低价 | | `close` | 前复权收盘价 | | `pre_close` | 前复权昨收 | | `volume` | 成交量 | | `amount` | 成交额 | | `turnover` | 迅投返回的换手/成交相关字段 | | `source_batch_id` | 写入批次 | ### 6.2 分钟线表 表名:`cn_stock_kline_1m_front` 用途:前复权 1 分钟 K 线。 核心字段: | 字段 | 说明 | | --- | --- | | `symbol` | 股票代码 | | `bar_time` | 分钟时间 | | `open` | 前复权开盘价 | | `high` | 前复权最高价 | | `low` | 前复权最低价 | | `close` | 前复权收盘价 | | `volume` | 成交量 | | `amount` | 成交额 | | `turnover` | 迅投返回字段 | | `source_batch_id` | 写入批次 | ### 6.3 60 分钟线表 表名:`cn_stock_kline_60m_front` 用途:前复权 60 分钟 K 线;可以由迅投直接提供,也可以从本地 `1m` 派生。 字段与 `cn_stock_kline_1m_front` 基本一致,时间字段同为 `bar_time`。 ### 6.4 覆盖统计表 表名:`cn_stock_kline_front_coverage_daily` 用途:按交易日和粒度统计覆盖情况,判断是否补齐。 核心字段: | 字段 | 说明 | | --- | --- | | `period` | `1d`、`1m`、`60m` | | `trade_date` | 交易日 | | `table_name` | 对应物理表 | | `row_count` | 当天该粒度总行数 | | `symbol_count` | 当天覆盖股票数 | | `expected_symbol_count` | 期望股票数 | | `coverage_ratio` | 覆盖率 | | `min_bar_time` | 分钟/60 分钟最早时间 | | `max_bar_time` | 分钟/60 分钟最晚时间 | ## 7. 常用 SQL 查单股日线: ```sql SELECT symbol, trade_date, open, high, low, close, pre_close, volume, amount FROM cn_stock_kline_1d_front WHERE symbol = '600519.SH' AND trade_date BETWEEN '2026-05-01' AND '2026-05-26' ORDER BY trade_date; ``` 查单股分钟线: ```sql SELECT symbol, bar_time, open, high, low, close, volume, amount FROM cn_stock_kline_1m_front WHERE symbol = '600519.SH' AND bar_time >= '2026-05-20 00:00:00' AND bar_time < '2026-05-21 00:00:00' ORDER BY bar_time; ``` 查单股 60 分钟线: ```sql SELECT symbol, bar_time, open, high, low, close, volume, amount, source FROM cn_stock_kline_60m_front WHERE symbol = '600519.SH' AND bar_time >= '2026-05-20 00:00:00' AND bar_time < '2026-05-21 00:00:00' ORDER BY bar_time; ``` 查某日全 A 日线覆盖: ```sql SELECT period, trade_date, row_count, symbol_count, expected_symbol_count, coverage_ratio FROM cn_stock_kline_front_coverage_daily WHERE period = '1d' AND trade_date = '2026-05-20'; ``` 查某月分钟线覆盖: ```sql SELECT period, trade_date, row_count, symbol_count, expected_symbol_count, coverage_ratio, min_bar_time, max_bar_time FROM cn_stock_kline_front_coverage_daily WHERE period = '1m' AND trade_date BETWEEN '2026-05-01' AND '2026-05-31' ORDER BY trade_date; ``` 查覆盖不足日期: ```sql SELECT period, trade_date, row_count, symbol_count, expected_symbol_count, coverage_ratio FROM cn_stock_kline_front_coverage_daily WHERE period = '1m' AND trade_date BETWEEN '2026-05-01' AND '2026-05-31' AND (expected_symbol_count IS NULL OR symbol_count < expected_symbol_count) ORDER BY trade_date; ``` ## 8. 推荐工作流 1. 先用 HTTP `/health` 确认服务在线。 2. 用 MySQL 覆盖统计表确认目标日期和粒度是否已有数据。 3. 如果缺数据,再通过 HTTP `/jobs` 或 `/repair` 触发补数。 4. 用 `GET /jobs/{job_id}` 每 `10-30` 秒查看任务状态。 5. 任务完成后,再从 MySQL 主表读取数据。 6. 如果任务产生 `still_missing_after_download`,由服务负责人根据 `gap_records.csv` 统一补缺。 ## 9. 注意事项 1. `symbols` 不传表示全 A,会产生大任务。 2. `1m` 数据量最大,建议按单日、单月或明确股票集合补。 3. `workers` 建议不超过 `2`。 4. `1m` 的 `batch_size` 建议使用 `50`。 5. 服务依赖本机迅投客户端、MySQL 和补数服务进程同时在线。 6. MySQL 查询大表时必须带 `symbol` 和日期范围,避免全表扫描。 7. 外部同事读取数据时优先读 `*_front` 表,不要混用旧的不复权表。 --- # 附录 B:`HTTP与MySQL使用说明.local.md` 原文复制 # 前复权 K 线补数服务外部映射连接信息(本地私有) > 本文件包含明文连接信息,已通过 `.gitignore` 排除,不应提交到 Git。 ## HTTP - Base URL:`http://317w7246e5.vicp.fun:12181` ## MySQL - Host:`317w7246e5.vicp.fun` - Port:`50176` - User:`root` - Password:`***` - Database:`trading_xuntou` ## 建议发给同事的最小连接信息 ```text HTTP: http://317w7246e5.vicp.fun:12181 MySQL: 317w7246e5.vicp.fun:50176 Database: trading_xuntou User: root Password: 123456 ``` 正式使用说明见: ```text data/HTTP与MySQL使用说明.md ```