记录一次重连风暴的排查(2026-09-09)
记录一次重连风暴的排查(2026-09-09)心跳超时说的从来不是"连接断了",而是"没人告诉它还活着"
smart_money_trader 的日志里堆着几千次心跳超时。表面看是网络不稳,但一台机器如果真烂到每两分钟断一次,它早该被停掉了——交易进程却一直在跑,仓位也没乱。所以这次不猜,先把它拆开看。
这一天的活,从头到尾只有一件事:把"到底是谁在超时"查清楚。 修不修得好是第二步。
先看清风暴的形状先把原始日志预解析了一遍,而不是凭印象判断。
reconnect_preanalysis.json 记录的这场风暴,时间跨度是 2026-08-18T22:00:00 到 2026-09-05T13:57:21。进程侧的 _HEARTBEAT_TIMEOUT 是 120.0 秒,stall_profile.jsonl 里的分析确认了一件事:重连的节奏大约是 每 130 秒一次——正好对上"超时阈值 + 重连周期"。
预解析里还埋着一个更早的坑。第一次统计出来的心跳数是 4355,但这个数字是错的:它把 47 ...
记录一次依赖的坑(2026-09-07)
记录一次依赖的坑(2026-09-07)报错写的是模块名,坑往往在环境里
那天的两件活都不大,但都属于同一类:代码没写错,环境却有缺口。一个是 Streamlit 看板少了 pymysql,一个是本地 workbuddy-delegate 的 wb.py 有两个"改了代码却没更新元数据"的 bug。都不难,但都容易只看表面就修错方向。
一、两个页面挂了Streamlit 看板跑在 8501 端口,页面 5(views.5_market_mood)和页面 14(views.14_new_tokens)直接报 ModuleNotFoundError: pymysql。
看板是给交易系统做可视化的,页面挂了不会影响交易本身,但会影响"看"——而看不了,等于把判断系统状态的眼睛蒙住一只。所以这类问题优先级不高,但不能拖。
二、报错指向模块,坑在环境第一反应很容易是"去改代码、加个 try 兜住"。这是错的。报错已经明确说是缺模块,那就去装模块,而不是改代码绕开它。 尤其当缺的是 pymysql 这种公开的包——绕开它,只会让下一次跑 ...
记录一次夜里的异常(2026-08-23)
记录一次夜里的异常(2026-08-23)一晚上三条"死因",长得像同一个症状
有些夜里,你会以为在处理一个 bug,其实是四个。那天从 OOS 停写查起,查到最后发现:一条 3 天的数据空洞、一个设错位置的超时、一笔拖了 4.6 天才被硬止损的仓,还有一行缺了 global 的赋值。它们互不相干,却在同一晚一起发作。
一、停写三天的 OOS先看最表面的:factor_ic_oos.db.oos_ticks 从 08-20 18:08 之后就再没写入了,空了 3 天。
runtime_state 给出了另一半答案:next_run_at=None,last_run_at=1787220565(≈08-20 18:08)——调度器在那之后就没再排下一次运行。automation 配置本身还是 ACTIVE,状态显示正常,那只是假象。
代码侧的根因,是 compute_regime()(L177)里的 fetch_candles 没有任何 try/except:
沙箱/网络异常 → fetch_candles 抛异常 → 冒泡到 main()(无 ...
记录一次模拟盘的排障(2026-07-10)
记录一次模拟盘的排障(2026-07-10)模拟盘最坑的地方,不是它亏了钱,而是它让你以为策略已经能跑了
这天打开 http://0.0.0.0:8501/,主流币模拟交易那条曲线几乎全是红的。接下来的几个小时基本都在同一个循环里转:发现一处数据不对,翻代码,改掉,重启,然后发现下一处不对。
下面按当时踩到的顺序记。
第一次运行就崩maincoin_paper.py 里 scan_signals() 返回的是 dict,但 run_tick() 全程用属性访问:sig.confidence、sig.signal.value。所以第一轮 mc_paper_loop 一起来就崩了。
改成 sig["confidence"]、sig["signal"],顺手删掉没用的 Signal import。这个 bug 的性质其实很典型——函数签名和调用方之间没有契约,写的时候两边各自都对,合起来就炸。
开仓时间和平仓时间一模一样翻交易记录时发现一件事:每一笔的 opened_at 和 closed_at 是同一秒,大量交易 duration=0s。
根因在 ...
记录一次看板数据丢失的修复(2026-06-15)
记录一次看板数据丢失的修复(2026-06-15)数据"消失"的时候,先看它是被删了,还是根本没写进去
TVL 看板某天开始是空的。不是加载慢,是图表直接报错,一张干干净净的空白。
这天把这块看板修回来。
一块空的图页面 pages/1_tvl_rank.py 里的 Plotly 柱状图,x 轴取的是 protocol 字段:
1px.bar(x='protocol')
它读到的是一个空列,于是图表报错、数据全丢。
前端报错看着像渲染问题,但真正的疑点应该往后推一步:数据是什么时候没的?是同步进来的数据本来就空,还是本来有、后来被写没了?
根因:往一个不存在的列里写查下来,问题在写入端,而且比想象中更基础。
tvl_rank 表原本就没有 protocol 列。而 save_tvl_rank 在做 INSERT 的时候,把协议名写进了这个不存在的列——结果数据全丢。
同时,页面又反过来用 px.bar(x='protocol') 去引用这个空列,于是一起报错。
这条链子很典型:写入端写了个不存在的目标,读取端读了个空的结果,两 ...
记录一次回测链路的抢修(2026-06-10)
记录一次回测链路的抢修(2026-06-10)一个数字离谱到没人信的时候,问题多半不在那个数字上
新做的 12 号页跑完之后,v1.16.4 的 BUY 数量是 15254。而同一时间,11 号页里真实成交只有两百多笔。
差了将近两个数量级。这天做的事就是把这个数字修回来。
一张对不上的表:11 页 215,12 页 15254起因很朴素:11 号页显示"买的币很少",12 号页却显示买了 15254 次。策略筛选出要买的币本来应该非常少,两个页面却给出了完全不同的结论。
这里先要分清两个页面在干什么。11 号页读的是 sim_trades,真实跑出来的成交;12 号页是反事实回测,用历史数据按每个版本的规则重放一遍。回测的意义在于"把历史数据和样本重新过一遍,快速筛出最近真正赚钱的策略,顺带看出优化方向"——它能跑出的笔数本来就该和实盘一个量级,绝不该多出几十倍。
15254 这个数字本身就在喊"我不对"。
根因一:combo 的 max() 被避雷策略钻了空子第一层根因在 engine.py 的 score_combo( ...
记录一次「0 BUY」的归因(2026-06-06)
记录一次「0 BUY」的归因(2026-06-06)系统在跑,日志在滚,但一笔交易都没发生
这是做交易系统时最让人心慌的一种故障:**它不是崩了,它是"活着但不动"**。
v1.8 部署上去两个小时,0 BUY、0 SELL。进程在,决策在写,就是不下单。
四层诊断:把"不动"拆开面对这种情况,最容易犯的错是直接去怀疑策略——是不是阈值太高了?是不是信号太差了?然后开始降阈值。
但那天没有先动手改参数,而是先做了一次分层排查。因为"0 BUY"这件事,可能在四个完全不同的地方断掉:
层
卡住的表现
1. 策略层
headless 跑出来的 want 比例过低
2. 风控层
策略想买,但被 _risk_check_buy 拒了
3. 仓位层
想买,有额度,但拿不到仓位
4. 持久化层
链路断了,数据根本没进来
排查结果:
第 1 层——健康。 headless 跑 sol + bsc + base 共 120 个 token,**111 个 want=True,通过率 92.5%**。策略评分没有任 ...
记录一次黑窗口的排查(2026-06-01)
记录一次黑窗口的排查(2026-06-01)如果你发现自己在打补丁,先退一步:上游修了吗
那天要解决的毛病很小——小到可以忍,但每次都在提醒你它的存在。
那个一闪而过的黑窗口现象是:每执行一次终端命令,屏幕上就闪一下黑色的 cmd 窗口。
它不影响结果,命令照跑,输出也正常。但一天下来闪几百次,人会烦。更要命的是它给人一种"这东西没做干净"的感觉。
查下来是本地装的 Hermes 0.14.0(v2026.5.16,2026-05-18 发布)的一个已知 bug:Windows 上每次调用终端都会弹一个控制台窗口。
先查上游,再动手改第一反应当然是"把弹窗的地方关掉"——找到 Popen 的调用点,加个 Windows 专用的隐藏标志就行。
但我先多做了一步:去看看上游有没有修。
结果很干脆:这个 bug 在 0.15.0(v2026.5.28)里已经修了,做法是加了一个 windows_hide_flags() 的辅助函数。而当时 PyPI 上最新是 0.15.2(v2026.5.29.2)——本地落后了整整两个补丁版本。
那正确的修法就只有 ...