数据自愈BUG Cursor 1

数据自愈启动缓慢与修复无效:根因与修复方案

现象(来自日志)

  • 每次启动都对多个交易对(如 EIGEN、MOVE、FARTCOIN 等)做数据自愈。
  • 部分对子始终显示「数量不足(2/3)」,连续 3 轮修复,每轮都「Level 1修复完成: 成功=1, 实际写入 1 条记录」。
  • 3 轮后仍为「自愈结果: degraded | D级 (66.7%) | 数据量: 2 条」——修复没有提升可见条数。

完整因果链:输入 → 状态变化 → 调用路径 → 出错点 → 根因

1. 输入(Trigger)

项目 说明
入口 实时 K 线服务实例化:RealtimeKlineServiceBase.__init__()src/services/realtime_kline_service_base.py L118)
触发时机 构造时第 9.6 步执行 _run_data_healing()(L192),在加载配对缓存、构建订阅列表之前
输入数据 heal_pairs = 候选配对 ∩ DB 中已有 zscore 的配对(L1808–1811);repair_count = ceil(144×5/240) = 3(L1846–1849);每个配对 (symbol, base_symbol) 与 repair_timeframe='4h'

2. 状态变化(State Transitions)

阶段 状态 位置/条件
S0 DB 中某 (symbol, base_symbol) 在「某时间窗内」仅有 2 条 zscore_4h 记录(如 kline_time = T1, T2,且 T1 < T2) analysis_results
S1 加载得到 records = 2 条,按 kline_time 升序 _load_zscore_history(3) 返回,orchestrator L159
S2 诊断:连续、无缺口、数量不足 → shortfall_targets = [earliest - 4h] = [T0] _diagnose() L262–263,L413–422
S3 修复写入 1 条 (kline_time=T0) 到 analysis_results RepairExecutor.repair()_repair_from_klines_insert_records,L184–205
S4 重载再次调用 _load_zscore_history(3),仍只返回 2 条(不包含 T0) L205,同一套 SQL 与时间窗
S5 下一轮诊断仍为「数量不足(2/3)」,shortfall_targets 仍含 T0,循环重复 L162–205,最多 3 轮
S6 最终 records 仍为 2 条,_final_assessment 得到 degraded / 数据量 2 条 L207–209,L334–348

3. 调用路径(Call Path)

RealtimeKlineServiceBase.__init__()
  └─ _run_data_healing()                                    [realtime_kline_service_base.py L1766]
       └─ for (symbol, base_symbol) in heal_pairs:          [L1831]
            └─ DataHealingOrchestrator(...).heal_and_prepare(required_count=3)  [L1833, L1851]
                 ├─ Phase 1: records = _load_zscore_history(required_count)     [orchestrator.py L159]
                 │    └─ 循环 hours in [12,16,24]: WHERE kline_time >= NOW()-hours, ORDER BY kline_time DESC
                 │         break 当 len(rows)>=3;否则 records = rows(可能 2 条)  [L458–489]
                 │
                 └─ for iteration in 1..max_iterations:      [L162]
                      ├─ diagnosis = _diagnose(records, required_count)          [L166]
                      │    ├─ checker.check_continuity(records, 3)             [L252–253]
                      │    ├─ 若 连续 且 无 gap 且 len(records)<3 → shortfall_targets = _generate_shortfall_targets(...)  [L261–263]
                      │    │    └─ [earliest_time - i*interval for i in 1..need_count]  [L419–422]
                      │    └─ is_healthy = False
                      │
                      ├─ all_targets = _merge_repair_targets(diagnosis)        [L176]
                      ├─ repaired_count = executor.repair(all_targets, ...)   [L184–189]
                      │    └─ RepairExecutor.repair → _repair_from_klines → _insert_records  [repair_executor.py L54–86, L174–176]
                      │
                      └─ records = _load_zscore_history(required_count)        [L205]  ← 重载仍用同一时间窗

4. 出错点(Failure Point)

出错点 位置 错误行为
F1 _load_zscore_history 的 WHERE 与 break 条件(orchestrator.py L459–484) 取数语义是「在 NOW()−N 小时以内的、按 kline_time DESC 的前 required_count 条」。补写的时间点 T0 = earliest−4h 早于现有两条,若 T0 落在当前尝试的窗口(如 12h)之外,则重载时该条不会被 SELECT 返回。
F2 同上,重载与首次加载使用完全相同的 time_ranges_hours 与 SQL(L458–476) 修复后不扩大窗口、也不改为「按条数取最近 N 条」,因此刚写入的 T0 若已在 12h 窗外,重载结果集不变,仍为 2 条。
F3 _generate_shortfall_targets_load_zscore_history 的语义不一致(L413–422 vs L437–489) 不足时补的是「更早时间」;加载看的是「最近 N 小时内」。二者未对齐,导致「补了也看不到」。

5. 根因(Root Cause)

  • 设计根因:加载层采用「时间窗内的最近数据」语义,而数量不足修复采用「向更早时间扩展」语义;两者未统一,修复写入的条在重载的过滤条件下被排除。
  • 实现根因_load_zscore_history 仅用 kline_time >= NOW() - make_interval(hours => %s) 限定范围,且重载时未根据本次修复目标扩展窗口或改为按「最近 N 条」取数,导致补写对重载不可见,诊断循环无法收敛。
flowchart TB
  subgraph input [输入]
    I1["服务 __init__"]
    I2["heal_pairs × repair_count=3"]
  end
  subgraph state [状态变化]
    S1["加载 → records=2 条"]
    S2["诊断 → shortfall=[T0]"]
    S3["修复 → 写入 T0"]
    S4["重载 → 仍 2 条"]
  end
  subgraph path [调用路径]
    P1["_run_data_healing"]
    P2["heal_and_prepare"]
    P3["_load_zscore_history / _diagnose / repair / _load_zscore_history"]
  end
  subgraph fail [出错点]
    F1["_load 用 NOW()-Nh 窗"]
    F2["重载不扩窗、不按条数"]
    F3["补早 vs 取窗内 语义不一致"]
  end
  subgraph root [根因]
    R1["加载「时间窗内」与修复「向更早扩展」未统一"]
    R2["重载未包含补写时间或未按最近 N 条取数"]
  end
  I1 --> I2
  I2 --> P1
  P1 --> P2
  P2 --> P3
  P3 --> S1
  S1 --> S2
  S2 --> S3
  S3 --> S4
  S4 --> F1
  F1 --> F2
  F2 --> F3
  F3 --> R1
  R1 --> R2

根因分析(简述)

1. 为何“修复”后重载仍只有 2 条?

加载逻辑在 src/utils/data_healing/orchestrator.py_load_zscore_history 中:

  • 相对当前时间的时间窗加载:kline_time >= NOW() - make_interval(hours => %s),依次尝试 12h、16h、24h。
  • 取数语义是:在「当前时间往前 N 小时」的窗口内取数据,再按 kline_time DESC 取前 required_count 条。

数量不足时的修复逻辑在 _generate_shortfall_targets

  • 当前已有数据的最早时间之前补点:earliest_time - i * interval(向更早扩展)。
  • 因此补写的是比现有数据更早的 kline_time(例如日志里的 2026-02-22 00:00:00)。

矛盾在于:

  • 若现有 2 条在「较久以前」(例如 04:00、08:00),补写的 00:00 更早。
  • 重载时若先用 12h 窗:NOW() - 12h 之后的才被选中,00:00 可能落在 12h 窗外,查询结果里就看不到这条新记录。
  • 于是重载仍然只得到原来的 2 条 → 下一轮诊断 again「数量不足」→ 再修同一时间点 → 形成无效多轮修复,且最终「数据量」仍为 2。

因此:修复是写入了的,但“按时间窗重载”的语义与“向更早时间补点”的语义不一致,导致补写条数在重载时被过滤掉。

2. 为何每次启动都要做大量修复?

  • 自愈对所有「候选配对 ∩ DB 中已有 zscore 的配对」执行:realtime_kline_service_base.py_run_data_healingheal_pairs 为上述交集,且串行对每个 (symbol, base_symbol) 执行 heal_and_prepare
  • 因此:
    • 配对数量多 → 总耗时长。
    • 凡在「时间窗内」不足 3 条的对子都会触发数量不足修复;又因上面 1 的 bug,部分对子会无意义地跑满 3 轮仍 2 条,进一步拉长启动时间。

3. 小结(因果链)

flowchart LR
  subgraph load [加载阶段]
    A["按 NOW()-Nh 窗加载"]
    B["得到 2 条"]
  end
  subgraph repair [修复阶段]
    C["向更早补 1 条"]
    D["写入 00:00 等更早时间"]
  end
  subgraph reload [重载阶段]
    E["再次按 NOW()-12h 等窗"]
    F["00:00 在窗外 → 仍 2 条"]
  end
  A --> B
  B --> C
  C --> D
  D --> E
  E --> F
  F --> B
  • 直接原因:重载使用「固定时间窗」,补写使用「向更早扩展」,二者不一致,导致补写对重载不可见。
  • 放大因素:对所有有数据的配对做自愈且串行,且无效轮次拉长单对耗时。

修复方案(建议)

方案 A:重载语义改为「最近 N 条」(推荐)

  • _load_zscore_history 中,不再用「NOW() - N 小时」作为主语义,改为:
    • (symbol, base_symbol) 查询 analysis_resultsORDER BY kline_time DESC LIMIT required_count(或略大一点如 required_count + 2 再取前 required_count),保证取到的是按时间最近的 required_count 条,与「是否在 12h 内」解耦。
  • 可选:仍保留一个最大时间范围(例如只考虑最近 7 天),避免扫到过旧数据:
    WHERE ... AND kline_time >= NOW() - interval '7 days' ORDER BY kline_time DESC LIMIT required_count
  • 这样,只要补写成功(同 symbol/base_symbol),重载时就会把这「最近 N 条」包含进去,修复轮次即可收敛,避免同一对子无意义地跑满 3 轮。

涉及文件:src/utils/data_healing/orchestrator.py_load_zscore_history 的 SQL 与循环逻辑。

方案 B:若保留时间窗,则重载时扩大窗口以包含本次修复范围

  • 在修复后重载时,至少使用能覆盖「本次 shortfall 目标时间」的窗口:
    • 例如用 min(shortfall_targets)NOW() 计算所需 hours,或直接使用 required_count * interval_minutes 对应的小时数(并乘一个系数如 1.5)作为单次重载的窗口。
  • 这样重载能看到刚补的较早时间点,但逻辑比「按最近 N 条」更绕,且仍需注意与「新鲜度」判断的配合。

方案 C:减少启动时自愈范围/轮次(辅助)

  • 仅对“明显不足”的配对自愈:例如仅当「当前窗内条数 < 某阈值(如 2)」或「完整度 < 某百分比」时才加入 heal_pairs 或才执行多轮修复,避免对已经 3 条的对子再做一遍检查。
  • 限制每对最大修复轮次:若检测到「本轮修复目标与上一轮完全相同」(例如 shortfall_targets 未变),可提前终止该对的自愈,避免无效 3 轮。
  • 可选:对自愈做并行或批量(需注意 DB 连接与超时配置),减少总墙钟时间。

以上 A 解决「修复后仍 2 条」的根本问题;C 作为补充可明显缩短启动时间。


建议实施顺序

  1. 先做方案 A:改 _load_zscore_history 为「最近 N 条」语义(可加最大时间范围),验证同一对子在 1 轮修复后重载即变为 3 条、不再出现 3 轮仍 2 条。
  2. 再视需要做方案 C:缩小启动时自愈范围或提前终止无效轮次,进一步缩短启动时间。

未改代码前,可先通过日志确认:修复目标时间(如 2026-02-22 00:00:00)是否总是早于 NOW() - 12h,以验证「时间窗排除补写点」的结论。

Read more

跑步的技巧(滚动落地)

“滚动落地(rolling contact / rolling foot strike)”不是一种教条式的“脚法”,而是一种 让冲击沿着整只脚、整条后链逐级传递的落地机制。 它的核心不是“你先用哪儿着地”,而是: 你的脚落地之后,冲击是不是像轮子一样滚过去,而不是像锤子一样砸下去。 这就是滚动落地的本质。 一、什么叫“滚动落地”? 你可以把它理解成两种完全不同的落地方式: 1. 砸地(撞击式) 脚像锤子一样拍到地上: * 要么后跟先砸 * 要么前掌先戳 * 冲击集中在一个点 * 一个结构瞬间吃掉大部分载荷 结果就是: * 后跟砸 → 膝盖难受 * 前掌戳 → 前脚掌磨烂 * 都不是长跑友好模式 这叫 撞击式着地(impact strike)。 2. 滚地(滚动式) 脚像轮胎一样“滚”过地面: * 不是某一点硬砸 * 而是外侧中足先轻触 * 再向前滚到前掌 * 最后从大脚趾蹬离

By SHI XIAOLONG

AMI的优越性

世界模型(World Models)的具体例子 如下,我按类型分类,便于理解。每类都附带实际实现、演示效果和应用场景。 1. Yann LeCun / Meta 的 JEPA 系列(最直接对应“世界模型”概念) 这些是 LeCun 主张的非生成式抽象预测世界模型代表。 * I-JEPA(Image JEPA,2023) 输入一张图像,模型把不同区域(context 和 target)编码成抽象表示,然后预测 target 的表示(不在像素级别重建)。 例子:给定一张遮挡了部分物体的图片,模型能预测“被遮挡物体的大致位置和属性”,构建对物体持久性和空间关系的理解。 这是一个“原始世界模型”,能学习物理常识(如物体不会凭空消失)。 * V-JEPA / V-JEPA 2(Video JEPA,

By SHI XIAOLONG

什么是:“世界模型(World Models)”

世界模型(World Models) 是人工智能领域的一个核心概念,尤其在 Yann LeCun 等研究者推动的下一代 AI 架构中占据中心位置。它指的是 AI 系统在内部构建的对现实世界的抽象模拟或内部表示,让机器能够像人类或动物一样“理解”物理世界、预测未来、规划行动。 简单比喻 想象你闭上眼睛也能“看到”房间里的物体会如何移动、碰撞或掉落——这就是你大脑里的世界模型。AI 的世界模型就是类似的“数字孪生”(digital twin)或“内部模拟器”:它不是简单记住数据,而是学习世界的动态、因果关系和物理直觉(如重力、物体持久性、遮挡、因果等)。 为什么需要世界模型? 当前主流的大型语言模型(LLM) 擅长处理文本(统计模式预测),但存在根本局限: * 缺乏对物理世界的真正理解 → 容易“幻觉”、无法可靠规划。 * 样本效率低 → 人类/

By SHI XIAOLONG

K线周期可配置化设计方案

K线周期可配置化设计方案 1. 背景与目标 当前 Beta 套利策略的 K 线周期硬编码为 "1h",分散在多个文件中。需要: 1. 将 K 线周期从 1h 改为 2h 2. 提取为环境变量 BETA_ARB_KLINE_INTERVAL,使其可在 .env 中配置 2. 影响范围分析 2.1 需要修改的文件(共 6 个) 文件 硬编码位置 修改内容 src/trading/config.py BetaArbConfig dataclass 新增 kline_interval 字段,

By SHI XIAOLONG