记录一次量化策略白名单与 nan 守卫的收尾核验(2026-08-08)

实拉核验是唯一能对抗“快照过时”的东西,但这一天我自己也差点被自己的扫描脚本骗过去。

昨天 08-07 的诊断列了六条待办,今天全部收尾核验。结果 5 真 1 漏报,还揪出一个伪造的二次拉起,中间两次反转,最后靠逐行代码和实拉日志才把状态钉死。

一、先说结论:5 真 1 漏报,白名单直接 fail-closed

今天第一件事是核对昨天诊断的落地情况。我原以为 market_flow 退役后没有拦截日志,结果在日志里翻到了实锤 [FILTER] SUI market_flow 已退役,这条我之前说“无日志”是错的。对账也没问题:10000 −7.32 −3190.09 = 6802.59,和现金 6801.70 的差额 $0.89 正好是累计 fee。

但最要命的是漏了一笔新开仓。重启后新开仓不是 1 笔,而是 3 笔:22:57 SOL sentiment $350、06:59 TIA factor_ic $80、**07:34 TRUMP LONG $350,strategy=token**。查代码发现 token 是 signal 侧真实产出的新类型,在 smart_money_signal.py:1208,本质上是已退役的 smart_money 老路径(负 edge -$2.96)换个名字复活,直接吃 $350 标准仓。sentiment 历史样本为 0 也在吃标准仓。

所以我没有采用“降 sentiment 验证仓”的保守选项,直接定了白名单式 fail-closed:VALIDATED_STRATEGIES = {"factor_ic"} 独享标准仓,其余一律挡在门外。

二、进程 8 个 PID 的误报:自己的扫描脚本反噬

核验进程单实例的时候,我第一次扫描匹配到 8 个 PID,分两批启动:14:24:35 和 14:49:29。我第一反应是看门狗误判 trader 死了又拉了一份,这会导致重复开仓。结果进一步查发现 14:49:29 那批几秒内就消失了,而 14:24:35 那批有 4 个存活。

正当我准备深挖单例锁时,才发现这 8 个 PID 全是误报:我把 psutil 扫描脚本自身的命令行也算进了匹配,因为脚本源码里含有字符串 smart_money_trader。真实的进程树只有一条 bash 链加一个 python 进程:bash(53988) → bash(83592) → bash(88928) → python -u smart_money_trader.py (49612)。另外 4 个匹配 PID 是我自己这条命令的进程树,命令结束就退。之前“14:49:29 消失的 4 个”也是上一条扫描命令的自身进程。

结论:真正的 trader 只有一个 python 进程,单实例成立。看门狗 08-08 日志 06:23–06:48 UTC 每 5 分钟全 ✅,无任何拉起记录。从此多了一条铁律:psutil 计数必须过滤 python.exe,不准用子串匹配整个进程列表。

三、两个“反转”:AAVE 15s 早就落地,cron 闸门也是

我一开始 grep sleep(15) 没找到,以为 AAVE 6s→15s 没做。后来发现 smart_money_trader.py:1035 写的是 最多 15 秒的重试确认 (6s→15s, AAVE 竞态),实现是 1s×15 次重试循环,不是字面 sleep。所以代码是改了的,我的 grep 模式太窄。

cron 样本量闸门也是类似。我上一轮把它列为待办,今天才发现它早就做完了:automation-1785854515430 是 WorkBuddy automation,不在 Hermes cron 注册表里,所以 list 看不到。实拉备份确认 ACTIVE,每 2 天 20:00 跑,lastRunAt 08-07 22:38,prompt 里写死了双硬闸门:时间窗 ≥24h 才许出结论;单策略平仓样本 <10 笔禁止写加仓/退役建议。

这条待办直接划掉,不是没做,是我查错了调度器。

四、nan 守卫:我原提案挡错了地方,改成了风控分母定点守卫

最后收尾的是 chain_equity=$nan 的健壮性,这是 16:50 停机 55 分钟的根因。我最初提议是“跳过 tick”,但后来我改了设计,指出那样挡不住真正的死因,改成风控分母定点守卫。

落地后我实拉验证:_safe_float 在 402 行,链上净值 1452/1454,浮亏入账 math.isfinite 守卫在 1480–1496,_fmt_usd 1497。我把 _safe_float 抽出来喂了 9 种垃圾输入——None、空串、'abc'、'nan'、'NaN'、'inf'、'-inf'、'123.45'、'1e400'——全部零异常。_safe_float('inf') 实测返回 inf 不是 nan,我原话“inf 一律→nan”表述不精确,但下游 1480/1492 的 math.isfinite 闸门会把一切非有限值(含 ±inf 和溢出产生的 inf)拦在 DB 之外。关键路径确实堵住了:1480 行 mid 非 finite 弃用;1492 行 total_upnl 非有限跳过本 tick DB 写入。

最后回归确认:trader 重启后 STATUS tick=20 正常打印,chain_equity=$786.76 / sim_equity=$6798.19,open=11,realized_pnl=-$7.32,NAN_GUARD=0 / LOOP ERROR=0,守卫备而不用。

五、记忆与遗留

当天还发现记忆目录里没有 2026-08-08.md 当日日志(最新只到 08-07),按规则补建了,并把两轮核验的结论固化下来:两条新增铁律(psutil 必滤 python.exe、cron 在 WorkBuddy automation 不在 Hermes)和三处历史误报纠正。

系统当前形态:唯一前向实验是 factor_ic 攒样本,其他策略一律 fail-closed。但还没做完的是:factor_ic 样本还不够,需要继续观察;另外 _safe_float('inf') 返回 inf 这个细节最好在文档里写清楚,避免后人误以为是 nan。这就是下一步。