边界内任有缺失则整组重暂存后统一切换,避免旧新批次混发; refresh_stocks 失败时主档保持旧值,并补齐回归测试。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
7.3 KiB
xiaobai-datahub
独立行情数据中枢(HEL-382 / P0)。与 xiaobai-review 同仓库、不同容器、不共享数据库文件。
本阶段不部署现网;只提供可本地运行、可自测的底座和盘后正式数据链路。
做什么
- SQLite WAL
datahub.db,容器名xiaobai-datahub,端口8766 - Tushare 盘后正式数据:交易日历、股票主档、daily、daily_basic、adj_factor、index_daily、moneyflow、stk_auction
- 暂存 → 校验 → 整批原子发布 → 可回滚
/v1稳定接口(X-Datahub-Token)/admin/最小管理后台(总览 / 数据源 / 调度 / 发布 / 数据集 / 审计)- 东财/腾讯/同花顺/选股宝/AKShare/iFinD 适配器位已预留,本阶段不拉实时源
单位口径(相对现站)
现站 xiaobai-review 按 Tushare 原始单位入库、展示时再换算。中枢在归一化层一次换算:
| 字段 | Tushare / 现站 | 中枢 canonical |
|---|---|---|
daily.amount / index_daily.amount |
千元 | 元(×1000) |
daily.vol / index_daily.vol |
手 | 股(×100) |
moneyflow.*_amount |
万元 | 元(×1e4) |
daily_basic.total_mv / circ_mv |
万元 | 元(×1e4) |
stk_auction.amount |
元 | 元 |
差异为口径升级,golden 测试按上表对照,不为 0 的字段都有说明。
本地启动(不走 Docker)
cd xiaobai-datahub
python -m venv .venv && .venv/bin/pip install -r requirements.txt
cp .env.example .env
# 填入 DATAHUB_ENCRYPTION_KEY / DATAHUB_TOKEN / DATAHUB_ADMIN_PASSWORD / TUSHARE_TOKEN
# 生成 Fernet 密钥:
# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
.venv/bin/python server.py --host 127.0.0.1 --port 8766
- 管理后台:http://127.0.0.1:8766/admin/
- 存活检查:http://127.0.0.1:8766/livez (无需 token)
/v1/*必须带请求头X-Datahub-Token
Docker(独立 compose,不改现网 review 服务)
cd xiaobai-datahub
cp .env.example .env # 填密钥
mkdir -p data
docker compose build
docker compose up -d
仓库根目录另有 compose.datahub.yaml,供总工以后与现有 compose.yaml 叠加部署,本卡不执行现网 up。
自测
cd xiaobai-datahub
python -m unittest discover -s tests -v
不调用真实 Tushare;用内存/临时库和假适配器。
历史回补
交易日历默认从 20160101 拉到今天后 30 天;盘前 precheck 与手动回补都走同一 UPSERT,可重复执行。
网站实际使用的指数(上证、深成、创业板、沪深300)按交易日增量发布,默认覆盖 260 个交易日(大于现有 90 天窗口,并覆盖智能选股基准回看)。已发布日期默认跳过。
cd xiaobai-datahub
python -m datahub history-backfill
# 可选:--calendar-start 20160101 --index-days 260 --force
管理后台也可手动跑 history_backfill 任务,或 POST /admin/api/backfill 且 dataset=history、确认词 history:full。
区间接口在 meta.coverage / meta.incomplete 标明覆盖是否完整;网站只读接入把不完整区间视为不可用并回旧链路。个股日 K 的 90 天区间查询依赖已核实,本阶段不回补全市场历史。
估值字段级质量门
hub-quality.config.json 的 field_gates 按数据集配置关键字段:非空率下限(支持按字段覆盖,如 dv_ttm 合法高空值)、非有限值比例上限、以及相对上一已发布批次的非空率塌陷保护。字段大面积为空的批次会被拒绝发布、保留上一份正常正式数据,失败原因逐字段写入 batches.error / quality_json。被拒后数据集仍视为缺失,盘后自动重试(HEL-435 机制)会继续尝试直到成功或截止。配置对任意数据集生效,不写死单日或单字段。
整批原子发布(release group)
盘后发布/重发(eod_a、eod_retry、eod-refresh、跨数据集重发)不再逐数据集各自切换,而是走整批原子可见机制:
- 一致性边界:日 K、估值、资金流、竞价同属 A 组整批;指数日 K 为 B 组;当日股票主档快照随 A 组一同切换(主档
stock_master的 UPSERT 与快照发布同一事务,不会出现主档先行/滞后)。 - 流程:组内全部成员先在暂存表完成拉取、字段质量门、覆盖检查和跨数据集交叉校验(
cross_gates配置 ts_code 覆盖重叠率下限),全部达标后才在一个 SQLite 事务里复制正式表并翻转全部publications指针。 - 任一成员失败(拉取失败、质量门拒绝、交叉校验不过、切换事务中断)→ 整批不切换,对外继续提供上一份完整正式版本,失败原因写入
batches.error与audit_log(action=release-group),等待晚间自动重试。 - 读取侧任何时刻只会看到"旧完整版本"或"新完整版本":发布指针在单事务内统一翻转,容器重启/事务中断自动回滚,不暴露字段残缺或跨数据集混合版本。
- 幂等:仅当一致性边界内全部成员都已发布时才整组跳过;边界内任有缺失则整组重暂存后统一切换,避免旧批次与新批次混在同一次重发中。重复执行、并发重试不会在完整边界已就绪时生成重复批次(调度器另有 EOD 互斥锁)。
股票主档每日刷新与发布
交易日 20:00 与 23:10(stocks_refresh_times 可配)自动刷新股票主档并发布版本化快照(eod_stocks + publications.dataset='stocks'),覆盖当日新上市、证券简称变化和上市首日 N/C 前缀摘除;无变化则跳过,重复执行幂等。/v1/stocks 从最新已发布快照提供数据并带 batch_id / published_at;/v1/datasets/status 同步展示 stocks 状态。
cd xiaobai-datahub
python -m datahub stocks-refresh # 手动触发;--force 无变化也重发
资金流历史回补
网站会沿真实调用链查最近若干交易日的 moneyflow(个股详情任意日期点查 + 智能选股最近 5 个交易日),默认回补最近 60 个交易日(moneyflow_history_trading_days 可配,已发布日期自动跳过)。点查未覆盖的历史日期返回 DATASET_NOT_PUBLISHED 并附 available_from / available_to(低于下界时 reason=history_not_backfilled),网站据此明确回退旧链路,不会静默拿到半截数据。
cd xiaobai-datahub
python -m datahub moneyflow-backfill # --trading-days 60 --end-date --force 可选
盘后补跑与强制重发
cd xiaobai-datahub
python -m datahub eod-refresh --trade-date 20260904 # 只补缺失数据集
python -m datahub eod-refresh --trade-date 20260904 --force --dataset valuation
# 强制重取重发:仍走全部质量门,生成新批次,上一批次保留可回滚
备份
每日 00:40 任务把 datahub.db 备份到 data/backups/(保留 14 份)。也可手动:
python -c "from pathlib import Path; from datahub.db import HubDB; HubDB(Path('data/datahub.db')).backup_to(Path('data/backups/manual.db'))"
安全
- 密钥只以
configured / 末4位 / 更新时间出现在后台,不进日志、不进/v1 - HTTP 解析失败只记录“请求不是合法 JSON”,不把请求正文、密码或 Token 写入容器日志
- 回滚、补数需重新输入密码 + 确认词
- 容器非 root(uid 10002)、read_only、cap_drop ALL