记录一次看板数据丢失的修复(2026-06-15)
记录一次看板数据丢失的修复(2026-06-15)
数据"消失"的时候,先看它是被删了,还是根本没写进去
TVL 看板某天开始是空的。不是加载慢,是图表直接报错,一张干干净净的空白。
这天把这块看板修回来。
一块空的图
页面 pages/1_tvl_rank.py 里的 Plotly 柱状图,x 轴取的是 protocol 字段:
1 | px.bar(x='protocol') |
它读到的是一个空列,于是图表报错、数据全丢。
前端报错看着像渲染问题,但真正的疑点应该往后推一步:数据是什么时候没的?是同步进来的数据本来就空,还是本来有、后来被写没了?
根因:往一个不存在的列里写
查下来,问题在写入端,而且比想象中更基础。
tvl_rank 表原本就没有 protocol 列。而 save_tvl_rank 在做 INSERT 的时候,把协议名写进了这个不存在的列——结果数据全丢。
同时,页面又反过来用 px.bar(x='protocol') 去引用这个空列,于是一起报错。
这条链子很典型:写入端写了个不存在的目标,读取端读了个空的结果,两头各自沉默,直到图表上什么都看不见,才被人发现。中间不会有任何异常抛出——INSERT 不会因为列不存在而"成功",但它也不会把问题喊出来。
修复四步
修复本身不复杂,但要一层层补齐。
建列:
1
ALTER TABLE tvl_rank ADD COLUMN protocol VARCHAR(255)
**改读取方法
db.load_tvl_rank()**,调整为提供别名并过滤旧脏数据:1
SELECT protocol AS name ... WHERE protocol IS NOT NULL
**改页面
pages/1_tvl_rank.py**,把图表 x 轴字段从x="protocol"改成x="name"。重新从 API 同步数据:补回 7661 行干净数据。
为什么第 2 步要 SELECT protocol AS name?因为建列之前,表里可能已经有历史的、protocol 为空的脏行。用 WHERE protocol IS NOT NULL 把它们挡掉,剩下的干净数据再用 name 这个别名交给页面——页面不直接依赖物理列名,中间的映射放在读取方法里。这样以后列名或表结构再变,改一处就够。
验证结果是:name 列正确包含协议名,Plotly 图表和 st.dataframe 格式化都恢复正常。
同一天,另一张看板的校对
同一天还顺手处理了 12 号页的一个问题——不是它坏了,而是要确认它没坏。
任务是 t_7b6f89d3:验证"策略启动时间"的时间门逻辑。核心要回答的是:有没有交易的 buy_ts 早于它所属版本的 data_start_ts?如果时间门失效,回测就会把版本诞生之前的行情算进去。
验证结果:
1 | buy_ts < data_start_ts 违规笔数:0 |
过滤逻辑有效。接着把 25 个版本的 data_start_ts 与 sim_strategy_versions 逐一对齐,全部通过:
v1.0~v1.9/v1.11~v1.15/v1.17/v1.22~v1.23:取自sim_versions.created_atv1.10/v1.16/v1.16.3/v1.16.4:created_at是脏值,正确回退到activated_atv1.17.1:无数据库记录,取自version_risk_history.py:704硬编码的deploy_ts=2026-06-10 22:00:00,符合设计意图
第二条尤其值得记:**created_at 会有脏值,activated_at 是它的兜底**。这就形成了一条 fallback 链——created_at → activated_at → deploy_ts。版本启动时间宁可用一个偏晚的兜底值,也不能用一个脏的早值,否则回测就会多算一段不该算的行情。
还确认了两个"看起来像 bug、其实不是"的点:v1.17.1 在 sim_strategy_versions 里没有记录,硬编码 deploy_ts 是设计意图;bt_cf_summary 表没有 run_id 列,_get_latest_run_id 是从 bt_cf_constraint_stats 读的,不影响 12 号页展示。
数据流路径也顺了一遍:
1 | _resolve_strategy_start_ts → _load_strategy_created_ts_map → bt_cf_summary 写入 → pages/12_counterfactual.py 展示 |
整条链路无 bug。 这次的价值不在于修了什么,而在于把"没坏"这两个字验证了出来。
巡检的现状
同天的第三个任务 t_c49fe03f,做的是一次"确认能力是否存在"的核查,结果比预想的更值得记。
先看交付:往 OpenViking 写了两条 case——DexScreener/GMGN/paper-trading 巡检体系,以及 P1 任务的完整记录。内容里带了那份 12 号页的时间门 fallback 链(created_at→activated_at→deploy_ts),还有一条 Windows 下 sim_loop 的恢复偏好:用 run.bat /MIN。
重点是核查本身——**"自动巡检"到底在不在**。
1 | cron API list → 0 个 job |
四个方向查下来,**backtest_summary_patrol 确认不存在**。
现有的是 cron_v3h_*.md 那套报告体系,成熟、内容扎实,但靠手动执行,没有自动化调度。
这个结论让 6 月 12 日那句"sim_loop 手动拉起来不够,应该上 WinSW/服务守护"落地了:不仅守护没上,连巡检本身都还没有自动跑起来。看起来像是有自动化的东西,其实一直是人在点。
顺带把 GeckoTerminal 那条免费 API 也记了进去:30 calls/min,best-effort 降级——它和 DexScreener 是一类的补充源,能拿到就拿,拿不到不影响主流程。
这一天合上了四张单子
回头看 6 月 15 日这一天的看板任务列表,其实一次性收口了四件事:
t_9107d845:tvl_rank表数据丢失及图表报错修复——就是这篇的主要部分t_7b6f89d3:策略启动时间逻辑验证——验证"没坏"t_c49fe03f:巡检能力核查——结论是"自动巡检不存在"t_8693621c:确认完成
四件事的性质完全不同:一个是修,一个是验,一个是查缺,一个是收尾。但把它们放在同一天看,反而凑出了一条完整的链——修好一个会丢数据的看板,再确认另一个看板的时间门没漏,最后发现连"定期检查"这件事都还没有自动跑起来。
缺的那一环最容易被忽略:数据看得见了、回测算得准了,可没人定时去看"它今天还正常吗"。这一天的三个结论叠在一起,指向的正是那个还没补上的守护层。
最后
这一天的三层。
最表面的一层,是 TVL 看板修好了:建了列、改了读取、换了 x 轴、补回 7661 行数据,图表恢复。
中间一层,是那个报错的分层。 页面报错只是最上层,往下是读空列,再往下才是"往不存在的列里写"——真正根因往往比报错的位置更靠后。这也是那张 sim_dexscreener_signals 表的教训的翻版:写和读之间隔着一层物理结构,谁都可能各自"成功"。
最里面一层,是这天做了一半的活其实是"验证没坏"。 12 号页的时间门、25 个版本的对齐、数据流路径——全都通过了,一条 bug 都没有。而巡检的核查更直接:结论是"没有自动化",也是结论。
技术活做久了容易低估"确认不存在"的价值。可这天的两件事说明,**"它是好的"和"它一直好"是两回事**,前者靠这次核验,后者靠守护——而守护,那时还没上。