数据自愈BUG Cursor 2
数据自愈模块启动期 BUG 分析报告(Cursor 2)
基于 realtime_kline_service 启动日志与代码阅读的根因归纳。
1. 「尝试更大范围」未真正扩大窗口
现象(日志)
- 出现「数据不足: 1 条(24h 窗口),尝试更大范围」后,没有再出现「加载历史数据: N 条(48h/72h 窗口)」。
- 直接进入「第 1 轮检查...」,最终「无法确定修复目标」。
根因(代码)
orchestrator.py 中 _load_zscore_history 的时间窗口是写死三档:
needed_hours = (required_count * self.interval_minutes) / 60
time_ranges_hours = [
math.ceil(needed_hours), # 12
math.ceil(needed_hours * 1.3), # 16
math.ceil(needed_hours * 2), # 24
]
time_ranges_hours = sorted(set(time_ranges_hours)) # [12, 16, 24]
对 4h、要 3 条时,最大只尝试 24h。24h 仍只有 1 条时循环结束,没有 48h/72h 等更大窗口,「尝试更大范围」只是日志文案,没有对应逻辑。
涉及文件:src/utils/data_healing/orchestrator.py(约 L417–425)。
2. 仅 1 条数据时不生成任何修复目标
现象(日志)
SEI、XAI、IOTA、CAKE、SUPER、FARTCOIN(vs BTC)、PEOPLE、ATOM 等出现:
- 「数量不足(1/3), 已过时1284min」
- 「无法确定修复目标,终止修复」
根因(代码)
- 在
_diagnose里,shortfall_targets只在「连续且无缺口且数量不足」时生成:
# 3. 数量不足检查
shortfall_targets: List[datetime] = []
if is_continuous and not gap_times and len(records) < required_count:
shortfall_targets = self._generate_shortfall_targets(records, required_count)
continuity_checker在 少于 2 条 时直接返回is_continuous=False:
if len(records) < 2:
logger.debug(f"数据点不足({len(records)}条),无法判断连续性")
return False, [], completeness_pct
因此:当只有 1 条时,is_continuous 恒为 False,shortfall_targets 永远为空,_merge_repair_targets 得到空列表 → 打印「无法确定修复目标」。
即:恰恰在「只有 1 条、最需要补数」的场景下,自愈逻辑不生成任何补数目标。
涉及文件:src/utils/data_healing/orchestrator.py(L235–238)、src/utils/data_healing/continuity_checker.py(L55–57)。
3. 加载「时间窗」与修复「向更早补」语义不一致
(与 docs/数据自愈BUG Cursor 1.md 中的根因一致。)
- 加载:
kline_time >= NOW() - N 小时,只看「最近 N 小时内」的数据。 - 数量不足时的修复:在当前最早一根之前向更早补点(
_generate_shortfall_targets里earliest_time - i * interval)。
结果:补进去的 bar 若早于当前 12h/16h/24h 窗,重载时仍用同一套时间窗,看不到刚补的那条,出现「修了也白修」、多轮修复无效。
当前日志里多数在「只有 1 条」就终止,尚未进入多轮修复;一旦修复 1 条场景,仍会撞上「补了重载看不见」的问题。
涉及文件:src/utils/data_healing/orchestrator.py(_load_zscore_history 与 _generate_shortfall_targets)。
4. 同一标的在不同配对下表现不一致
现象(日志)
- FARTCOIN vs kNEIRO:12h 窗即拿到 3 条,自愈 ready。
- FARTCOIN vs BTC:24h 窗仍只有 1 条,自愈 failed。
说明
自愈是按 (symbol, base_symbol) 在 analysis_results 里查的,每个配对独立。有的配对历史上只写过很少的 zscore(例如只有 1 根 4h),所以即使用 24h 窗也只有 1 条。
这不是实现错误,但说明:「数据不足」往往对应该配对历史数据本身就极少;若 1 条时还不生成修复目标(见第 2 点),这些配对就会一直 failed。
5. 「已过时 1284/1285min」与单条数据
「已过时1284min」约 21.4 小时,说明那唯一一条是约一天前的 4h bar。
对 4h 来说,新鲜度阈值是 interval_minutes * FRESHNESS_MULTIPLIER(约 600 分钟),因此会被判为过时。
问题在于:过时 + 只有 1 条 时,既没有生成 shortfall_targets(因为 is_continuous=False),stale_targets 又只在 len(records) >= required_count 时生成,因此既不会按「数量不足」补更早的 bar,也不会按「过时」补更新的 bar,结果就是直接放弃。
涉及逻辑:_diagnose 中 shortfall 与 stale 的生成条件。
小结(BUG 清单)
| 类型 | 表现 | 根因 |
|---|---|---|
| 虚假的「尝试更大范围」 | 24h 仍不足就结束,没有 48h/72h | time_ranges_hours 最大只到 24h,没有更大窗口 |
| 1 条数据不修 | 数量不足(1/3) → 无法确定修复目标 | 1 条时 is_continuous=False,shortfall 从不生成 |
| 修了也看不见 | 多轮修复后条数仍不增加 | 加载「时间窗内」与修复「向更早扩展」不一致,重载窗不变 |
| 过时 + 不足 3 条 | 已过时但无修复目标 | stale 只在 record_count≥required 时生成,1 条时 shortfall 也不生成 |
建议修复方向(与 Cursor 1 文档一致)
-
1 条/0 条也生成修复目标
在len(records) < required_count时,即使is_continuous=False(例如只有 1 条),也生成 shortfall_targets(从当前最早一根向前补足到 required_count),或对 0 条沿用 full_timeline。 -
真正扩大窗口或改为「最近 N 条」
- 要么在
_load_zscore_history中增加更大窗口(如 48h、72h、168h); - 要么改为「按
ORDER BY kline_time DESC LIMIT required_count(可加最大时间范围)」的「最近 N 条」语义(推荐,见 Cursor 1 方案 A)。
- 要么在
-
修复后重载语义与「补更早」一致
若保留时间窗,则修复后重载至少应扩大窗口以包含本次 shortfall 目标时间;或采用「最近 N 条」使补写条在重载时可见。 -
可选:缩小启动自愈范围 / 提前终止无效轮次
见 Cursor 1 方案 C,减少启动耗时与无效 3 轮。
涉及文件一览
src/utils/data_healing/orchestrator.py:加载、诊断、shortfall/stale 生成、重载src/utils/data_healing/continuity_checker.py:len(records)<2时返回is_continuous=Falsesrc/utils/data_healing/config.py:LOAD_TIME_MARGIN等- 参考:
docs/数据自愈BUG Cursor 1.md(完整因果链与方案 A/B/C)