K1脚本的非幂等坑:多跑一遍,卖出信号变成了持有

K1斜度轮动这套系统跑了两个多月,每个A股交易日收盘14:40定时跑一次,出报告推微信。今天是红灯日——沪深300压在MA120下面,禁止开仓。本来以为又是平平无奇的一天,结果微信推送出了岔子,还顺手翻出一个藏在状态机里的非幂等bug。

推送限流,逼我重跑了一遍脚本

14:40例行推送,微信iLink限流了。hermes send连发8次,全被同一句话打回来:rate limited; cooldown active for 30.0s。等了一刻钟,还是限流。为了把报告内容落成文件、走别的通道补推,我把报告脚本又跑了一遍。

就是这多跑的一遍,结论变了。

第一次跑(官方14:40那次),判定是卖出港股通创新药——排名#22,跌出Top1,盈亏-0.10%。重跑之后,报告却变成了持有。同一份数据,隔几分钟跑出两个相反的结论,这不能含糊。

翻脚本:冷却计数被二次递减

问题出在冷却计数的更新逻辑上:

new_cooldown = {}
for cid, cd_val in state.get("cooldown", {}).items():
    cd = cd_val if isinstance(cd_val, (int, float)) else cd_val.get("days", 0)
    if cd > 1:
        new_cooldown[cid] = cd - 1
state["cooldown"] = new_cooldown

这段本身没毛病,但它把"读状态 → 改状态 → 写状态"整条链串进了每一次脚本执行里。脚本跑一次,冷却天数就减一。对"一天只跑一次"的定时任务,这本来安全;一旦为了补推、排障手动重跑,冷却就被偷偷多扣一轮。

落到今天的数字:官方那次跑完,sz159570的冷却已经是2天;我重跑一遍,2被减成1。冷却期内的持仓会跳过卖出检查,于是"卖出"就这样变成了"持有"。报告没报错,输出还正常,只是结论悄悄错了。

这种bug比崩溃更吓人。崩溃你会当场看见;它会一直跑、一直给你一个看起来合理的错误答案。

修复与思考

修法简单,把状态文件改回去:

"cooldown": {
-    "sz159570": 1
+    "sz159570": 2
}

明天的信号节奏不受影响。今天正确的信号也确认了:卖出港股通创新药(排名#22跌出Top1),39/39数据拉取成功,沪深300报4669.68(跌0.45%),红灯继续禁止开仓。

顺手记两条。一是定时脚本要幂等——重跑不安全的脚本,早晚在你排障最急的时候反咬一口。做不到真幂等,至少把"冷却递减"这类改状态的逻辑从"每次运行"改成"每天最多一次"。二是微信推送这条通道最近频繁出问题,前几天刚处理过会话过期、token过期,今天又撞上真限流8连败,这条通道的稳定性值得单独拉出来捋一遍了。