记录一次链上权益重置与 OOS 停写复盘(2026-08-25)

链上 ≠ DB,报数前必须先 user_state(),不许用 DB 兜底。

今天这一天,从早上 11 点发现两个红灯开始,到下午 4 点半把 sm_account.current_usdc 重置到链上实时值结束。中间有认错、有自愈、有对账、有写库,核心教训就一句话:拿 DB 当链上,早晚要还。

一、早上两个红灯:自报表冻结 2 天,OOS 又停写 17 小时

11 点左右,进程全活,trader 已经连续跑 11 小时没重启,心跳修复是稳的。但两个异常信号已经摆出来:

  • sm_account.updated_at 停在 Aug 23 14:37,自报表 2 天没刷新。
  • oos_ticks 末行是 Aug 24 18:08,到当下 11:22 又是 17 小时没写。

这两个信号方向不同:一个说明权益追踪层可能冻住了,一个说明 OOS 监控又出现了之前 Aug20→Aug23 那次的同款停写。按纪律,先拿链上真实数据说话。

二、链上实测:6 笔在仓、0 ghost,但权益数字对不上

直连 HL testnet 读 info.user_state(),链上实测 6 笔在仓、0 ghost,跟 DB 里 OPEN 数量对得上。未实现盈亏从 +$3.3 涨到了 +$20 左右,但关键数字是:

  • 链上 accountValue = 938.01
  • 自报表 current_usdc = 9680.98(冻结在 08-23 14:37)

$493 的总名义仓位根本解释不了 $9680 和 $938 之间的差。这说明 testnet 账户被重置过,DB 里那套“+$165 已实现 / $10k 初始”是上一个 testnet 纪元的陈旧记账,不是当前活账户的真实盈亏。

这里必须认个错:我 08-23 说“链上验证 $9,684”是错的。 那天 reconcile 根本没打印 current_usdc,我拿了自报表的 9680 当链上数报出去。这是五天里第二次在“报数前必须 grep 再下结论”这条上栽跟头。链上 ≠ DB,这条已经写进 memory 钉板。

三、OOS 空档自愈,决策 HALT

查 OOS automation 主表,状态是 ACTIVE / HOURLY,tracker 没有独立进程,全靠 automation 触发。但 oos_ticks 明明停在 08-24 18:08。

关键时间戳来了:OOS automation last_run_at = 2026-08-25 11:24:39 北京——也就是我刚查完 oos_ticks 的前一刻!我之前 11:22 看的是旧快照。等于 automation 在 11:23 刚写入了 id=66,是 17 小时调度空档后的自愈恢复,不是死。

但 id 66 的决策还是 HALT:gate3_sharpe=-5.42、gate4_maxdd=320.9%,完全没戏。factor_ic 毕业彻底无望。

同时按用户要求做了 HL 只读对账巡检(dry-run,没加 --apply)。结论:无 ghost 仓位,6 个 orphan 告警(SOL/BTC/ETH/AAVE/SUI/TIA 归 hl_testnet.db 视角),hl_smart_money.db 6 OPEN,hl_testnet.db 0 OPEN,账户等式残差 -$0.89 是已知历史偏移。没有红色告警。

四、DB 权益层不止 stale,还自相矛盾

为了搞清楚 reset 根因,做了一遍只读调查。结果发现权益追踪层比“过期”更严重:

  • sm_account.current_usdc = 9680.97(08-23 14:37)
  • sm_daily_baseline 08-23 行 = 10168.49(08-23 07:35)

同一天两张表相差 $488。而且 sm_daily_baseline 末行也停在 08-23,之后没有 08-24/25 的行。整个权益追踪层 08-23 后同步停摆,不是单个数值过期。

此时链上实时值已经从 938.67 变成 934.16,再变成 935.36,每拉一次都不一样。DB 和链上的漂移一度算到 936%。这已经不是用旧值能糊弄过去的,必须重置基线。

五、重置 sm_account:先核后写,写库时再实时拉链上值

重置方案不是拍脑袋。先读了 trader 的同步逻辑,确认:

  • open_position:current_usdc -= real_size,扣保证金。
  • close_position:current_usdc += proceeds,退回保证金+盈亏。
  • 风控分母 sim_equity = current_usdc + unrealized_pnl,而 unrealized_pnl 每 tick 由链上 mark 重写,current_usdc 本身不自动从链上同步。

所以把 current_usdc 设成链上全量账户值(已含保证金)反而是自洽的。6 笔 OPEN 持仓是真实的,entry 与链上逐一对齐,保留不动;sm_trades(198 笔) 和 factor_ic_oos.db 不动。

执行前先备份了 DB,写重置脚本时再实时拉一次链上值,避免 verify 到 apply 期间过期。重置成功,apply 时刻链上值是 935.36(比 verify 后又动了一点)。复跑核验,漂移归零到 **0.01%**,trader 每 tick 重读 sm_account,不用重启。

六、复盘与下一步

今天把“自报表 2 天没刷新”和“OOS 停写 17 小时”这两个表象剥开,底下真正的雷是:DB 账本和实盘脱钩,而且不是今天才脱,是 08-23 就冻住了。

已经记进 memory 的教训有两条:一是链上 ≠ DB,报数前必须 user_state();二是调度空档后自愈不能当没事,17 小时不可靠这个模式要留档。

遗留:任务 75 还没做完,得建一个每日 sanity 检查脚本,链上 vs DB 漂移 >5% 就报警。下次再出现 updated_at 冻住两天的情况,希望是它先叫,而不是我人肉去对。