记录一次 factor_ic 切 SHORT-only 的干净重启(2026-08-10)
记录一次 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 范式)执行重启:
- 杀旧 signal(85884) + 旧 watchdog(66160);
- 先拉新 signal,启动命令带
FACTOR_IC_LONG_N=0 FACTOR_IC_SHORT_N=1; - 再拉新 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 依赖维持不变。信号这边暂时不用再动,等发令。