记录一次 factor_ic 切 SHORT-only 的干净重启(2026-08-10)

代码里的开关是死的,进程里的 env 才是活的;而看门狗怎么拉起它,决定了这个开关能活多久。

这天从复验一版 factor_ic 开关报告开始,到真刀真枪把 signal 从默认 1/1 切成 SHORT-only,中间踩到的坑全在“运行时环境”上。

一、复验 5/5 全过,但埋了个雷

先把前一天的报告逐条实拉,核验清单 5/5 通过:

  • FACTOR_IC_LONG_N/SHORT_N 默认 "1" 的 env 开关,落地在 smart_money_signal.py:246-247,机制同 FACTOR_IC_ENABLED;
  • footgun 守卫生效:short_slice = ranked_asc[-sn:] if sn > 0 else [],SHORT_N=0 时不再全选 12 币;
  • py_compile 通过;
  • 门控四态复测:(1,1)/(0,1)/(1,0)/(0,0) 用真子进程导入模块复测,四种状态全对;
  • live DB 旁证:LONG 6 笔 +$2.74 / SHORT 11 笔 +$5.35,合计 17 笔 +$8.08。

这些全对之后,我又补了一个运行态洞察,反而发现一个隐蔽坑:**FACTOR_IC_* env 只在进程 import 时解析,现跑的 signal PID 85884 还是旧代码,但因默认 1/1 等价所以零差;而 watchdog 拉起 signal 时启动命令里没带 FACTOR_IC_* env,意味着将来跑 SHORT-only 对照时若被 watchdog 接管重启,会回退到 1/1 默认。** 这个专门记了下来。

二、动手前先实拉进程态:旧代码还在跑

既然决定发令“带 FACTOR_IC_LONG_N=0 重启 signal + 同步 watchdog 启动命令”,就不能凭记忆动手。先实拉当前进程:

  • signal:PID 85884,已跑 64.8h,启动命令里无 FACTOR_IC_* env——跑的是旧 1/1 默认,必须重启;
  • watchdog:PID 66160(--loop,42.9h)——改了 WATCHED 必须杀掉重启才能生效;
  • factor_monitor / trader / oos / sim_loop / mc_paper 均无关,不动。

又确认一遍 env 读取方式:FACTOR_IC_LONG_N=0 会被 int("0") 解析为 0,LONG 腿关闭,没问题。项目里已有 relaunch_daemons.py / relaunch_clean.py,用 psutil 杀旧 + start_new_session 单实例拉起,我决定沿用这套范式,不自己另造一套导致重复实例。

三、根治方案:改 watchdog 启动命令,而不是只重启 signal

如果只带 env 重启 signal,下一次 watchdog 因故障把 signal 拉起来时,又会回到默认 1/1——A/B 对照会被污染。所以正确做法是同步改 watchdog.py 里对 signal 的启动命令,内联 env 注入。

具体改法:在 watchdog.py 的 signal 启动命令里,把 set FACTOR_IC_LONG_N=0&&set FACTOR_IC_SHORT_N=1&& 内联进去,写法与 trader 的 set HL_* 同款 cmd /c 注入。改完 py_compile 通过。

这样以后无论是手动拉还是 watchdog 拉,signal 都是 SHORT-only,不会再回退。

四、干净重启与三方核验

用 _tmp_relaunch_shortonly.py(沿用 relaunch_daemons 范式)执行重启:

  1. 杀旧 signal(85884) + 旧 watchdog(66160);
  2. 先拉新 signal,启动命令带 FACTOR_IC_LONG_N=0 FACTOR_IC_SHORT_N=1;
  3. 再拉新 watchdog,它看到 signal 已存在就不会重复拉,避免竞态。

重启后的核验不靠“我觉得”,全实拉:

项 结果
signal 实例数 1(PID 75444)✅
signal environ FACTOR_IC_LONG_N=0 / FACTOR_IC_SHORT_N=1 ✅(psutil 实读进程环境)
watchdog 实例数 1(PID 58604)✅,无重复
live import 铁证 独立子进程带 FACTOR_IC_LONG_N=0 导入模块,读出 FACTOR_IC_CONFIG["LONG_N"]=0,门控 LONG=[] SHORT=[11] ✅
watchdog 启动命令 实拉 watchdog.py:52,确认内联 set FACTOR_IC_LONG_N=0&&set FACTOR_IC_SHORT_N=1&& ✅

日志里那行 ARB LONG z=+2.32 是 cvd_momentum 横截面排名(另一条 signal 路径),不受 factor_ic 的 LONG_N/SHORT_N 影响,我们的改动只门控 factor_ic。所以核验结论是:factor_ic 在 live 进程里确为 SHORT-only,且 watchdog 后续拉起不会再回退。

五、落盘与回退步骤

这一步不能只做不记。我把这次状态变更落盘:

  • A/B 状态从“待办”改为“进行中”;
  • 回退步骤也写了:如果 SHORT-only 对照要停,把 watchdog 内联 env 改回 LONG_N=1 SHORT_N=1,然后杀 watchdog 和 signal 重启,或者直接杀 watchdog 再手动拉默认 1/1 的 signal;
  • 把“env 只在 import 解析、watchdog 不带 env 会回退”这个坑记进 MEMORY.md 已知坑,下轮直接避开。

遗留

SHORT-only 对照刚起步,数据还没攒够。下一步等 OOS 变厚(建议 ≥30 笔回测窗口)再跑 _tmp_xs_pair_bt.py 一键复核;$80 验证仓 继续不放大,edge regime 依赖维持不变。信号这边暂时不用再动,等发令。