数据层 · 实时数据及时性 — 现状与落地设计
归属:ADR-031 数据能力内化分诊 分诊三问之下的数据层主线。承接来源(两份评测报告的收敛结论)见 当前 milestone 单一事实源 的 2026-06-25 块。
§1 是什么
FinBayes 在「实时数据的获取及时性」上要立得住。这是两份独立评测共同指向的真实能力短板,两个用户实测最有代表性:
- ETF 15 天回测题:K线只覆盖约 3.5 天,用户当场说「4 天的数据都不到,太少了」。
- 宏观题:刚公布的 PCE actual 没拿到,机器人也承认「没法百分百确认刚刚公布的实际值」,然后基于假设把后面全部推理做完了——缺口被诚实标了,但分析没锚在一个明确参照上,读着像「没拿到数据就交差」。
这条线把「及时性」建到两件事上:取得更快更全(取数能力本身),和取不到最新值时诚实标清楚、并照样给出锚定参照的可用判断(输出诚实性)。前者是工具与源的扩建,后者是这一轮的第 1 刀。
源策略(owner 2026-06-25 拍):免费源先榨干、不上付费。首刀范围(owner 拍):宽——macro + 盘中 + 通用实时数据一起看,不先窄到单一场景。
§2 要做什么
| 刀 | 范围 | 状态 |
|---|---|---|
| 第 1 刀 | 诚实输出契约 + 新鲜度标注 | 已落地(§3) |
| 第 2 刀 | macro 发布感知:优先 DH + 等 R2 日历 + 给 DH 提需求;过渡用 as_of + 关键指标必查 + 诚实话术堵「报未发布数」 | 设计已签(§4) |
| 第 3 刀 | 报价:第一性原理三层重构(源=可达×需要 / 诚实=material 才外显 / 结尾推进用户意图) | 设计已签(§5) |
| ohlcv 深度 | 长周期 K线:since 游标分页累加 / 长回看自动降频 | 并行机械修、不占刀序(§5) |
§3 第 1 刀:诚实输出契约 + 新鲜度标注(已落地)
§3.1 是什么
一条贯穿取数全过程的三态契约:数据拿到就据实分析、没拿到就锚定参照照常做条件化分析、明确排除「没数据就停」。它把「诚实」从一句搪塞(「没获取到数据」然后收工)扶正成「标清楚缺口 + 照样给完整分析」。
§3.2 做成什么样(用户视角)
- 拿到新鲜数据 → 据实分析,亮源 + 时点(as_of)。
- 没拿到新鲜数据 → 条件化分析:亮缺口 + 锚定一个明确标注的参照(上一期已知值 / 公布的预期或一致预期 / 用户给的值)+ 在该参照上照常给完整分析 + 说清真实值偏高 / 偏低时结论怎么变(敏感度)。
- 明确排除:一句「没数据」然后停(cop-out)——这是失败,不是诚实。
回到 core_pce:正确形态是「刚公布的 actual 还取不到;上一期核心 PCE 同比 = X%、市场一致预期 = Y%,按这两个锚来看……若 actual 高于 Y% 则……」——而不是「没查到 PCE,以下基于假设」。
§3.3 怎么做(四个注入点,各管一个触发时机)
诚实来自确定性的注入,不靠模型自觉。四个点对齐 ReAct 回路里「数据可能缺 / 陈旧」的四个不同时机:
- 常驻倾向(
finbayes/agent/financial_guidance.py表达纪律新增一条「数据缺口照样给判断」)——覆盖「压根没去查」这种没有失败信号的情形(core_pce 正是:查了 core_cpi、没查 core_pce)。让模型默认就带「锚定 + 条件化」倾向,不必等到取数失败才被纠。 - 取数失败 notice(
finbayes/agent/loop.py的_DATA_UNAVAILABLE_NOTICE)——每次取数失败,把契约贴进模型看的工具结果:不编造 + 不止于「没拿到」+ 锚定参照照常分析 + 标敏感度。 - 新鲜度标注(
finbayes/agent/loop.py新增纯函数_freshness_note,复用finbayes/data/fetch_result.py出处信封的as_of/staleness_s/fallback_used)——取数成功但陈旧 / 走了回退源时,确定性贴一行[FRESHNESS]给模型,让它把时效性条件化。新鲜的主源数据不贴(不制造噪音),陈旧(≥1 小时)或走回退源才贴。 - 兜底收口(
finbayes/agent/loop.py的_force_closure)——预算耗尽 / 停滞 / 连续取空触发收口时,最终回答同样要求锚定 + 条件化、不搪塞。
四点互补不重叠:第 1 点是默认倾向、第 2 点管失败、第 3 点管陈旧成功、第 4 点管终局收口。它们都在 ReAct 回路内做上下文注入或软指引,不在主回路外架墙。
§3.4 落地与验证
- 实现:上述四个注入点 + 纯函数
_freshness_note(确定性、防御式读信封、新鲜主源数据静默)。FinBayes 本地提交、未推。 - 自验:全量 788 passed / ruff 净;
_freshness_note单测 9 例(非 dict / 新鲜主源静默 / 陈旧带 age + as_of / 回退源即便新鲜也标 / 一小时阈值含界 / 滞后类型正常滞后静默 / 滞后类型仍 fallback 触发 / 负龄不出负号 / 亚分钟不带龄括号)+ 失败 notice 测试加「锚定 + 条件化」断言,pin 住 cop-out 修复不被改回。 - 真实证据待补:契约措辞产生的真实行为须 gateway 重启后用一道缺数据的问题 live 验——offline 只验得出「无结构性崩」,验不出「措辞真的改了行为」。这是这一刀诚实成立与否的最后一步。
§3.5 经四方对抗 review 收紧
落地后过了一轮四方独立对抗 review(金融正确性 / 工程健壮 / 架构反过度 / 安全诚实回归),四路全「通过需改」、无安全或编造回归(安全路确认重写比旧 notice 更强防编造、新鲜度函数注入面结构性封闭)。据评审把这一刀做得更准的几处收紧:
- 新鲜度按 data_type 分层:
macro/institutional_holdings(13F)这类结构性滞后数据,正常滞后不再触发新鲜度标注(只在走回退源时贴)—— 一份月度宏观印数就是「最新即官方」、没有「更新的版本」,旧逻辑用一个秒数平阈值把它标成「待更新」是误导;同时重写了 banner 措辞,去掉「等更新值进来」的错误暗示。 - 负龄钳零:回退源时戳略超前 / 时钟偏移会让「陈旧秒数」变负,旧逻辑会渲染「≈-3m old」这种来自未来的字样 —— 钳零 + 龄括号只在真有 ≥1 分钟滞后时才出。
- 敏感度泛化:契约里「真实值偏高 / 偏低时结论怎么变」泛化成「不同取值 / 情形」,覆盖定性 / 事件型缺口(如某决策做没做、某并购成没成),不把它们硬塞进数值敏感度的壳。
- 体量护栏脱钩 + 收口瘦身:常驻指引体量上限与实测脱钩(留足 headroom,并写明撞顶是「该把方法论挪进 skill」的信号、不是抬上限);预算耗尽收口指令引用既有纪律、不再整段重述。
延后记账(评审标为可选、非本刀):crypto / news 自建 provenance 没顶层信封键 → 新鲜度对它们暂静默(覆盖盲区,待这些工具补信封或后续刀处理);锚集可补「同族高频代理 / 上游分项」;盘中亚小时延迟的精细化归第 3 刀报价源工作。
§4 第 2 刀:macro 发布感知(设计,待签)
由第 1 刀 live 验暴露的洞驱动:agent 把还没发布的 5 月 core PCE 报成「3.4%」(数来自 web_search、无法证伪)。本刀让 macro 读数发布感知,从源头堵住「web 给个数就当真」。源选型由 researcher 做过免费源版图扫描。
§4.1 是什么
让 macro 读数发布感知:对一个宏观指标,agent 知道「最新已发布的 actual + 参考期 + 自算同比 + 下次发布日期」,从而绝不把还没发布的月份当成已公布事实报。
§4.2 做成什么样(用户视角)
core_pce 正确形态:「最新已发布核心 PCE = 2026 年 4 月、同比 3.X%(参考期 4 月、约 5-30 发布);5 月数据预计 6-27 发布、尚未公布。」—— 而不是自信报一个还没出的「5 月 3.4%」。同比由数据层直接给(agent 不必再 web_search 捞一个可能错的数)。
§4.3 源选型:FRED 主干、共识延后
| 源 | 免费档能给 | 共识 | 下次发布日期 | license(可给终端用户) | 判定 |
|---|---|---|---|---|---|
| FRED(已接、缺 key) | 最新 actual + 参考期 + 自算同比;未来发布日程 | 无 | 有(releases/dates,直连 HTTP) | public domain ✓ | 主干 |
| Trading Economics | 含 forecast / 共识 | 有 | 有 | 免费档排除美国 | 延后 / 付费 |
| FMP | 含 estimate、覆盖 PCE | 有 | 有 | 免费档 license 禁止给终端用户展示 | 排除 |
| Finnhub | 经济日历 | 部分 | 有 | 多半付费、US-free 未证实 | 延后 / 待验 |
| investing.com 抓取 | 全 | 有 | 有 | ToS 禁止、JS 脆 | 排除 |
结论(owner 2026-06-26 拍:优先 DH + 等 R2 + 给 DH 提需求,不内化 FRED):免费世界 forecast / 共识没有干净结构化源;FRED 虽能内化「下次发布日期 + 同比序列」,但 DH 的 /calendar + nextReleaseDate 本就是 R2「在建」路线,且 DH 已有 FRED 实践——当前 workload 下,优先复用 DH、给 DH 提需求比在 FinBayes 重复建设一套 FRED 日历更合理。该判断是阶段性 sourcing decision,不代表 DH 是过渡桥;若 latency、resilience、cost 或产品需求改变,按 ADR-033 重新评估 DH、FinBayes 内化或双轨。FRED 内化路保留为独立兜底,本刀不走。
§4.4 前置依赖:给 DH 提需求(不配 FRED key)
本刀不内化、不配 FRED key。前置 = DH R2 落地 /calendar + 每指标 nextReleaseDate。主控起一份 DH co-request(DH macro 发布日历与序列端点需求)请 DH:① /calendar + 每指标 nextReleaseDate 上线;② /macro/latest 之外补一个序列 / 历史端点(供 FinBayes 自算同比——现 /latest 是点值、读不出同比);③ 给出 R2 时间预期。
§4.5 怎么做(DH 落地前的过渡 + 落地后的消费)
过渡期(DH R2 未到,现在就能堵核心洞,不靠 nextReleaseDate):
- 用已有的
as_of做发布感知:DH/latest已给as_of(参考期)。常驻倾向——若用户问「刚发布的最新一期」而手里最新as_of还停在上一期,就老实说「我这边最新是 <as_of> 那一期,更新的一期我的源里还没有」,绝不报一个没在数据里的新值。这就是 cut-1 三态契约在 macro 上的落地(锚已发布参考期、照常解读、不编未发布值;上位原则=诚实是约束、解决问题是目标)。 - 关键指标必查:core_pce / cpi / core_cpi / nfp / unemployment 等做成「问到宏观先必查 DH」的常驻倾向,别漏查 / 绕 web_search。
- 同比:
/latest点值读不出同比 → 过渡期让模型据上一期 + 当期给定性(涨 / 落 / 约多少、标「待精确序列」),不 web 抓一个可能错的同比当事实。
DH R2 落地后:消费 DH /calendar 的 nextReleaseDate(精确「下次 6-27 发布」)+ DH 序列端点算同比;dh_macro.py 适配器解禁 nextReleaseDate 字段(现按契约不读);信封照旧(as_of=参考期、不拿 fetch-time 冒充)。
§4.6 这一刀的范围
本刀 = macro 发布感知,走「优先 DH + 提需求」:过渡期用 as_of + 必查 + 诚实话术堵「报未发布数」,DH R2 落地后消费其日历 + 序列。不含 forecast / 共识(延后)、不内化 FRED(留作独立兜底、本刀不走)、盘中亚小时延迟归第 3 刀。
§4.7 决策(owner 2026-06-26 已签)
① 优先 DH、等 R2、给 DH 提需求——签;② 不内化 FRED、不配 key——签;③ 过渡期用「as_of + 必查 + 诚实话术」先堵核心洞——签;④ 主控起 DH co-request(日历 + nextReleaseDate + 序列端点 + R2 时间预期)。
§5 第 3 刀:报价 — 第一性原理三层重构(设计,owner 2026-06-26 签)
由 06-26 群里两个 BTC 报价 finding 驱动(① 答案甩「Binance 当前数据不可用」噪音 ② 报完价不追问意图 / 给下一步),从第一性原理重推,不打补丁。
§5.1 第一性原理:报价是「为用户决策定向的一次接触」,不是「跑固定管线吐个数」
一个价格,用户真正要的是三件:从哪来、有多可信、接下来往哪走。这三件都该由「用户问这个价是为了做什么」反推,不由固定管线决定。上位约束 = 诚实是地板、解决问题是目标:在不编造数据 + 不幻觉的前提下,把用户的问题往前推一步——诚实不是目的本身。
§5.2 三层(各被一个 finding 戳穿)
① 源 / 场所层 —— 场所 = 可达 × 需要
- 病根:场所写死成一对(binance + coinbase),还带个这个部署上永远 451 的死场所;那对的目的(跨场所价差)对「BTC 多少钱」不是用户要的。
- 设计:默认 = 最权威 / 最深的可达场所的价 + 源 +
as_of;跨场所价差降为按需深档(用户问套利 / 流动性、或场所真分歧才出)。可达集配置 / 学习出来,不硬编带尸体。原「退役 robinhood」是这一层同一原则——flaky / 受限源不该在路径;robinhood(美股)/ binance(crypto)是两个实例。
② 诚实 / 降级层 —— material 才外显(精化 cut-1)
- 病根:quote 工具把每个场所状态(含失败的 binance)都渲染进模型看的结果 → 模型把内部冗余失败当 caveat 甩用户。
- 设计:降级只外显改变用户能信什么的(陈旧、服务源材料性偏差);内部冗余失败不外显(主场所给了好价、某冗余场所挂了 = 管线记账、非数据 caveat)。报「服务了答案的源 + 它的新鲜度」,不报「好源已成功时失败的那些源」。= 给诚实加一道「对用户是否 material」的闸;上位是「解决问题」——别让「为标而标」反而吓退用户。
③ 意图 / 接触层 —— 结尾推进用户的真实目标
- 病根:两条结尾指令打架(
financial_guidance「下一步总给」vs_FOLLOWUP_DIRECTIVE「没分支就别加」),模型把裸报价判「无分支」→ 查完即停 = 查价机器人。 - 设计:把两条塌成一条原则——「答案结尾要推进用户真实目标」,由读隐含意图驱动、非「有分支就加按钮」的通用规则。报价 ≈ 朝某决策的开场(买 / 卖 / 等),读出这个、给下一档(技术面 / 关键位 / 设提醒 / 加自选)。
§5.3 范围与边界(防过度)
- 第 ① 层报价路径专属(场所解析器)。第 ②③ 层背后原理通用,但本刀只在报价路径落第一个实例(quote 多场所渲染 + 报价意图读取),设计成可推广、不现在全局重构。
- 守 cut-1(亮源 + 时点、不编造)、守「judgment 自己出」;本刀让报价从「管线吐数」回到「定向接触」。
§5.4 怎么做(落地点,签后实现)
- 场所解析器(源层):crypto quote 默认场所对去掉死的 binance、换 coinbase + okx(两家经代理都通);引入「可达场所集」配置;spot quote 默认走资产主盘口(coinbase BTC-USD 而非薄的 BTC/USDT),跨场所价差按需。美股同原则核 quote、退役 robinhood。
- material 降级渲染(诚实层):
crypto_quote.py多场所结果只渲染服务了答案的场所 +as_of;失败的冗余场所不进用户面(除非全失败 / 价差不可算且用户要)。 - 报价意图读取(接触层):解
financial_guidance「总给」vs_FOLLOWUP_DIRECTIVE「opt-in」矛盾,塌成一条;报价 / 查价类问题给 1-2 个轻量下一步。
§5.5 ohlcv 深度(并行机械修,不占刀序)
长周期 LTF 回测要的根数超单次 1000 根上限(15 天 5m 约需 4320 根)。方向:since 游标分页累加 / 长回看自动降频(>N 天用 1h / 4h)。可与上面并行。
§6 架构站位(支撑注脚)
- 诚实是约束、解决问题是目标(贯穿全线的上位原则,owner 2026-06-26):不编造数据(基于真实信息的推理 / 假设不算编造)+ 不幻觉 = 地板;在此约束下把用户问题往前推一步才是目标。任何「诚实 / 降级 / 出处」机制都服务这个目标、不为标而标——cut-1 三态契约的「照样分析(still-analyze)」、刀3 的「material 才外显」都是这条的实例。
- 诚实是渲染层 + 信封的确定性,不是模型软遵从:四个注入点都是系统确定性产生的(信封字段、确定性 banner、契约文本),模型只负责在被喂的诚实上下文上做判断。
fetch_result.py出处信封是新鲜度的单一事实源:as_of/staleness_s/fallback_used已在采集层算好,第 1 刀只是把它从「校准层事件」也喂给「答案上下文」,不新造时效计算。- 不碰快线主回路的控制结构(沿用 ADR-025 头号铁律「不在 ReAct 主回路之外架控制墙」):四个点都是回路内的上下文注入 / 软指引。
- 质量优先于简短:契约要求「照样给完整分析」,不为省事砍掉分析、也不为「在诚实上加分」而退化成搪塞。