文档性质:终局说明(思路 + 踩坑 + 反思 + 定稿架构),不是施工流水账。 版本:2026-08-08 · 仓库:jason9356/trader-data 数据根:D:\trader-store(密钥仅 secrets.env,永不进 git) 早期工程约定已发表:博客 · Tushare 本地全量入库(与仓库 blog_tushare_ingest_week_postmortem.md 同源) 策略调用面见 research_sdk.md;存储铁律见 storage_single_source.md。
0. 一句话结论
这是一套 给策略调用的本地市场数据基础设施,不是看盘软件。
- 下载:按表登记拉取形状(截面 / 报告期 / 按码),可续跑、可熔断。
- 存储:业务事实只在 DuckDB;Parquet 只是备份。
- 调用:横截面用
panel,单票研究用dossier/bars(ResearchClient)。 - 界面:看板与检修;不承担生产力级验数以外的职责。
截至定稿:约 88 张业务表、约 3.12 亿行,A 股日线主链水位约 20260807;crypto.ohlcv 空表为待导入,不是损坏。
1. 从哪来:产品意图如何收敛
1.1 起初想要什么
独立模块「数据底座」,服务日后回测 / 策略 / 实盘:
- 多源:Tushare(高权限)、Freqtrade 加密、已购期货文件,并预留扩展口;
- 数据不再是黑箱——能检索、能抽查;
- 与兄弟项目解耦,契约稳定,可被 HTTP 或 Python 包消费。
第一期先搭了 Catalog + Parquet + sidecar + 验数 UI(Demo 闭环),再接入加密 / 期货,最后才是 Tushare 全量——这条生长顺序是对的:先有壳与契约,再灌数据。
1.2 中途偏过的方向
| 阶段 | 做法 | 后来为什么改 |
|---|---|---|
| Parquet 为主、DuckDB 加速扫湖 | 当时以为「湖 = 真相、库 = 缓存」 | 策略调用要稳定 SQL/契约;单文件库反而更适合业务;湖适合备份搬运 |
| Hermes「核心只在湖、零重叠」 | 避免库湖双份 | 用户纠偏:业务只认库;重叠的备份可以接受;零重叠让读路径分裂 |
| UI 做成工程师三栏控制台 / 研究首页 | 验数需要界面 | 再次纠偏:库的价值在调用,不在呈现;UI 降级为看板 |
1.3 定稿定位(必须记住)
策略 / 回测 / 研究脚本 → ResearchClient / 直接 SQL(只读)
运维 / 日更 / 迁库 → ingest + MarketDB upsert
人眼看板 / 检修 → Web UI + /db + /series(次要)
横纵两个调用维度(比 UI 重要):
| 维度 | 含义 | 入口 |
|---|---|---|
| 横 | 宇宙 × 交易日 × 字段面板 | calendar / universe / panel / freshness |
| 纵 | 单标的全量切片 | resolve / bars(adj) / dossier |
2. 下载构建的思路(定稿)
2.1 总流程
账户探测 → 策略矩阵登记 → 注册表与批次对账
→ 冒烟(1~2 个真实截面)→ 全量/续跑(Guard + 心跳)
→ 校验布局与行数 →(历史)迁入 DuckDB / 双写 upsert
→ 日更只走水位(库内 max(date) 优先于状态 JSON)
细节级约定(模式定义、熔断开路/半开、湖分区布局、检查清单)以博客原文为准,不必在此重复抄写。真源代码:
| 主题 | 路径 |
|---|---|
| 策略矩阵 | src/trader_data/ingest/pull_modes.py |
| A 股 loader | src/trader_data/ingest/tushare_ashare.py |
| Pool / 批次 | src/trader_data/ingest/tushare_pool.py |
| Guard | src/trader_data/ingest/guard.py |
| DuckDB | src/trader_data/db/market.py |
| 研究调用 | src/trader_data/research/ |
2.2 铁律(下载侧)
- 先形状,后速度——能
by_trade_date/by_period绝不全市场by_code。 - 对本账户探测,不死抄文档示例(有无
*_vip、是否支持只传日期)。 - 未登记策略禁止开跑。
i/N≠ 入库;看分区、行数、first non-empty。- 传输层可恢复(开路/半开),逻辑层硬停;勿把 ProxyError 三次失败当成永久终态。
- 改策略先 quarantine 旧分区并重置绑定水位。
- 注册表 − 批次 − 排除 = ∅。
- 排除默认不做:全市场 1 分钟(如
stk_mins);小时级限频接口独立慢批次。
2.3 写入定稿路径
- Ashare:
write_partition→ 先MarketDB.upsert_dataframe,再写 parquet 备份。 - Pool:直接 upsert DuckDB(既有行为)。
- 历史一次迁入:
scripts/migrate_ashare_to_db.py等(湖 → 库)。 - 水位:
by_trade_date优先信库内max(trade_date),状态 JSON 只是辅助;过期 JSON 会导致审计误报(用refresh_sync_state_from_db.py对齐)。
2.4 批次逻辑顺序(经验)
- Meta / 日线主链(daily、adj_factor、daily_basic、stk_limit、moneyflow…)
- 财务
by_period(VIP) - 市场广度与微观截面
- 跨品种日频(指数、基金、期货日线…)
- Snapshot 维表
- 结构类(周月、持仓等)
- 强制
by_code重表(如筹码) - 小时级限频(最后)
3. 踩坑记录(比博客更「事后」的那一层)
下列条目来自全量建设 + 库湖纠偏 + 调用层落地;与博客 §5 互补,不替代。
3.1 形状与吞吐
| 坑 | 表现 | 教训 |
|---|---|---|
| 财务按码循环 | 很忙、磁盘几乎不涨 | 默认 by_period + *_vip |
| 指数/基金误用 by_code | 墙钟爆炸 | 能按日键截面就截面 |
| 改布局后新旧混存 | 行数对不上、重复 | quarantine;迁移只认一种布局 |
3.2 观测与运维
| 坑 | 表现 | 教训 |
|---|---|---|
早于 history_start 空烧 |
i/N 走完 0 文件 / 误熔断 |
写准起点;看首次非空 |
| 代理闪断 | 假死、误判 Tushare 挂了 | 全量倾向直连;开路冷却而非三次永停 |
| 心跳/日志文件互锁 | 写入失败、进程秒退 | 每 job 独立心跳;Redirect 与业务日志分离 |
| 看门狗强杀 + 睡眠误报 | 「假死」其实是机器休眠 | 观察优先于 taskkill;电源策略要管 |
| 只杀 uv 启动器 PID | 子进程残留 | 用 jobctl / 认清进程树 |
| 状态 JSON 过期 | audit 一直报不一致 | 以库为准刷新状态;别吓自己 |
3.3 本地库与质量
| 坑 | 表现 | 教训 |
|---|---|---|
| 未引用标识符 | SQL 在 limit 等词处炸 |
schema/table/column 一律双引号 |
| pandas 类型污染 | string → DOUBLE 失败 |
object→VARCHAR;冲突则按样本重建 |
| 无主键叠行 | 行数虚高(cyq_chips、ths_member、利率等) | 全业务表补 PK;fix_pool_pks 类脚本去重 |
| 财务 PK 过粗 | 湖库行数对不齐 | 纳入 update_flag / ann_date 等细主键后重迁 |
index_basic 被单市场覆盖 |
只剩数百条 | 多市场拉取(SSE/SZSE/CSI/SW/CNI/BSE/OTH) |
| 宏观/股东「暴跌」 | 审计吓人 | 多为叠行修复或下载 bug 修正后的正确收敛,先对账再恐慌 |
| 复权 | 错价策略结果全废 | 原始价与 adj_factor 分离;qfq/hfq 在调用层算 |
stk_factor 被当成日线 |
语义混乱 | 日线主链永远是 ashare.daily |
| 涨跌停标记 | 新股 up_limit=99999… | 策略侧过滤;文档坑位保留 |
| 指数早期 NaN OHLC | UI/API500(JSON 不接受 NaN) | 读路径 isfinite 过滤;勿把序列化失败当库坏 |
3.4 架构决策坑
| 坑 | 表现 | 教训 |
|---|---|---|
| 库湖双读 | 有时读湖有时读库,空结果静默回落 parquet | DB 优先且空结果也是结果(is not None) |
| 「零重叠」教条 | 核心表只在湖,业务路径拧巴 | 用户指令:库为 SSOT,湖备份允许重叠 |
| 把 UI 当产品核心 | 工程师控制台很好看,策略零收益 | 精力给 ResearchClient 与水位/PIT |
| 并发多 writer | DuckDB 文件锁 | 入库与迁库串行;研究侧规划只读副本 |
3.5 范围与预期管理
| 坑 | 表现 | 教训 |
|---|---|---|
crypto.ohlcv 空 |
审计/UI 当故障 | 合法待导入;审计白名单 |
hk_daily 极慢/降级 |
主链「不齐」焦虑 | 独立慢批次;不阻塞主链合格判定 |
stk_managers 无历史深度 |
当成长面板用会错 | 快照语义写进坑表 |
trade_cal 含未来日 |
日历直接用会穿 | 研究层截断 <= today 且 is_open=1 |
4. 反思
4.1 做对了什么
- 独立仓库 + 稳定契约——数据模块可以单独生长,不被回测 UI 绑架。
- 形状门禁——没有策略矩阵时,高权限等于加速烧配额。
- 熔断有恢复语义——代理世界里「急停」和「熔断」不是一回事。
- 及时否定错误架构——零重叠、湖主库辅,在真实调用压力下站不住,纠偏成本可控。
- 质量脚本化——audit / report / refresh_sync / fix_pk,比「感觉行数对」可靠。
4.2 付过的学费
- 把呈现当目标会吞噬上下文:控制台、主题、图表,对策略调用杠杆接近零。
- 状态文件不是真相——JSON 水位过期比数据坏更常见。
- 「行数变少」常常是变好——去重、修下载 bug 后的收敛。
- 调用层要单独建设——库装满了不等于
JOIN adj_factor自动正确;面板、复权、宇宙、日历必须产品化。 - 单文件 DuckDB 是双刃剑——简单、够快;并发与备份要提前设计,别等回测农场撑爆再想。
4.3 仍然诚实的缺口
- 财务 / 披露的 point-in-time(按
ann_date可得日)尚未一等公民化。 - 指数成分等 历史宇宙 未做成稳定 API。
- 研究与入库并发的 只读副本 / 快照 未落地。
- 港股日线、加密全量仍属「计划内未完成 / 慢做」。
- Git 远程是否推齐取决于网络环境;本地 bundle 是兜底,不是替代纪律。
5. 最终设计(2026-08-08 定稿)
5.1 分层总图
┌─────────────────────────────┐
│ Strategy / Research / BT │
│ ResearchClient · SQL 只读 │
└─────────────▲───────────────┘
│
┌──────────────┐ ┌────────────┴────────────┐ ┌────────────┐
│ Tushare Pro │────►│ Ingest Pipeline │────►│ DuckDB │
│ Freqtrade │ │ pull_modes · guard · pool│ │ SSOT │
│ Futures zip │ │ ashare write: DB→parquet │ │ ~88 tables │
└──────────────┘ └────────────┬────────────┘ └─────▲──────┘
│ 备份双写 │
▼ │
parquet/ashare/* 检修 UI /series
(不参与业务读) /db · 看板
5.2 存储
| 层 | 路径 | 角色 |
|---|---|---|
| SSOT | db/market.duckdb |
全部业务事实;策略只读这里 |
| 备份湖 | parquet/ashare/* |
与写入同批;迁移/拷贝/灾难恢复 |
| 状态 | state/* |
心跳、暂停旗、同步 JSON(可刷新,非 SSOT) |
| 密钥 | secrets.env |
仅本机 |
禁止:业务路径为了「方便」再读 parquet 当行情/财务事实。
5.3 库内逻辑分区(schema)
| Schema | 内容 |
|---|---|
ashare |
日线主链、复权、财务、广度、因子、两融… |
index_cn |
指数日线/周月/权重/基础 |
fund / fut_ts / fut_file / opt / bond / macro / hk / us / crypto |
多资产 |
meta |
同步与入库元数据(一般不进策略面板) |
日线主链:ashare.daily + ashare.adj_factor(分离存储)。 不要用 stk_factor* 替代 daily。
5.4 调用面
| 场景 | 用法 |
|---|---|
| 策略截面 | ResearchClient.panel(["core"], start=..., end=...) |
| 单票研究 | ResearchClient.dossier("茅台") / bars(..., adj="qfq") |
| 就绪检查 | ResearchClient.freshness() |
| HTTP 薄封装 | /research/*(默认给工具用;重活用 Python) |
| 检修扫表 | /db/tables · 专家模式 UI |
| 看图 | /series · 行情页 |
字段包:ohlcv / valuation / limits / core 等,见 research/fields.py。
5.5 质量与运维工具
| 脚本 | 用途 |
|---|---|
scripts/audit_datalake.py |
健康审计(crypto 空表合法) |
scripts/report_tables.py |
逐表行数 |
scripts/refresh_sync_state_from_db.py |
状态 JSON ← 库 |
scripts/verify_research_sdk.py |
研究调用面冒烟 |
scripts/verify_db_query_layer.py |
表扫描 / series 冒烟 |
scripts/daily_watermark.py |
水位日报 |
5.6 合格标准(数据底座「能开工」)
- 核心日线族 +
adj_factor+daily_basic+index_daily水位对齐到最近交易日量级。 - 业务表主键齐全;无大面积叠行。
- 复权可复现(qfq/hfq 锚点明确)。
- 审计 0 真实问题(允许:crypto 空、状态已刷新、已知慢表降级)。
- 策略侧能用
ResearchClient不经 UI 取到面板与单票包。
6. 文档地图(以后改哪里)
| 文档 | 读什么 |
|---|---|
| 本文 | 终局:思路、踩坑、反思、定稿架构 |
| 博客 / postmortem | 拉取策略、熔断、检查清单(工程公约) |
storage_single_source.md |
库主湖备铁律 |
research_sdk.md |
横纵调用 API |
database_intro.md |
表账与已知数据坑位 |
architecture.md |
仓库模块与消费边界 |
tushare_ingest_lessons_and_update_rules.md |
日更操作记忆 |
7. 给未来自己的备忘
- 新表:探测 → 登记 → 入批次 → 冒烟 → 再全量。
- 行数骤降:先查是否去重/修 bug,再查是否真丢数。
- audit 报警:先看状态 JSON 是否过期、空表是否在白名单。
- 策略要字段:加
FIELD_SPECS/ pack,而不是让策略手写五表 JOIN。 - 下一步杠杆最高的三件事:PIT 财务、成分宇宙历史、只读库快照——不是再做一个更炫的前端。
本文综合:公开博客约定、仓库内复盘材料,以及 2026-07-30~08-08 建设过程中的架构纠偏与验收结论。