Judgment, Oversight & Execution
一个五角色的 AI 交易台,用真实行情跑一个纸面账户。这个月它第一次算出自己的 alpha,发现自己落后 SPY 129 个基点,并为这个数字暴露出的问题交付了四十项修复——回测引擎、带外急停开关、哈希链日志、按证据淘汰信号,以及给自己研究循环加上的多重检验校正。24/7 跑在一台 Synology NAS 上。
分享这篇案例 →难点在于:硬性风控写在模型改不了的代码里,还有一个自我提升循环——同一个错误反复出现之后,会从一条教训升级成写死在代码里的硬性拦截。
APScheduler-based; wakes each role on a market-driven schedule. This is the scheduler that actually runs the desk in production — distinct from the bounded ReAct orchestrator, which is built but has never been promoted past shadow mode.
Python, Postgres, Alpaca, DeepSeek, Qwen, Video Factory LLM gateway
一个基于 APScheduler 的编排器按行情节奏唤醒每个 agent。分析师在美东 08:30 用质量档运行——重推理、每天一次——读隔夜新闻、宏观日历和自己过去的教训,写出盘前简报。交易员由一套逐分钟的频率解析器唤醒:开盘后约每 5 分钟一次,盘中每 25 分钟,尾盘每 12 分钟,另有一条独立的、每 8 分钟检查一次持仓风险的线——它用 fast 档决定开平哪些标的、多大仓位、止损设在哪。评估者在中午重新审视每个持仓是否还符合当初的逻辑。复盘者在收盘时运行,驱动下面的自我提升循环。只有交易员能真正下单。
挑战者角色坐在任何有实质影响的开仓之前,作为对抗性的第二意见:它是独立的一次调用、独立的一套提示词,用的还是更重的档位,而不是同一次运行里顺手带出来的「第二意见」。这个月的审计发现它一次都没触发过。它的「是否重要」判断从 action 里读订单数量——而交易员自己的提示词明确禁止模型输出这个字段——所以这个判断每次都看到「不重要」,与此同时配置开关显示安全层已经启用。这类缺陷只有在你去找「什么东西没有发生」时才会浮出来,这也正是角色运行次数要放进公开决策单的原因:它今天有没有触发,你可以自己看,不用听任何人说。
每一个交易决策函数都是纯函数——零 I/O、零副作用、可完整单测。52 周新高动量信号(引 George & Hwang 2004)无论候选是否通过,都返回全部诊断字段,所以不存在「黑箱式拒绝」。核心仓再平衡器同样是纯的:没有 I/O、没有 LLM,只输出一张订单清单交给薄薄的外壳去执行。实际效果是:大约九成的交易逻辑在不接券商、不用 mock 的情况下就被单测覆盖,而且测试过程中不存在误下真实订单的风险。
交易员是五个 agent 里工具集最丰富的一个,但无论模型怎么判断,都要受几道确定性门禁约束。一套冷却机制防止它在连续几轮里反复交易同一只股票——开仓后 120 分钟,评估后未行动 40 分钟,而且冷却只会延长、不会缩短。一个退出管理器给每笔平仓打上标准化原因,并把持仓超过约 30 个交易日的标记为「逾期」——这是 Triple Barrier 方法里「时间屏障」的纯函数实现,阈值从 48 小时上调过,因为实测下来它要等的漂移就是这么长。这两道之上还有一个每日熔断:组合当天回撤达到或超过 2%,当天彻底停止开新仓,没有例外。如果分析师的盘前简报缺失或过期,交易员会硬性拦截新开仓,而不是蒙着眼交易。
其中四道门禁是这个月新增的,每一道都替掉了一个「看起来像门禁」的东西。带外急停开关可以从 agent 循环之外叫停交易台——在这之前,唯一的刹车是 LLM 自己写下的风险姿态,那根本不算刹车。数字论断接地检查现在会记录一条决策的理由里的数字是否真的追溯到证据,而不是只检查「有没有填证据字段」——它已经建好、在影子模式下量过,但还不允许它拒单。仓位计算被重建,因为一次线上实测显示 Kelly 乘数塌缩到 0.0057,把每一笔都压成同一个约 500 美元的仓位:最高信心和最低信心下出的单完全一样。
组合被拆成两个机械上独立的仓。核心仓(SPY 70% / QQQ 30%,占净值 65%)跑零 LLM 的漂移带再平衡——纯数学,偏离超过 8% 才下单。卫星仓是剩下的约 35%,完全由 LLM 驱动,走网关的 fast 档,而这一档优先用自托管模型。这保证了就算卫星 agent 的推理彻底跑偏,核心仓也纹丝不动、依然跟着市场。资本保全是被架构进去的,不是写在制度里的。
每个交易日结束时,复盘者计算盈亏归因、在交易之间找规律,把带日期和 ID 的教训写进日志——提交并推到 Gitea,没有向量库、没有 RAG;git 历史就是记忆系统。这本日志现在是哈希链的,所以它的不可篡改来自结构,而不是来自有没有人认真读 diff。
这个循环以前就到此为止,而那正是它的弱点:一条教训能影响明天的简报,却没有路径变成一条被检验过的论断。现在它闭合了。有了回测引擎,一个风险参数可以从一次运行里论证出来,而不是从一次对话里——在此之前,风险配置里的每一个值都是「讲道理讲进去的」,从没被量过。信号评估循环会同时搜索大量假设,现在带上了多重检验校正,因为这么宽的搜索总会找到点什么。信号可以退役:以前有十一个信号不管证据如何都能永生,现在它们的衰减由实际预测结果驱动。
循环也仍然把教训升级成代码。在若干次「在 20 日均线下方开仓然后亏损」被记录之后,这条阈值不再是「要记住的注意事项」,而成了风险模块里的一条强制规则,并附带一处有文档的例外:如果某个策略的入场条件本身就是这条均线,它仍可以在下方开仓,但仓位减半。从「我们注意到这里出过错」变成「代码把这件事算进去了」。
Python、DeepSeek API(质量档与 fast 档,经由共享 LLM 网关)、Alpaca、Finnhub、EDGAR、pgvector、APScheduler、Docker、自建 Gitea Actions CI/CD。