FinceptTerminal DataHub Phase 10 强制执行与清理:移除废弃回调 API、锁定唯一数据路径的工程实践
【免费下载链接】FinceptTerminalFinceptTerminal is a modern finance application offering advanced market analytics, investment research, and economic data tools, designed for interactive exploration and>项目地址: https://gitcode.com/GitHub_Trending/fi/FinceptTerminal
导读
本文聚焦 FinceptTerminal 数据层迁移的收官阶段——Phase 10(Enforcement & Cleanup)。经过 Phase 0–9 九个阶段的增量迁移,终端内所有数据消费者均已切换到fincept::datahub::DataHub发布/订阅体系,旧的回调式取数 API 已标记[[deprecated]]数月。Phase 10 的任务是"关门":删除废弃包装层、用 lint/CI 规则阻止其复活、让 DataHub 成为终端唯一受支持的数据获取方式。读完本文,你将掌握该项目如何规划一次不可逆的代码清理:包括强制规则设计(D1–D5)、CI 纪律检查(datahub-discipline)、废弃 API 删除清单、Topic 注册表规范、特性开关移除、以及完整的回滚预案。
一、背景:九个阶段增量迁移之后的"关门时刻"
FinceptTerminal 的数据获取经历了从"各 Service 各自为政、回调满天飞"到"统一走 DataHub 总线"的渐进式重构。Phase 10 的前置条件非常明确(见 phase-10-enforcement-cleanup.md):
- 依赖:Phase 0–9 全部上线并在生产环境稳定运行 ≥ 2 周;
- 规模:名义工期 2 天,规模"小但不可逆"——这是唯一一个删除代码的阶段,所有先前阶段都可回滚,而这一阶段"关上大门"。
九个先行阶段覆盖的数据域(对应 DATAHUB_TOPICS.md 中的分节)包括:
| 阶段 | 数据域 | 代表性 Topic 家族 |
|---|---|---|
| Phase 2/3 | 行情数据 | market:quote:*、market:history:*、market:sparkline:* |
| Phase 4 | WebSocket 生产者 | ws:<exchange>:*、prediction:polymarket:*、prediction:kalshi:* |
| Phase 5 | 新闻 | news:general、news:symbol:*、news:category:*、news:cluster:* |
| Phase 6 | 经济数据 | econ:*、dbnomics:*、govdata:* |
| Phase 7 | 券商账户流 | broker:*:*:positions/orders/balance/holdings/quote/ticks |
| Phase 8 | 地缘/航运/公司 | geopolitics:*、maritime:*、ma:* |
| Phase 9 | AI/Agent/LLM | agent:*、llm:session:* |
Phase 10 的原则是No new features. Just locking the door behind the migration.——不引入任何新功能,纯粹为迁移收尾。
二、核心目标:防回退(Prevent Regression)
Phase 10 的 Goal 章节明确其核心诉求是防止回归。此时的状态是:
- 每个数据消费者(screens/widgets)都已完成转换;
- 每个生产者(Service)都已注册到 DataHub;
- 旧的回调式 API(callback-based wrappers)已被
[[deprecated]]标记数月。
Phase 10 要做三件事来拆除"安全网":
- 移除废弃包装层——旧回调 API 从
.h声明和.cpp实现中彻底删除; - lint 规则阻止重新引入——CI 中新增纪律检查,任何新的违规代码直接让构建失败;
- DataHub 成为唯一受支持的数据路径——全终端数据获取只能通过 DataHub。
从源码看,当前 DataHub 的实现正是这套"关门"设计的目标形态:DataHub.h 中的单例DataHub提供了完整的订阅/发布/策略/自省 API,而 Producer.h 定义了生产者接入接口,二者共同构成迁移的终点。
三、交付物 1:CLAUDE.md 强制规则(D1–D5)
Phase 10 的第一个交付物是给CLAUDE.md增加新章节D. DataHub Rules (MANDATORY),置于现有P15与Security章节之间。五条规则构成完整的"数据流向纪律":
| 规则 | 内容 | 约束对象 |
|---|---|---|
| D1 | Screens 和 widgets 不得直接调用PythonRunner,所有 Python 支撑的数据流都必须通过注册到DataHub的 Producer | 消费端 |
| D2 | 获取外部数据(HTTP、Python、WS)的 Service 必须实现fincept::datahub::Producer,并通过DataHub::publish(...)发布结果 | 生产端 |
| D3 | Widget 永远不能自己持有数据刷新QTimer;节奏由 hub 调度器掌握。Widget 在showEvent订阅、在hideEvent退订(延伸现有 P3 规则) | 生命周期 |
| D4 | 消费者代码不得调用 Service 的fetch_*方法;所有读取都通过DataHub::subscribe(...)或DataHub::peek(...) | 读取路径 |
| D5 | 新 Topic 字符串必须在首次使用前注册到docs/DATAHUB_TOPICS.md,并标注 TTL、owner、policy | 注册管理 |
此外需交叉引用更新现有P5规则,显式指向 hub 与DATAHUB_ARCHITECTURE.md。
D3 与 DataHub 的实现深度吻合:DataHub.h 的subscribe()采用owner 生命周期守卫——owner对象销毁时订阅自动取消(auto-cancel on destroyed),这正是"widget 在 hideEvent 无需手动清理、在 showEvent 重新订阅"机制的基础。而调度器则是 DataHub 内部以 1 秒为周期的QTimer(scheduler_),统一驱动各 Producer 的refresh(),widget 确实不需要也不应该自建 QTimer。
四、交付物 2:Lint / CI 纪律检查(datahub-discipline)
第二个交付物是在 CI(.github/workflows/ci.yml)中新增一个专门的构建检查步骤,扫描fincept-qt/src/screens/目录,命中即让构建失败:
# Screens must not spawn Python directly. rg -t cpp 'PythonRunner::instance\(\).run\(' fincept-qt/src/screens/ && exit 1 # Screens must not call the deprecated fetch_* APIs. rg -t cpp '(MarketDataService|NewsService|EconomicsService)::instance\(\)\.fetch_' \ fincept-qt/src/screens/ && exit 1 exit 0该检查作为独立 CI 步骤datahub-discipline运行;失败输出需包含违规文件路径及一行修复提示,指向DATAHUB_ARCHITECTURE.md§4。
这条规则直接呼应 D1/D4:任何绕开 DataHub 直连PythonRunner或调用废弃fetch_*的代码,在合并前即被拦截。仓库现状印证了这些模式确实存在:例如 MarketDataService.h 中fetch_quotes、fetch_history、fetch_sparklines这三个回调式 API 正是 Phase 10 要删除的目标,其注释已明确标注 "Phase 3+: preferDataHub::subscribe(...)for streaming widgets"。
误报防护设计
文档的 Risk 章节专门处理了 CI 检查的误报问题:
rg模式可能命中注释或测试夹具 → 排除tests/**;- 允许
// NOLINT(datahub-discipline)注释作为豁免; - 豁免机制必须在文档中明确说明,防止被滥用。
五、交付物 3:删除废弃 API 清单
在确认 lint 规则能捕获新违规之后,开始删除。Phase 10 给出了精确的删除清单,按引入阶段分组:
行情服务(Phase 2/3)
MarketDataService::fetch_quotes(callback)MarketDataService::fetch_history(callback)MarketDataService::fetch_sparklines(callback)
这三个方法在当前源码中仍可找到(MarketDataService.h),Phase 10 执行时将从.h删声明、从.cpp删实现。
新闻服务(Phase 5)
NewsService::fetch_general(callback)、fetch_by_symbol(callback)、fetch_by_category(callback)、fetch_all_news_progressive(callback)
经济服务(Phase 6)
EconomicsService::fetch_series(callback)、fetch_batch(callback)
Phase 8 服务族
GeopoliticsService、MaritimeService、GovDataService、RelationshipMapService、MAAnalyticsService的全部回调式 fetch 包装
旧版 Qt 信号(Phase 4/7)
ExchangeService::tickReceived(...)PolymarketWebSocket::orderBookUpdate(...)AccountDataStream::positionsUpdated(...)及其同类信号- 若已无消费者连接,连带删除
AccountDataStream直通信号层
每个删除的验证流程
对每一项删除,文档规定必须按序执行:
- 用
git grep验证零调用者残留(Phase 9 结束时应全部迁移完毕); - 从
.h删除声明; - 从
.cpp删除实现; - 在全部三个预设(
win-release、linux-release、macos-release)上跑构建; - 验证测试套件全绿。
从源码结构看,被删 API 的替代路径均已就绪:行情类数据走
market:quote:<sym>等 Topic 的DataHub::subscribe,且 DataHub.h 提供subscribe的模板化重载,可自动把QVariant解包为注册过的强类型(T须经Q_DECLARE_METATYPE+qRegisterMetaType<T>()注册)。
六、交付物 4:docs/DATAHUB_TOPICS.md——Topic 注册表
Phase 10 要求一份对全终端所有 Topic 字符串具有权威性的注册表文档,表结构为:
| Topic pattern | Producer | TTL | min_interval | push-only | Notes |
|---|
Phase 2–9 引入的每个 Topic 都占一行。文档中给出的示例行(Phase 10 执行时由最终文档补全全集):
market:quote:<sym> MarketDataService 30 s 5 s no batched market:history:<sym>:<period>:<interval> MarketDataService 1 h 30 s no — ws:kraken:ticker:<pair> ExchangeService — — yes coalesce 50 ms broker:<id>:<acct>:positions BrokerProducer 5 s 2 s no — econ:fred:<series_id> EconomicsService 1 h 1 min no warm-start subset news:general NewsService 5 min 30 s no progressive publish agent:<id>:stream:<run> AgentService — — yes coalesce 100 ms该交付物在当前仓库已有实质落地:DATAHUB_TOPICS.md 已是一份内容远超示例的"工作注册表",包含数十个 Topic 家族。其约定包括:
- Topic 段以
:分隔,首段为域(domain),后续段为域内键(symbol、provider、series id 等); - 通配符
*匹配单个段; - 明确区分轮询型(有 TTL / min interval)与push-only型(如 WebSocket 流、Agent 流);
- 标注具体负载形状(如
agent:output:<run_id>的{request_id, success, response, error, execution_time_ms, final}); - 注明迁移别名(如
market:price:fncpt是market:price:token:<mint>的废弃别名)。
强制刷新语义
注册表中min_interval对应的运行时行为由 DataHub.h 的request(topic, force=true)实现:force=true可绕过min_interval_ms(用户点击刷新按钮即可在间隔门内强制刷新),但每个 Producer 的max_requests_per_sec()仍被遵守——狂点不会打爆上游。而 Producer.h 给出了速率限制的典型取值:Zerodha REST 3、Angel One REST 1、Polymarket 10。
七、交付物 5:DataHub Inspector 提升为用户可见界面
Phase 1 在开发标志下构建的DataHubInspector,在 Phase 10 中提升为用户可见界面:
- 位置:Settings →Developer Mode→DataHub Inspector;
- 受现有 "Developer mode" 设置开关控制(用户自行开启,默认不暴露);
- Inspector 代码本身零改动——Phase 1 的实现即视为生产就绪;
- 从
SCREEN_SOURCES中移除#ifdef FINCEPT_DATAHUB_ENABLED守卫——特性标志不再控制任何东西,因为 hub 已是唯一数据路径。
Inspector 的数据来源与 DataHub.h 的stats()呼应:TopicStats结构包含 topic 名、订阅者数、最后发布时间戳、最后刷新请求时间、错误计数、in_flight状态、是否 push-only、最后错误字符串等字段,可直接渲染为诊断界面。此外 DataHub.h 提供last_publish_ms(topic)与age_ms(topic),便于 Inspector 或 widget 渲染数据新鲜度指示——对金融终端而言,"显示一个价格却无法说明其年龄"是误导性的。
八、交付物 6:移除FINCEPT_DATAHUB_ENABLED编译开关
hub 不再可选之后,Phase 10 要求删除 CMake 选项FINCEPT_DATAHUB_ENABLED:
- 从
CMakeLists.txt:123删除该 flag; - 删除所有
#ifdef FINCEPT_DATAHUB_ENABLED代码块(大致包括main.cpp的 metatype 注册、Producer 注册调用); - 更新
CMakePresets.json中对该 flag 的引用。
仓库中已有配套的自动化脚本 strip_datahub_guard.py,专为 Phase 10 编写,可批量重写源文件:
#ifdef FINCEPT_DATAHUB_ENABLED ... #endif→ 保留主体、去掉 guard 行;#ifdef ... #else ... #endif→ 保留 if 分支、删除 else 分支;#ifndef FINCEPT_DATAHUB_ENABLED ... #endif→ 整块删除(legacy-only);#ifndef ... #else ... #endif→ 保留 else 分支(legacy 回退删除)。
脚本支持--dry-run预览、可处理无关宏的嵌套#if深度跟踪、会报告每个文件移除的行数;其 docstring 明确这是 "Phase 10 task: the hub is now the only supported data path"。目前git grep FINCEPT_DATAHUB_ENABLED仅在脚本自身的宏定义中出现,说明源码中的 guard 已随各阶段迁移逐步清除,Phase 10 主要是兜底清扫。
九、交付物 7:架构与阶段文档状态收尾
Phase 10 最后处理文档元状态:
- 在
DATAHUB_PHASES.md中将 Phase 0–10 全部标记为 ✅ DONE; - 将
DATAHUB_ARCHITECTURE.md状态从Proposal改为Current; - 在
docs/ARCHITECTURE.md顶部链接这两份文档; - 删除各阶段规划文档(
docs/datahub-phases/phase-0*.md)或移入docs/datahub-phases/archive/——工作落地后,阶段总文档 + 提交历史已足够。
十、成功检查清单:如何判定"关门"完成
Phase 10 给出了可执行、可量化的验收标准:
git grep PythonRunner::instance\(\).run src/screens/ → zero git grep MarketDataService::instance\(\).fetch src/screens/ → zero git grep NewsService::instance\(\).fetch src/screens/ → zero git grep EconomicsService::instance\(\).fetch src/screens/ → zero CI datahub-discipline 步骤绿色;在临时分支故意制造一次违规并验证构建失败 三个预设(win-release / linux-release / macos-release)无废弃 API 构建全绿 完整测试套件通过(Qt Test test_datahub + Phase 2–9 新增的下游套件) 在线构建手工冒烟:每个主要界面访问一遍,数据渲染与 Phase 10 前最后一个版本一致 docs/DATAHUB_TOPICS.md 表行数与运行时打开所有界面后的 hub.stats().size() 一致 git grep 中 FINCEPT_DATAHUB_ENABLED 不再出现最后一条"注册表行数 == 运行时 stats 数量"的验收尤其精妙:它把文档与运行时状态绑定,防止任何人私下添加 Topic 却漏注册——这正是 D5 规则的可验证形式。
十一、风险与缓解:不可逆阶段的工程纪律
Phase 10 的头号风险就是不可逆性(Irreversibility)——这是整个阶段的意义所在。文档给出了三层缓解:
- 时序闸门:仅在 Phase 9 生产稳定运行满两周后启动;
- 提交粒度:每次删除单独提交(separate small commit),单个回归可单独回退;
- 保留回滚锚点:打一个
v-pre-phase10标签发布,用于快速回退到"旧包装仍存在"的世界。
隐藏消费者风险
第三方代码(用户插件、scripts/下的脚本)可能仍在调用废弃 API。缓解方案:
scripts/目录也纳入 grep 扫描;- 必要时通过 Python bridge 继续暴露 API(内部实际走 hub);
- 用户可见的 Python API 表面不变——破坏面被隔离在 C++ 层。
CI 误报、Agent/MCP 回归
- 误报:排除
tests/**、允许NOLINT注释覆盖(文档已在前文详述); - MCP 回归风险低:MCP 工具面不暴露 Service API,外部 MCP 客户端调用的是工具名,实现是内部的。
十二、回滚预案:最后一次回滚的机会
Phase 10 的回滚 = 从打标签的 pre-phase-10 提交恢复废弃 API。文档给出了两条路径:
标准回滚(整体恢复):
- 检出
v-pre-phase10; - Cherry-pick Phase 10 之后、回归之前落地的 bug 修复;
- 重新部署。
外科手术式回滚(针对细微回归,如某个 agent 脚本故障):
- 仅重新加入出问题的那个废弃方法,作为调用
DataHub::peek()+subscribe()的薄包装; - 对被影响文件临时用 NOLINT 注释绕过
datahub-disciplineCI 规则; - 提交 ticket 迁移遗留调用方并择期复查。
由于 Phase 10 是唯一不可逆阶段,回滚预案必须提前成文,并在执行前于 staging 分支演练一次。
十三、超范围声明:Phase 10 明确不做的事
为了保持阶段聚焦,Phase 10 明确排除以下内容:
- 新 hub 功能——hub 在 Phase 9 后即功能完备;
- 性能优化——hub 已在 Phase 2–9 完成埋点,进一步调优属于上线后事项;
- 新 Topic 家族——Phase 10 之后的新增按正常服务添加流程走(
DATAHUB_TOPICS.md注册 + D1–D5 合规); scripts/Python 代码迁移——这些脚本是生产者的后端而非消费者,不与 hub 直接交互。
结语:从"可用"到"唯一"的工程收尾
Phase 10 展示了一个成熟项目如何为大型数据架构迁移收尾:先用九个增量阶段完成"可用"(每个阶段都可回滚),再用一个不可逆阶段达成"唯一"(删除双轨、规则强制、文档锚定运行时)。对 FinceptTerminal 而言,这套机制最终沉淀为三个互相咬合的资产——强制规则(D1–D5)、CI 纪律(datahub-discipline)、Topic 注册表(DATAHUB_TOPICS.md),以及一份随时可执行的回滚预案。值得注意的实践启示是:清理阶段的验收标准不该只看"代码删干净了",更应看"规则能否阻止它回来"——这正是git grep零命中、CI 违规验证、注册表与运行时 stats 对齐这三条检查同时存在的意义。
【免费下载链接】FinceptTerminalFinceptTerminal is a modern finance application offering advanced market analytics, investment research, and economic data tools, designed for interactive exploration and>项目地址: https://gitcode.com/GitHub_Trending/fi/FinceptTerminal
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考