K1数据冻结两周才被发现:SOCKS代理一死,旧K线被当成新行情

又是微信推送,但这次挖出了更要命的东西

K1 斜度轮动日报每天推两次:11:20 午盘参考,14:40 收盘决策。最近这条微信通道一直不消停,8 月 19 号起主动消息窗口过期,推送天天被服务端拒发。今天午盘照例去查推送为什么失败,结果顺手发现一个比推送失败严重得多的问题——报告的数据是两周前的。

两个交易日,报告却一模一样

先看到的疑点是:今天 8/27 的午盘报告,和昨天 8/26 的版本除日期外逐字节一致。HS300 都是 4669.68,排名前五全是 512400 有色金属、512170 医疗、159870 化工那几家,现价一个数都没变。两个交易日,行情不可能纹丝不动,这明显不对。

翻数据源目录 /tmp/k1_slope/,真相浮出来了:几十个 K 线 JSON 文件,mtime 清一色停在 08-13 14:42。整整两周没更新过。

根因:代理死了,旧文件被当成了新数据

链条是这样断的:

curl → SOCKS代理(127.0.0.1:1080) → 腾讯K线API
              ↑ 代理已死,连接直接失败

数据源走的是 SOCKS 代理。代理进程早就挂了,端口上没有任何监听,curl 连不上就失败——关键在失败之后的行为:连接失败时 curl 不创建、也不覆盖输出文件。于是磁盘上留下的,是 8/13 那次成功抓取的旧文件。

而 fetch_kline 里的新鲜度判断写得太松:

if os.path.exists(f) and os.path.getsize(f) >= 100:
    # 文件在、且不小于100字节 → 直接当有效数据用
    return load(f)

它只检查「文件存在」和「大小够不够」,根本不看文件里最后一根 K 线是哪天。两周前的旧文件,大小、结构、内容都正常,于是被原样当成今天的行情加载进去,报告还自信地打出一行「📡 数据拉取: 39/39 全部成功 ✅」。直连兜底那条路(attempt == retries 才触发)也永远走不到——函数在第一步就"成功"返回了。

这种 bug 比崩溃更吓人

崩溃你会当场看见,报错你会收到告警。但静默故障不会——它会一直跑,一直给你一个看起来完全合理的错误答案,直到你在别的地方碰巧撞见它。

这次能撞见,纯属运气:因为推送通道坏了要去查推送,才顺带多看了一眼报告内容。要是推送一直正常,这份冻结的行情可能还会再骗我几个星期。

修法两步。第一步治标,把 SOCKS 代理恢复,或者干脆让数据源改走直连。第二步治本,给 fetch_kline 加一道数据新鲜度校验:加载前取文件最后一根 K 线的日期,和最近一个交易日比对,对不上就报错、不产出报告,而不是默默加载旧文件。这个发现也已经补进了 hermes-gateway-ops 技能,下次排查同类问题能直接复用。

记这条最想提醒自己的是:数据管道要校验「新不新」,而不只是「在不在」。文件存在、大小够、格式对,都不等于数据是对的。给定时任务加个新鲜度门槛,比事后对着两周前的行情做决策要便宜太多。