BinaryBlogBUILD

BUILD · M / W / F

量化实验室系列 * 闪崩实验室 2

Editorial image for 量化实验室系列 * 闪崩实验室 2

一个值得反复思考的观点。

游泳池

2026 年 3 月 10 日,我做了一个糟糕透顶的决定:我打开了生产环境。

公平地说,当时一点也不觉得糟糕,反而觉得壮丽极了。窗外,香港的天际线照旧带着不知疲倦的能量嗡嗡作响;窗内,一台 iMac 稳定地发着光,照亮了我几乎凭空造出来的东西。十二天前,我手里只有一个空白文件夹、荒废了二十年的软件直觉、一个老交易员对市场结构的本能,以及一个名叫 Codex 的 AI 编程代理。现在,完全出乎所有理性预期,我居然拥有了一间能运行的量化交易实验室。

屏幕背后,是整整七年的比特币逐笔历史数据,经过细致回放和压力测试。最后一次研究运行里有九十七笔模拟交易,由一套确定性策略串联起来,累计收益高达七十九 R。沙盒像一座墓地:埋着失败的假设,也留下幸存的参数、执行日志、单元测试、标注图表,以及名字越来越郑重其事的文件。这个系统被我亲切地叫作 The Alligator(鳄鱼)。它通过了我能想到的每一场统计考试、蒙特卡洛模拟和常识检查。于是,我自然得出了一个结论:该让它拿真钱去捕猎了。

这套逻辑,大概和那些在郊区游泳池里造精密帆船的爱好者一模一样。沙盒里有水,船也浮起来了。还能出什么问题呢?

生产版本看起来确实威风凛凛。它不再只是终端里的一段脚本,而是一个生态系统:调度器、后台看门狗、基础设施心跳、结构化日志管道、交易所直连、本地持仓对账,以及带止损和止盈管理的自动风险参数,一应俱全。它甚至还有一套 Telegram 驾驶舱,配备 TOTP 保护的多因素认证,让我能直接从手机远程查看、武装,或者对这只自主行动的加密捕食者按下 kill-switch。

我可以输入 /status,问它还在不在呼吸;发送 /kill,立即禁止新开仓;再发 /unkill,允许它重新潜伏在订单簿里。我对这个功能的喜爱明显超出了合理比例。离开严肃软件工程二十年后,我走在中环街头,口袋里揣着一只算法爬行动物的遥控器。年轻时学计算机的我一定会被迷得神魂颠倒;如今这个年纪、坐在管理层位置上的我,大概应该深感不安。

最初什么戏剧性事件都没有发生,而这恰恰成了最煎熬的部分。鳄鱼本来就是个耐心的猎手。它不靠噪音交易,也不会每小时翻来覆去地刷合约。它等待非常精确的条件:系统性疲弱、杠杆过度拥挤、结构耗竭,以及毫不含糊的破位。它可以一动不动地趴上好几天。

所以,我的实盘量化入门并没有肾上腺素飙升的剧情,只有催眠般单调的系统日志。

绿色。

绿色。

绿色。

还是绿色。

没有信号。没有持仓。没有剧情。我花了将近两周造出一台复杂机器,而它最突出的运营才能似乎是克制地什么都不做。这是数字自制力的胜利。

一家微型夜班公司

然后,现实不可避免地介入了。一个下午,输入数据流里一段至关重要的管道悄无声息地停住了。未平仓量不再更新。策略高度依赖未平仓量;杠杆拥挤是必须同时满足的条件之一。没有新鲜的未平仓量,鳄鱼仍然看得见价格和资金费率,却等于失去了嗅觉。

主进程还在运行,信号引擎仍然活跃,后台调度器甚至报告一切健康。然而,支撑判断的一个核心输入已经悄悄冻结。生产环境第一次让我认识一种阴险的软件故障:没有明显崩溃,没有抛出异常,也没有满屏刺眼的红色警报。机器只是变得没那么真实了。

我们钻进管道深处,排查端点、缓存层、数据新鲜度时间戳和底层网络延迟。一度,区域 VPN 成了头号嫌疑人。iMac 在香港,执行服务器从别处路由,一个 IP 地址悄悄变了,第三方数据提供商也开始用不同方式返回缓存响应。我盯着网络追踪图直摇头:我明明是来研究比特币的宏观市场级联,为什么下午却在调试国际网络拓扑?

鳄鱼对我的存在主义困惑毫无兴趣。它只想要新鲜的未平仓量。我们修好管道,恢复数据完整性,看着数字重新跳动起来。那只动物又开始呼吸,而我也少天真了一点。

接着,执行调度器开始行为古怪。沙盒里的研究脚本很听话:你触发它,它按顺序执行,然后干干净净地结束。生产软件却有复杂的社交生活。它按时钟周期自己醒来,和陌生 API 交谈,把持久状态写入本地数据库,轮询外部进程,处理动态重启,还会留下一些微妙的数字痕迹。偶尔在凌晨三点,它会断定旁边的后台进程已经死了,其实对方只是在喘口气。

为了解决这个问题,我们引入了调度器;然后又造了看门狗来监视调度器;接着发现看门狗需要一套专门的心跳,才能分清调度器是真的死了,还是只是在安静工作;最后我们又花了几个小时调心跳阈值,因为一个过分热心的看门狗,可能比它负责监视的进程危险得多。

到了某个时刻,我意识到自己无意间造出了一套微型数字官僚机构:调度器负责干活,看门狗负责监督调度器,心跳负责证明调度器确实在干活,Telegram 向高层汇报状态,而所谓高层,就是半夜躺在床上盯着 iPhone 的我。它已经不太像高频交易台,更像一家只在深夜营业、员工全部是过度紧张微服务的小公司。

不过到此为止,我的本金一分钱都没动。

5 月 18 日

直到 5 月 18 日。

那天下午,鳄鱼终于闻到了气味。一个真正的信号出现了——不是 CSV 文件里的历史回放,也不是沙盒试跑,而是波动剧烈的比特币市场里,一个真实的生产级机会。安静监控了几个星期后,多层条件终于完全对齐:市场状态确认、结构脆弱性,以及价格触发。大嘴张开了。

就是这一刻:第一次完全自主的开仓。

订单立刻被拒绝。

我难以置信地盯着控制台。鳄鱼准备了两个月的晚餐,宴席摆到面前,却连叉子都塞不进嘴里。系统实际上拦住了自己的交易。

我的第一反应是常见的技术摩擦:API 权限不对、端点超时、合约格式错误,或者少了一个名字很神秘的交易所参数,比如 tdMode。Codex 和我立刻钻进原始交易所日志,逐行追踪请求载荷。

然后,数学上的真相浮出了水面。策略的风险引擎被设定为每笔交易承担固定金额的风险,大约 5,000 美元。纸面上这完全合理。但我在急着从研究转向生产时忽略了一件事:风险预算名义持仓规模 根本不是同一个东西。

当时的市场结构要求初始止损放得非常紧。止损越窄,要在被止损时恰好损失固定金额,底层持仓就必须越大。紧止损叠加固定美元风险,算出来的就是巨额名义杠杆。

我看了看计算出的下单规模,又看了看交易所允许的最大杠杆,再回头看那只机器人。这个算法认为,我区区 5,000 美元的风险预算,足以配上一家小型对冲基金的资产负债表。

幸运的是,有人事先想到了这种场景。不幸的是,对我当下的交易雄心而言,那个人正是我自己。几个星期前,某种自保本能让我在执行层写死了一条绝不妥协的安全参数:系统严禁使用超过 交易所最大持仓上限的百分之七十

订单失败,不是因为平台坏了;它失败,是因为安全治理器完全按照设计工作。过去的 Robin 跳出来,保护了现在的 Robin,不受现在这个 Robin 自己那套不受约束的量化策略伤害。这个发现很好笑,也很让人泄气。

我们把安全缓冲原封不动地保留下来。没有情急之下强行绕过,没有提高杠杆参数,也没有仓促改配置。交易窗口过去,市场继续向前,鳄鱼依旧饿着。

一笔和自己意见不一致的持仓

几天后,市场又给了一次机会。这一次,计算出的仓位舒舒服服地落在全部风险参数之内,订单毫无阻力地进入订单簿。我们第一次真正拥有了一笔活的实盘持仓:真实交易所、真实比特币永续合约、真实本金、有效止损,以及明确的止盈目标。

我盯着屏幕,心里一半是职业上的满足,一半是深深的怀疑。机器终于跨过了理论模型与经济现实之间的边界。几个月来,每一笔交易都活在历史图表和模拟成交构成的无菌、无摩擦世界里。现在,一笔仓位真的待在野外,要接受现实世界的处置。

确认订单参数已经稳定后,我去睡觉了。

第二天早上,我打开仪表盘查看持仓,立刻感到一阵发冷。不对劲。本地数据库说的是一种状态,交易所报告的却是完全不同的状态。很少有事情比发现自己的自主交易脚本可能根本不知道自己目前持有什么,更能让人瞬间清醒。

我直接打开 OKX 交易所页面,调出机器人原始日志,对照 Telegram 事件流,开始逐一核对时间戳。交易所里的物理持仓的确存在,但本地记账模块被一个意外的执行状态彻底搞糊涂了。

我人工介入:关闭未平的交易腿,取消失去归属的保护订单,再发出远程 kill 指令,阻止任何新开仓。随后,Codex 和我进入了渐渐变成晨间例行公事的环节:数字取证。机器人以为发生了什么?交易所实际执行了什么?状态从什么时候开始漂移?本地磁盘写下了什么?订单簿里又究竟成交了什么?

策略在理论上有多优雅已经不重要了。唯一重要的问题是:两个独立的计算机系统,能否对“我此刻到底拥有什么”保持绝对共识。

我们从头重建状态对账模块,强制每笔交易之后都做同步状态检查。但生产环境还没上完课。很快,它又暴露出一种更微妙的结构摩擦。

鳄鱼可以在同一段趋势里产生多个逻辑开仓。在回测环境中,这些开仓被当作完全独立的交易:交易 A 有自己的入场价和保护止损,交易 B 也有独立逻辑、风险配置和退出触发。策略回测器分别追踪它们,交易日志分别记录它们;概念上,它们是不同的实体。

交易所的现实却简单得多。账户采用净持仓模式。从交易所看来,同一个合约连续做两笔空单,并不是什么“两件不同的哲学事件”,而只是一笔合并后的空头持仓:总规模相加,入场价格取一个加权平均。

我们造了一套优雅的内部本体论。交易所只做基础算术。

我记得自己盯着屏幕想:不,这明明是两个完全不同的交易想法。

交易所 API 仿佛回答:真可爱。现在它们是一个了。

我们只好再次适应,重新设计执行架构,把多个策略层的逻辑持仓持续映射到交易所层的一笔合并持仓上。本地状态可以保存三个独立的交易概念,交易所则只保留一个净持仓,而两种表述必须始终保持对账一致。

日子一天天变成几个星期,生产环境系统性地拆掉了我们在沙盒里习以为常的每一条假设。止损单可能存在于本地内存,却因为速率限制没能登记到交易所;持仓可能已经成交,配套保护订单却还没确认;一个有效信号可能用了稍微延迟的数据点;看门狗可能把短暂的网络沉默误判为系统死亡;交易所也可能拒绝一个数学上完全正确的订单,只因为合约张数、保证金模式或步长取整规则根本不在乎回测有多漂亮。

现实一层层挤进系统。它并没有带来更复杂的数学参数,而是要求更严格的运营纪律。我们加入 API 预检、明确的订单回读、失败即关闭的安全流程、防御性错误处理、更细的健康状态,以及不可变事件账本。软件在纸面上没那么优雅了,在实践中却坚韧得多。我不再追逐刺激的新功能,反而开始从安静、无聊的状态日志里获得巨大安慰。绿色成了世界上最好看的颜色。

T008 在咬桌子

然而,系统在机械层面越来越可靠时,另一个领悟开始浮现。它和软件无关,而和交易本身有关。

鳄鱼会发起一场战役。第一笔通常很漂亮,正好抓住破位的拐点。可价格继续下跌时,后续信号也会一个接一个触发,不断往仓位上叠。回测认可这种做法:历史上,次级入场的期望值仍为正。数学上,它说得通。

可是亲眼看着它在实盘里发生,视觉和直觉都越来越觉得鲁莽。

有一场行情把问题暴露得格外清楚。第一笔空单抓住了一段巨大、干净的向下扩张,是教科书式的交易。第二笔在更低处进场,第三个信号又在更低处触发。等第四笔空单几乎在行情最底部提交时,它看起来已经不像经过计算的统计优势,更像一个忘记了“系统性捕猎”和“盲目追价”根本不同的算法。

我调出执行图,盯着那一簇标记:T005T006T007T008

我拿起便笺,写了四行:

T005 是咬下去。

T006 是咀嚼。

T007 是追着吃碎屑。

T008 是在咬桌子。

这句话好笑到让人过目不忘,但背后的教训很严肃。第一笔入场抓住了真正的市场结构变化;最后一笔只是对已经耗尽的动量作出反应。算法把每个有效触发都当成彼此独立的机会,而我的眼睛看到的却是同一场延伸过度的战役:主要扩张早已完成,只剩一只怎么也不肯离开餐桌的动物。

开放海域

到了暮春,鳄鱼已经彻底变了样。它最初只是一个干净研究文件夹里的一组优雅方程,如今却带着实盘部署留下的一身伤痕。

数据管道曾经变旧;区域 VPN 迫使我们研究地理位置;调度器需要看门狗,看门狗又需要心跳;我的手机多了一个远程 kill-switch;交易所拒绝过第一笔订单;百分之七十安全规则悄悄把我们从自己的杠杆数学里救了出来;一笔真实持仓暴露了状态同步缺陷;而交易所把我们精细的交易想法压扁成了简单的净持仓算术。

七年回测没有为这些事情中的任何一件做好准备,因为回测安稳地活在过去。生产环境则完全活在现在,每一个决定都会立刻产生后果。

深夜,又一张比特币图表在黑暗里发光。系统在后台安静运行,提交心跳、轮询 API,等待下一次结构变化。鳄鱼终于离开游泳池,进入了开放海域。也是从项目开始以来第一次,我意识到最重要的工程挑战,已经不再是教机器什么时候咬下去。

真正的挑战,是教会它什么时候停止进食。