记录一次 trader 停摆 38 小时与 factor_ic“假 HALT”的排查(2026-08-29)
记录一次 trader 停摆 38 小时与 factor_ic“假 HALT”的排查(2026-08-29)
trader 不是代码坏了,是重连逻辑把网络抖动当成了死罪,而 factor_ic 的 HALT 只是监控标记,真正没停过交易。
今天从发现 hl_smart_money.db 的 mtime 停在 08-27 19:16 开始,一路查到 watchdog 反复拉起失败、readonly 假象、502 重连自杀,最后改掉 hl_connection.py 的 sys.exit(1),顺手把 factor_ic 的 HALT 登记落地。过程有点绕,记下来。
一、trader 停摆 38 小时:从 DB 时间戳发现不对劲
早上第一眼看到 hl_smart_money.db 的 mtime 停在 08-27 19:16,距离当时约 1.5 天。trader 日志尾部显示“连续 11 次重连 502 失败 → 放弃退出”,但进程列表里却有几个 PID 存活。当时我还没分清哪个是真正的 trader,先做三件事:确认进程身份、核对日志时间线、看 DB 是否还在写。
核对结果:当前没有存活的 smart_money_trader.py 进程——只有 watchdog(78616) 和 signal(114464)。日志文件 sm_trader.log mtime 停在 08-27 19:26,最后带日期的 STATUS 停在 08-27 19:16:20。也就是说 trader 已经停摆约 38 小时,而 watchdog 没能把它稳定拉起来。这已经是严重问题了。
二、readonly database 是假象:手动跑 trader 完全正常
刚开始查 watchdog 日志,发现它在反复拉起 trader,但 trader 启动即 EXITED(code=1),而且 [start_detached] 报 Permission denied 打开原日志,退化到新日志 sm_trader.log.1787972703.log。接着在启动日志里看到 LOOP ERROR: attempt to write a readonly database,于是怀疑是我们之前加的 readonly 护栏触发自杀重启。
但奇怪的是,我手动带 env 跑 trader,它完全正常:顺利进入 loop,真实平掉了 5 笔 stranded 持仓(BNB/APT/BTC/OP/SOL),无任何 readonly 报错(exit=124 只是我 timeout 杀的)。DB 文件本身也完全可写(journal_mode=delete,os 写测试 OK)。所以 readonly 根本不是文件系统问题,也不是 trader 代码问题——是 watchdog 通过 start_detached 拉起时的某个环节导致连接只读。后来发现,readonly 更可能是偶发的并发/锁残留,之前诊断脚本可能短暂持锁,已经自愈。
真正要命的不是 readonly,而是 HL testnet 网络层的间歇性 502。
三、根因:trader 把 11 次 502 当成了永久退出
手动跑 trader 正常,但 watchdog 拉起的 trader 日志里是“连续 11 次重连 502 失败 → 放弃退出”。也就是说,每次 watchdog 拉起 trader,trader 撞上 HL testnet 的 502,重连 11 次后直接 sys.exit(1) 永久退出,watchdog 又把它拉起来,形成“拉起→撞 502→退出→再拉起”的死循环。链上读(Info/user_state)还能用,但交易/行情端点间歇 502。
hl_connection.py 里那个重连逻辑第 170-173 行附近,原先达到 MAX_RECONNECT_ATTEMPTS(10) 后直接 sys.exit(1)。这就是为什么 trader 停摆这么久——网络抖动本来几秒就能恢复,但代码把“连续失败”当成了不可恢复的致命错误。
四、修复:删掉自杀,改成持续重试 + 长退避
确定根因后,改动很直接:**hl_connection.py 第 170-173 行删掉 sys.exit(1)**,改成达到 MAX_RECONNECT_ATTEMPTS (10) 后不再自杀,重置 _reconnect_attempts=0,进入下一窗口,time.sleep(BACKOFF_CAP=60s) 后 return (None, None),主循环下一轮 should_reconnect() 仍然触发,持续重试到网络恢复。同时备份了 smart_money_trader.py → _attr_0829.py。
验证过程:11:24:03 trader pid=108368 启动进 loop;11:24:33 读 signal.json,过滤掉 cvd_momentum / market_flow(已退役,符合 v1.39 DISABLED 列表);11:25 两进程稳定运行。后面 psutil 实测 6 个 WATCHED 进程全在位:smart_money_trader 108368、smart_money_signal 114268、smart_money_oos 68508(08-14 启动)、sim_loop 87932、factor_monitor 76312、mc_paper_loop(maincoin_paper.py)83144,watchdog 117848 也常驻(旧 78616 已 kill)。账本 DB 末次写入 11:44,确认还在循环写。
另外,我顺手把代码里两处还写着旧 sys.exit(1) 自杀逻辑的注释修正了,避免误导后续维护。
五、顺手查的两件事:漂移正常,factor_ic 的“假 HALT”
处理 trader 的同时,还跑了两个检查。
链上-DB 漂移检查:跑了 _sm_sanity_check.py,结果 chain=900.8364 USDC,db_total=912.0596(含未平仓保证金 399.88),drift=**1.25%**,阈值 5.0%,退出码 0,正常,没有 testnet reset 脱钩迹象。
factor_ic OOS tracker 扫描:factor_ic_oos_tracker.py once 跑完,实测样本数 25(达标),但 Gate2 binomial p=1.0000(不显著)、Gate3 年化 Sharpe=-2.676(未达标)、Gate4 maxDD=320.9%(远超)、regime 回落率 39.1%(<50%),所以决策 HALT。
一开始我以为 HALT 会真的停掉 factor_ic 交易,但查下来发现根本不是那么回事:tracker 本身只写 decision 字段,不做任何动作;smart_money_signal.py 里没有读 oos_ticks 自动停牌的代码;factor_ic_oos_apply.py --confirm 只能把 validation 设为 True(→$80),而 L359 本来就是 True——也就是说 factor_ic 实际上一直以 $80 验证仓在正常交易,HALT 只是监控层面的判定。真正的交易开关是 FACTOR_IC_ENABLED(默认开启),目前是开的。
最后用户拍板“维持 HALT 并正式登记”,我执行了 factor_ic_oos_apply.py --confirm:validation 已是 True 无需改动,halt_log.md 追加 [EXECUTED],alerts.md 追加 🔔 NOTIFY。注意这两个文件真实路径是 docs/strategy_research/_prereg/,不是 defi_dashboard 根目录,这个纠正也写进了项目日志。
六、遗留
虽然 trader 已经恢复,但还有几件事没完:一是 HL testnet 的 502 间歇性问题还在,需要观察重连逻辑改动后能否跨过网络抖动;二是 factor_ic 的 OOS 数据(Sharpe=-2.676、回落率 39%、25 笔不显著)说明策略本身没有正 alpha,下一步要考虑是继续 HALT 观察还是直接退役;三是 watchdog 日志里那两条旧注释虽然改了,但最好再全量搜一遍有没有别的 sys.exit 自杀逻辑残留。