从两篇 X 文章开始:我第一次构建量化系统的回忆录
时间范围:2026-05-12 至 2026-06-21
写作定位:这不是开发日志,也不是提交记录。它是一份面向分享的回忆录:记录一个交易想法如何从两篇 X 文章,变成一套越来越严肃、越来越不敢轻易相信自己的量化系统。
AI 协作说明:这份回忆录由 AI 协助我整理、追问和写作,用来记录我从交易想法出发,逐步构建、怀疑、修正一套量化系统的过程。里面的判断、取舍和反思来自项目推进中的真实证据与对话复盘,AI 主要承担梳理脉络、改写表达和补齐遗漏的工作。
更新原则:这份文档以后会不定期更新,但不会按“今天改了哪个文件”继续堆流水账。
每次更新优先记录四件事:
- 当时我以为什么是对的。
- 后来系统或数据如何证明我想简单了。
- 我做了什么决定,为什么没有选另一条路。
- 这个决定留下了什么结果、教训或新的风险。
具体代码、命令、回测数字仍会保留,但放在章节中的证据段或附录里。正文要尽量像一篇可以给别人看的长文,而不是一份工作汇报。
序章:最开始只是两篇文章
这个项目真正的开始,不是一个仓库,也不是一个数据库——而是我一次过于自信的“来都来了”。
它开始于 2026 年 5 月 15 日,我把两篇 X 文章丢给 Codex:
https://x.com/kovainvest/status/2055026277149741399
https://x.com/MMMusol/status/2054789628016931253
请你读一下这两篇文章,然后形成计划计划构建一套可执行的交易策略实现稳健盈利
当时我想要的东西其实很朴素:把别人总结出来的交易框架,转成一套我能执行、能回测、最好还能长期盈利的系统。换句话说,就是想让“灵感”学会按流程打卡。
第一篇文章偏进攻,讲的是成长股、CAN SLIM、相对强度、VCP、Pocket Pivot、止损和加仓。第二篇文章偏防守,讲的是牛市后段、估值、市场宽度、MA200、RSI 背离、成交量派发,以及 AI 和存储板块过热时的风险。
这两篇文章给了系统最早的骨架:强的时候进攻,弱的时候活下来。
但很快,第一个现实问题就来了:文章本身并不好读。X 页面公开请求不可读,短链展开后才发现是 X Articles,公开 HTTP 又返回 403。最后通过可用的 GraphQL / 登录态页面方式才读到正文。现在回头看,这个开场很有象征意义:我以为我要写策略,现实先让我学会“怎么把文章打开”。
我以为我要解决的是“如何写策略”。
后来才发现,真正一直缠着我的,是“如何取得可信的数据,如何证明自己没有读错世界”。
第一章:我第一次以为“回测出来了”就是进展
最早的策略框架很清楚:
- 市场强时,只买强势成长股。
- 没有 setup 时,不交易。
- 买错快速止损。
- 买对才加仓。
- 市场转弱时降仓或转现金。
- 所有交易必须可记录、可复盘、可回测。
这个框架本身没错。问题是,框架变成代码以后,任何一个细节都会改变结果。
当时我给出的执行约束是:
- 账户规模 4 万美元。
- 股票池先让系统自选。
- 尝试使用刚添加的 LongBridge MCP。
- 只做日线收盘信号。
- 先做回测,注意避免未来函数。
第一版实现很快写出来了。项目当时还不是 git 仓库,手边也只有一些零散的券商只读插件和行情脚本,于是我先把最小可跑的策略配置、筛选脚本、回测脚本和纸面脚本硬拼起来。当时可用的 LongBridge SDK 能跑起来,所以先走 LongBridge 日线,指数或失败项回退 Yahoo。
但如果只写成“新增了几个脚本”,还是太轻了。现在回头看,那个阶段虽然还远远称不上系统,却已经不是单文件原型,而更像一个用 Node.js 临时拼出来的微型交易工作台。
最早那套 Node 工具很快就被串成了一组可重复执行的命令:
strategy:regime负责看大盘环境。strategy:screen负责在观察名单里筛候选。strategy:backtest负责跑日线回测,也能切不同 universe。paper:run负责维护纸面状态、订单、权益曲线和同步说明。
也就是说,最开始的项目其实不是“先有一个大系统,再慢慢做功能”,而是反过来:先有一把零散但已经能工作的 Node 工具,再从这些工具里慢慢长出状态、规则、目录和边界。
那批脚本之间的关系也比“几个小脚本”更紧:
- 数据提供层先尝试走 LongBridge,失败或遇到指数就回退 Yahoo。
- universe 层负责 static 名单和 point-in-time 指数成分。
- 市场状态模块单独判断
Risk-On / Neutral / Risk-Off。 - 筛选模块用流动性、波动、相对强度和 setup 去挑候选。
- 回测模块真正把“日线收盘出信号、次日开盘成交”的执行语义、市场环境、相对强度排名、止损和仓位 sizing 串起来。
- 纸面模块则开始保存状态、日志、成交、权益曲线、订单和同步说明。
所以那时的项目形态其实很有意思:它既不只是几个脚本,也还不是一个严肃仓库。更像是一堆已经隐约在争夺“谁负责什么”的小程序,被交易想法强行推着往系统方向长。
第一版回测严格按一个原则执行:
T 日收盘生成信号,T+1 开盘成交。
这个原则很重要,因为它让策略至少没有用“当天收盘后才知道的信息”去买当天的开盘。
写完以后,第一轮结果出来了。
| 项目 | 数值 |
|---|---|
| 回测区间 | 2024-01-01 至 2026-05-14 |
| 初始资金 | $40,000 |
| 期末权益 | $100,389.44 |
| 总收益 | +150.97% |
| 最大回撤 | -25.93% |
| 交易次数 | 58 |
| 胜率 | 41.38% |
那一刻很容易兴奋:一个刚从文章里拎出来、还带着纸墨味的策略,居然跑出了 150% 的收益。
但我很快问了一个后来反复出现的问题:
这个结果到底有多少是策略,多少是运气?
答案并不浪漫:不是 100% 来自策略。更像是策略写了半份答卷,市场老师帮我把剩下半份也给批了个高分。
那 54 只股票是事后挑出来的热门成长股,里面有 AI、半导体、存储、核电、量子和高 beta 名字。2024 到 2026 又是非常适合动量成长股的市场。少数超级趋势股可以把整条权益曲线抬起来,但这不等于策略本身已经具备可复制的 alpha。
这是我第一次意识到,回测结果本身不是结论。它只是一个需要被审问的嫌疑人——而且还特别擅长在审讯室里讲好听的话。
第二章:为了去偏差,我开始扩大股票池
如果最早的收益来自“挑中了后来上涨的股票”,那就必须换一个更不讨巧的股票池。毕竟靠事后选股赢回测,跟考完试再选答题卡没什么本质区别。
于是下一步不是调参数,而是找数据工具——先把“我到底在回测什么”这件事搞清楚。
我测试过 OpenBB、yfinance、yahoo-finance2、dukascopy-node、index-constitution,也研究过 vectorbt 和 Backtrader。结果并不是“找到了一个万能框架”,而是更清楚地知道每个工具的边界:
- OpenBB 很全,但太重,不适合塞进轻量回测主链路。
- yfinance 可用,但更适合 fallback,不适合作为正式主源。
- dukascopy-node 更像外汇 / CFD / tick 工具,不适合这套美股日线策略。
- index-constitution 的 Python 包在本地不好用,但 GitHub 原始 CSV 可解析。
最后选择了一条更朴素的路:自己固定 NASDAQ-100 和 S&P 500 的历史成分 CSV,按日期解析 point-in-time universe。这样至少能避免用今天的成分股去回测过去。
这一段在代码层面其实也留下了很早的“系统意识”。当时的 universe 模块不是简单读一个股票列表,而是按 opt-in <= T < opt-out 去判断某只股票在某个信号日到底算不算 universe 成员。也就是说,在最原始的 Node 时代,项目就已经不完全满足于“把行情拉下来跑一下”,而是在逼自己处理过去和现在不应该混成一锅的事实。
这一步的结果立刻让第一版的光环变暗,像舞台灯突然打到后台:原来“看起来很强”也可能只是站位选得好。
| 股票池 | 终值 | 总收益 | 最大回撤 | 交易数 | 胜率 | 数据缺口 |
|---|---|---|---|---|---|---|
| 静态 54 股 | $100,389.44 |
150.97% |
-25.93% |
58 |
41.38% |
0 |
| NASDAQ-100 点时池 | $64,368.71 |
60.92% |
-22.76% |
67 |
38.81% |
67 |
| S&P 500 点时池 | $77,100.26 |
92.75% |
-31.93% |
64 |
45.31% |
175 |
我没有因此失望,反而觉得这是更可信的进展。
因为它说明两件事同时成立:
- 第一版不是纯粹靠未来函数或代码错误赚钱。
- 第一版确实含有明显的主题偏差、股票池偏差和数据缺口。
这是第二个重要转变:我开始不再只看收益,而是看收益在被去偏差之后还剩多少。
后来我问,胜率低是不是没有严格遵守策略。答案也很重要:不是。趋势突破系统本来就可能胜率不高,它的关键不是每一笔都准,而是小亏损可控,少数大趋势足够大。
从那时开始,我对“胜率”这件事不再那么执着。我更在意的是:输的时候是不是按规则输,赢的时候是不是能真的拿住。
第三章:纸面交易暴露了“两个真相”
回测能跑之后,我自然想让系统每天跟踪——人一旦尝到“自动化”的甜头,就很容易想把它升级成“每天都给我一个答案”。
于是项目进入 V2 阶段,出现了高 beta 金字塔纸面交易。这个版本回测接近 528.81%,看起来比前面的版本更有攻击性。它生成过两笔 LongBridge 模拟买入:
| 标的 | 方向 | 数量 | Setup | LongBridge 订单号 |
|---|---|---|---|---|
FIX.US |
Buy | 3 |
pocket_pivot | 1240354460052504576 |
MRVL.US |
Buy | 45 |
pocket_pivot | 1240354703062089728 |
那时候最容易犯的错,是把“本地策略状态”和“券商账户状态”混为一谈。就像把自己备忘录里写的“我有钱”当成银行余额。
本地纸面账户里可能显示我有 FIX 和 MRVL,也可能生成卖出计划。但 LongBridge 模拟账户只读检查显示实际没有对应持仓,于是卖单不能提交。
这件事非常具体,也非常关键。它没有什么高深数学,但能把人从幻觉里直接拽出来。
如果本地状态说“应该卖”,券商账户却没有仓位,那交易系统必须相信券商账户。不能为了让策略状态连续,就假装真实世界也连续。
而且那个阶段的“本地状态”也已经不是一句抽象的话。纸面模块真正在一套独立目录里维护自己的状态文件、交易流水、订单表、权益曲线和同步说明。它已经开始像一个小型纸面账户系统一样生活:会记住现金、持仓、pending orders、trades、runs 和 lastProcessedDate。这也是为什么后面的冲突会这么尖锐,因为你面对的不是一串临时变量,而是一套已经会积累记忆的本地现实。
后来系统里很多安全门都来自这个教训:
- 提交前必须读取券商余额、持仓和今日订单。
- 不提交本地凭空生成、但券商侧无法验证的卖单。
- 不发明订单。
- 只提交策略同步文件里明确列出的订单。
- 数量不清楚就不提交。
同一阶段还暴露了另一个更深的问题:Node 版 paper:run 和 Python 版 monitor 看起来像同一策略,但不是同一个执行引擎。
Python monitor 会先跑一整段历史回放,再打印最后一天 snapshot。输出里有历史收益、回撤、交易数,也有当前信号。这对于人来说很容易误解:我到底看到的是历史理论组合,还是今天应该下的单?
后来我把这个问题总结成一句话:
策略配置一样,不代表回测、监控、纸面交易是同一个系统。
这句话后来直接影响了 V6。一个系统只要有两个引擎、两个状态源,就会迟早产生两个真相——然后你会发现,最难的不是写代码,是决定到底该相信谁。
第四章:V6 看起来更强,但也更危险
5 月 19 日,我发现另一个会话里已经出现了一套新的 V6 形态。那种感觉有点像:你还在修补一辆自行车,转头发现自己已经在车库里停了一辆半成品的赛车。
它和根目录 V2 最大的不同,不是收益更高,而是系统形态更完整:
- DuckDB 数据库。
- TradingView 宽筛。
- LongBridge 日线确认。
strategy:scan。strategy:live。strategy:backtest。strategy:prelive。
V6 scan 输出的信号也更像真正能执行的交易计划:入场规则、止损、股数、风险金额、加仓规则、失效条件都比旧脚本清楚。
V6 基线回测约 748.76%。如果只看这个数字,很容易冲动地把它升级成自动交易——毕竟人类天生擅长在“看起来很顺”的时候按下更大的按钮。
但同一轮检查里,strategy:prelive 明确返回:
FAIL_FOR_FULL_AUTO_LIVE
原因包括实盘真实性检查不足、前五大赢家依赖过高、长期样本不足等。
这时做了一个很关键的决定:
升级信号源到 V6,但不要升级成全自动实盘。纸面交易执行层应该写进代码里,MCP 只作为把订单同步到 LongBridge 模拟盘展示的通道。
这大概是整个项目第一次真正有“系统边界”的意识。
MCP 可以下单,但它不应该成为策略状态源。LongBridge 可以展示模拟订单,但本地系统必须自己记录纸面账户、止损、拒绝原因、信号来源和审计证据。
这也是我第一次更清楚地感到:交易系统里,“能做某事”和“应该做某事”是两件完全不同的事。
第五章:漂亮的回测数字开始互相打架
系统越认真,数字越不再统一。就像你开始用多台秤称同一袋米:它们不一致不是因为米坏了,而是因为你终于开始在意“秤准不准”。
V6 之后,我们开始用不同方式挑战自己的结果:
- canonical backtest。
- vectorbt cross-check。
- Backtrader external replay。
- screener consistency。
- data equivalence。
- same-bar ambiguity。
- corporate actions audit。
这些工具没有带来更整齐的答案,反而带来了更多差异:
- vectorbt 复算和 canonical backtest final value 不一致。
- Backtrader replay 出现
missing_bar_exit。 - 日线 OHLC 无法证明同一根 K 线里先入场还是先止损。
- delisting coverage 仍是
PARTIAL。 - 数据源混用、复权 / 不复权、公司行动处理都会污染结果。
一开始看到这些差异会不舒服。后来我意识到,它们不是麻烦,而是系统开始诚实——它终于不再只会报喜,还会把“不确定”写在脸上。
真正危险的不是回测结果不一致。真正危险的是所有结果都很漂亮,却没有任何复核能解释它们为什么漂亮。
5 月 21 日还有一个很典型的例子:V6 回测从约 748% 下降到约 612%。最开始不能一句“参数变了”带过,必须拆到交易级别去看。
最后确认,下降主要来自新增 buy-side gap / risk execution guards,而不是 split-adjustment fix。也就是说,它不是策略突然失效,而是系统开始把实盘开盘跳空和风险膨胀考虑进来。
还有一个更有意思的发现:score 看起来像一个很聪明的分数,但它不是胜率概率。它是确定性的排序指标。样本里 score 和实际 pnlPct 的 Pearson 相关大约只有 0.037。
这件事让我很警惕。量化系统里有很多东西看起来像“模型”,其实只是排序规则。它们可以有用,但不能被当成预测概率。
第六章:我终于承认,项目架构一开始没打好
到 5 月 21 日左右,问题不再只是策略。更准确地说:策略还在,但工程已经开始拖后腿了。
真正暴露出来的是工程结构:
- 根目录不是 git 仓库,变更不可追踪。
- Node、Python、插件、自动化、报告、数据混在一起。
- Web route 聚合了太多业务逻辑。
- CLI、Web、TUI、paper runner 容易漂移。
- 回测引擎既像策略引擎,又像组合执行器。
- 纸面交易器、paper service、runtime、provider 之间的边界也不清。
- monitor 输出里混入历史回放摘要,用户很难判断它是不是当前信号。
这时候如果继续堆功能,系统会越来越像一团能跑但不能信的东西:你让它跑,它确实跑;你问它去哪,它开始装傻。
所以后面开始补架构文档:
- 架构复盘
- 依赖关系梳理
- 重构路线图
- 交易安全不变量
当时最重要的工程原则是:
不要盲目重写。先画边界,再做最小原子改动。
这句话让项目从“策略实验”开始转向“系统工程”。我开始接受一个不太浪漫的事实:架构不是装饰,是刹车系统。
它也让我意识到,架构不是为了好看。架构是为了在结果不一致、状态冲突、接口失败、用户想下单时,让系统知道谁说了算。
第七章:5 月 23 日,项目从原型变成工程
5 月 23 日,这套新仓库建立了 git 基线。
初始提交:
20b248e chore: establish project version control baseline
那一天不是因为新增了某个神奇功能而重要,而是因为系统终于有了可追踪的历史。
从那以后,项目开始系统性补课:
- service contracts。
- task lifecycle tests。
- market data / data health / backtest / scan / paper plan 边界。
- shared strategy engine。
- shared execution planner。
- replay comparison。
- same-bar ambiguity report。
- data integrity report。
- Web dashboard observability。
- read-only TUI。
- live paper TUI。
- execution lock。
- operator readiness gates。
这一天也做了很多安全修复:
- 没有
initialStop的 BUY 不能提交。 - 模拟提交默认关闭。
- reconciliation 必须识别真正的保护性止损。
- fresh action data 才允许 paper submit。
- paper refresh 要有 timeout。
- DuckDB import 要事务化。
- Web / TUI / CLI 逐步共享 service-owned 状态。
如果要用一句话总结 5 月 23 日:
这一天我不再把系统当成“帮我找股票的脚本”,而是开始把它当成“可能会伤钱的操作系统”。
这个心态变化很重要。因为只要系统可能触达下单,就不能只问它能不能赚钱,还要问它在什么时候必须拒绝我。
第八章:最严肃的一次安全审计
5 月 23 日还有一次非常严肃的交易安全审计。
当时用户明确强调:
- 仓位控制严格按照总资金。
- 买入使用现金购买力,禁止融资。
- 每次下单必须带止损。
- 要确认是否使用了未完成日线。
这次审计把几条原则写进了系统的骨头里。
第一,cash 是硬约束。
不能用 broker buying power 美化仓位。交易系统不能因为券商给了更高买力,就默认可以融资或扩大风险。
第二,买入必须有止损。
没有真正的保护性止损,就不能把“之后会补一个止损”当成安全。尤其在自动化场景里,主订单成交后保护止损失败,后续 mutation 必须停下来。
第三,未完成日线不能被当成完成日线。
LongBridge daily bar refresh 改成使用 AdjustType.NoAdjust,并用 expected_cache_date(end) 避免把美股当日未完成 K 线混进信号。
第四,复权语义不能混。
Massive 使用 unadjusted 数据,LongBridge 也必须保持 adjusted=false,否则回测和实盘刷新会看见两个不同的世界。
这些听起来都是技术细节,但它们其实是交易纪律。
一个没有这些纪律的系统,收益再高,也只是一个会自动放大误判的程序。
第九章:界面也会让人做错事
我一开始低估了 UI 的风险。
后来 Web 和 TUI 越做越多,才发现界面不是“展示层”那么简单。它会改变操作员对系统状态的理解。
如果一个 TUI 面板只显示 4 条信号,但实际有 8 条;如果 plans 被折叠成 More rows hidden;如果页面有空白但核心表格被裁剪;如果提交按钮看起来可用但背后会被阻断,那么操作员迟早会误解系统。
这就是为什么后来 TUI 被改造成更像券商工作台:
- 顶部只放压缩状态。
Signals和Plans放在主区域。- Account、Orders、Stops、Decision 放在侧边摘要。
- Logs 和 Pipeline 降为底部状态。
- 当前 snapshot 已加载的数据必须全部进入 table model。
- 终端高度不够就滚动,而不是服务层假装“没有更多数据”。
这件事让我学到一个很实在的道理:
在交易系统里,展示不完整也是一种风险。
第十章:Task 018 和 fail-closed 的代价
5 月 24 日,系统遇到一个很烦人的阻塞,像门禁刷卡差那一下:
strategy_history_coverage_incomplete
正式 paper plan 因为 10 个 symbol 历史不足而 fail-closed。进一步检查发现,这不是 LongBridge 本地导入 bug,也不是换 Massive 就能补好的问题。这些 symbol 在两个 raw source 里都短历史。
其中 HNGE 最接近,有 252/253 根日线,但仍然不够。差一根就像差一分:你知道自己“几乎”过线,但规则就是规则。
当时有一个诱惑:只差一点,要不要放宽?
最后没有放宽。
系统新增了 strategy:audit:paper-coverage,把阻塞原因清清楚楚暴露出来,但保留 _enforce_strategy_eligibility_coverage() 的 fail-closed 语义。
这个决定并不酷,也不让系统显得更“智能”。它只是让系统在证据不足时停下来。
但我现在觉得,这种停下来比很多自动化都重要。
第十一章:10 年数据和 253 根日线
后来讨论 Massive 升级和 10 年数据补齐时,又出现一个问题:
10 年够不够?
答案不是简单的够或不够。10 年的意义在于覆盖更多市场环境:2018 Q4、2020 crash、2022 tightening、2023-2026 strength。但它仍然不覆盖 2008。
253 根日线也不是随便设出来的门槛。它来自策略自己的指标需求:
ret126ret63- 52 周高低点
SMA200- warmup
如果数据不够,很多指标看起来能算,实际已经不完整。于是后续形成了“两遍跑”的原则:
- 先用当前数据跑,知道当前结论是什么。
- 补齐更长历史后再跑,比较结论是否只是样本窗口幻觉。
这也是系统从“测收益”变成“测结论是否稳定”的过程。
第十二章:最夸张的数字,最低的置信度
5 月 26 日,系统做了一份量化可信度审计。
那时最吸引眼球的数字是:
- 严格 5 年:
820.54% - 10 年:
16375.37%
如果只把这两个数字拿出去,很容易讲成一个漂亮故事——甚至可以配上那种“从此财务自由”的标题党。
但审计报告给出的结论很冷静:
| 窗口 | 结果 | 最大回撤 | 审计置信度 |
|---|---|---|---|
| 严格 5 年:2021-05-24 至 2026-05-22 | 820.54% |
-27.83% |
44/100 Low |
| 10 年:2016-05-27 至 2026-05-22 | 16375.37% |
-30.88% |
26/100 Low |
收益可以由当前本地代码和 DuckDB 数据复现。
但复现不等于可信。
扣分原因包括:
- PIT universe 不完整。
- 数据源 revision 证据不足。
- vectorbt 复算差异过大。
- Backtrader 有 missing-bar issue。
- same-bar 交易比例过高。
- 收益集中在少数大赢家。
- 行业归因覆盖不足。
- 10 年最大回撤超过 30%。
这大概是整段经历里最重要的一幕:系统一边递给我一张夸张的成绩单,一边在旁边写了个巨大的“但”字。
一个系统跑出了 160 倍回测,但系统自己告诉我:这个证据质量很低。
如果它只会输出收益,我会更兴奋。因为它会输出怀疑,我反而更愿意继续建设它——一个会自我质疑的系统,至少不像在哄我开心。
中场复盘:我后来真正记住的几件事
第一,文章给的是方向,不是证据
Kova 和 Musolsol 的文章很有价值。它们给了我进攻、防守、止损、加仓、市场状态的框架。
但框架不是策略。策略也不是回测。回测更不是实盘。
每跨一层,都要重新证明一次。
第二,漂亮回测最需要被怀疑
150.97%、528.81%、748.76%、820.54%、16375.37%,这些数字每一个都让人心动。
但每一个数字后面都有问题:
- 股票池是不是事后选的?
- 数据是不是点时的?
- 退市股票有没有被遗漏?
- 公司行动有没有处理对?
- same-bar 顺序能不能证明?
- 外部框架能不能复算?
- 收益是不是集中在少数赢家?
如果不能回答这些问题,数字越大,越应该谨慎。
第三,两个引擎会制造两个真相
Node 纸面交易和 Python monitor 的差异,是一次很早但很重要的警告。
当系统有两个地方能“算出当前状态”,它们迟早会不一致。到那时,人会自然倾向于相信自己更想相信的那个。
所以 V6 后来一直在收敛:scan、backtest、paper plan、submit、monitor 尽量走同一套 service boundary。
第四,本地状态不能替代券商事实
本地纸面账户可以记录策略逻辑,但实际提交前必须看券商账户。
有无持仓、现金多少、今日是否已有订单、订单是否取消、是否重复买入,这些都不能靠本地推断。
这是从 FIX / MRVL 那次状态冲突里学来的。
第五,最好的自动化不是自动下单
最开始我以为自动化的目标是每天生成信号,最好自动下单。
后来我更看重另一种自动化:
- 自动检查数据是否完整。
- 自动检查是否使用未完成日线。
- 自动检查是否缺止损。
- 自动检查是否重复订单。
- 自动检查是否过了 submit window。
- 自动检查是否应 fail-closed。
一个成熟系统最重要的能力,可能不是执行,而是拒绝执行。
第十三章:纸面交易的断桥,逼我把“可靠性”当成需求
在写完一堆“不准下单”“不准融资”“必须有止损”的安全门之后,我一度以为纸面交易最大的风险已经被框住了。
我的想法当时很天真,像第一次装防盗门的人:门锁上了,我就觉得天下太平。只要默认不允许真实提交,把所有变更都关在模拟账户里,剩下的就是“把接口接通”,听上去就像插上电源那样简单。
后来我才发现,真正会咬人的往往不是“权限”,而是“连接”。因为我把一个更隐蔽的东西当成了理所当然:连接本身的可靠性。
IBKR 的纸面环境不是一个本地函数调用,它更像一座有风、有雾、还会临时修路的桥:带会话、带桥接、会断、会抖动、会恢复失败。你可以把提交权限关掉,但你关不掉“半连接状态”带来的幻觉——一边是本地的“我以为还活着”,另一边是券商的“其实早断了”。
断桥真的发生时,我才意识到:如果系统缺少恢复策略和审计证据链,那么“我没下单,所以安全”就像在黑暗里摸到墙就宣布自己已经到家。因为下一次你想把开关打开时,你甚至不知道自己站在哪个状态上:是“干净的起点”,还是“昨天的烂尾现场”。
于是那天我做了两个决定:
- 不把“偶发断线”当成环境噪声,而是把它当成需求:要么能恢复,要么能 fail-closed,并且能解释发生了什么。
- 把这类系统建设写成耐用流程标准:以后每次改动都必须从当前证据开始,而不是从记忆开始——记忆这种东西,在交易系统里属于最不该当作依赖的第三方服务。
这听起来像是在写项目管理,但对交易系统来说,它其实是一种自保:我宁愿慢一点,也不想再被“看起来能跑”的假象牵着走。
证据(2026-05-30)
- 近期提交集中在 IBKR 纸面桥接的恢复与验证(例如
d9b2f22、4c233be、990d68a、75008db),并补齐了“会话复用策略”“订单验证”等边界说明。 - 纸面运行开始留下可追溯的审计日志、转发快照和账户状态证据。
- 开发流程标准和 IBKR 安全边界也第一次被正式写成文档,而不再只存在于会话记忆里。
第十四章:我不再相信“自动化会乖乖照做”,于是给它装上了回执
在把“纸面交易也要当网络系统”这件事写进脑子之后,我一度以为下一步就很简单了:既然连接不可靠,那就把恢复策略补齐,把 fail-closed 的门闩再加粗一点,然后让自动跑起来就行。
当时我的潜台词是:自动化只是一个更勤快的我。它只是在我睡觉的时候,替我按一下“刷新数据 / 生成信号 / 计划提交”。
后来我发现,这个想法的天真程度,仅次于“门锁上了就天下太平”。自动化不是一个勤快的我,它更像一个没有情绪、没有直觉、也不会害怕的实习生:它能按流程执行,但它也能把流程执行到你后背发凉——尤其是在你看不到它“为什么这么做”的时候。
真正让我警觉的不是“它会不会乱下单”(我已经尽量把权限关住了),而是另一种更危险的情况:它在一连串灰色地带里做了事,但我事后只能得到一句“跑完了”。比如:
- 这次 reconcile 为什么降级了?是 broker 状态不可信,还是我本地推断错了?
- 这次 cancel-all 是针对谁的单?到底取消成功没?有没有留下一条“未能证明已取消”的尾巴?
- 这次 flatten 到底提交了哪些腿?为什么要 flatten?是因为风控命中,还是因为生命周期状态断裂?
- 这次 multi-broker 的两个目标,拿的真是同一份冻结信号吗?还是我以为一致、实际上漂了?
我突然意识到:当系统拥有越来越多的“允许做事”的入口时,最可怕的不是它真的做错,而是你无法用证据回答“它当时到底做了什么,以及为什么”。在交易系统里,没有回执的动作等价于没发生过;没有证据的成功等价于失败。因为你没法把它复盘、也没法把它当成未来自动化的可信前提。
于是我做了一个很不浪漫、但非常工程化的决定:把自动化的手拆成一根根手指,每根手指都要有名字、有权限边界、有回执、有审计字段,并且默认只读。
“只读”这两个字,在这一阶段成了一种信仰。它不是为了保守,而是为了在你真的想打开开关时,手里至少握着一张能对账的清单。
更关键的一点是:我开始把“自动跑起来”看成一条产品能力,而不是一堆脚本堆在 cron 下面。它必须能够回答:
我现在是否处在可以做事的状态?如果不可以,阻断原因是什么?如果可以,每一次变更的证据在哪里?
这也顺手戳破了我对“回测数字”的另一个幼稚幻想。因为当我把自动化做成可审计之后,某些报告突然变得刺眼:同一个实验,换个窗口、换个计法、换个 neighborhood,表现可能完全不是你想象的“稳健更好”。我开始更习惯用“拆窗 + 对照 + 邻域”去看待收益,而不是被一个总收益表格催眠。
证据(2026-05-30 ~ 2026-05-31)
- 自动化入口与安全门被明确写成可复用文档,包括 system refresh gate、multi-broker 冻结信号复用,以及 autoSafety 的配置边界。
- 自动化控制面被“产品化”为可见的 operations,并带证据摘要/结构化字段(近期提交密集于 auto controller、reconcile 证据、flatten 生命周期持久化等:例如
f9cd595、1132d2a、328e603及其前后多条 commit)。 - “final40” 的过拟合/窗口敏感性验证也被落成正式报告,专门比较了 compound vs fixed、10y vs 5y 以及多窗口拆解下的差异。
第十五章:纸面交易终于承认“券商才是现实”,于是我学会了“领养”与“等到有上限”
在第十四章里,我把自动化当成一个需要回执的“产品能力”。我以为做到这一步,我就能安心一点:至少它做了什么我看得见。
但很快,一个更扎心的问题冒出来:我看得见,不代表我看得对。
因为当系统跨越了“本地推演”和“券商事实”两张表,它最大的敌人从来都不是 bug,而是“你以为你们在同一个世界里”。
我曾经习惯用一种很偷懒的叙述安慰自己:纸面账户是我自己维护的状态机,所以它应该更“干净”、更“可控”。券商那边的持仓和订单,只要我不让它真的成交、或者不把真实权限打开,就不会对我构成威胁。
后来我被现实按在地上摩擦了一次:券商那边可能已经有仓位/有止损/有订单,而我的纸面状态却还在“从零开始”的童话里。你可以继续假装看不见,但那样你就把系统推向一种很危险的未来——某天你真想把自动化开大时,你会发现自己连起点都不是同一个。
这件事迫使我承认一个不怎么优雅、但很诚实的事实:纸面系统不是“模拟出来的世界”,它只是“对真实世界的一种解释”。
如果解释和现实不一致,你要么纠错,要么承认自己暂时没有资格继续解释。
于是我做了两个看起来很工程化、但其实很“心理建设”的决定:
- 允许“领养”券商仓位,但前提是它必须通过验证:尤其是保护性止损这件事,必须能证明。
- 把“等待券商给出答案”变成一个有上限的行为:自动化可以等,但不能无限等,更不能因为等而把整台机器冻死在 submit window 的门口。
当我把“领养”“验证”“等待上界”“面向操作者呈现状态”这些词写进系统之后,我才觉得自己开始从“写策略”走向“做系统”:不是把一个想法跑通,而是把它变成一种可以持续运行、可以被解释、也可以被停下来的能力。
证据(2026-06-01 ~ 2026-06-02)
- 允许在 preflight 阶段“领养”券商仓位(带验证门槛):
0dc11b1(Allow verified broker position adoption in preflight)。 - 把“领养”落实为可测的行为,并把“止损验证”当成硬条件:
2df2f6a(Adopt broker positions with verified stops),并配套补上领养与 reconcile 测试。 - 给 preflight reconcile 的等待加上边界,避免无限悬挂:
52509de(Bound broker preflight reconcile waits),并同步补上对应测试。 - submit window 的等待不再把 auto paper runner 卡死:
bbb3e58(Keep auto paper running during submit window waits)。 - 把 auto runner 的状态渲染成操作者可读的输出(不是“跑完了”三个字):
7482d5f(Render paper auto runner status for operators)。
日记摘录(来自其它会话摘要;仅记录转折点)
- 我以为“项目变慢”是代码规模的问题,后来发现主要是“运行时负担”在拖拽: 其它会话在 2026-06-01 的复盘里指出,真正压垮体验的往往不是源码本身,而是大量生成物、长跑研究任务和多会话叠加的 CPU/IO 噪声。这让我更愿意把“工程治理”理解成一件朴素的事:让系统能在现实的进程噪声里持续呼吸,而不是只在理想的源码规模里优雅。
- “工作树不干净就停止”不是洁癖,而是一种交易系统的自保: 2026-06-01 的另一份摘要里,有一次 premarket supervisor 直接被严格 gate 拦住。当你同时有多个会话和自动化在跑,dirty worktree 会让“到底哪份代码在运行”这件事变得像玄学。
- 在谈“代码量”“项目大小”之前,先说清楚边界: 我第一次认真区分了外层工作区和内层仓库的口径,才意识到很多“规模感”其实来自把依赖、缓存和生成物混在一起算。这和“回执/证据”是一回事:你不先说明口径,任何数字都只是在讲故事。
第十六章:我终于不敢一边重构,一边假设“语义应该没变”
到了 6 月 2 日前后,我又差点掉进一个很程序员式的陷阱:既然已经知道边界混乱、依赖漂移、回测引擎身兼数职,那就赶紧把东西拆开,越快越好。
这个想法听上去很正义,甚至很工程。问题在于,交易系统不是普通后端。普通后端重构错了,可能是接口挂掉;交易系统重构错了,可能是回测口径变了,但你还以为只是文件更整洁了。
我那时第一次认真意识到,那种“大一统回测文件”固然危险,但更危险的是另一种幻觉:只要我主观上“没有想改策略”,系统就会自动替我保留语义。
现实当然没这么体贴。候选池过滤、PIT universe、公司行动、daily bars、feature cache、signal snapshot,这些东西一旦拆分,哪怕函数名不变、测试也能跑,仍然可能在某个边角上悄悄改掉信号生成的口径。最可怕的是,那种变化往往不是红色报错,而是绿色通过,再配上一份看起来仍然很漂亮的回测结果。
于是我把“重构”这件事换了一种顺序:先冻结口供,再允许动手术。
这里的“口供”不是比喻,是我后来真的写进系统里的几层东西:
- 先把 quick / 5-year / 10-year 的 baseline 跑出来,留下一组以后谁都赖不掉的对照。
- 先把
ENGINEERING_GOVERNANCE_PLAN、ENGINEERING_GOVERNANCE_BASELINES这种文档补齐,明确哪些改动算“行为保持”,哪些一旦碰到就必须重新对账。 - 再去拆数据合同、候选合同、snapshot 合同、FeatureFrame 边界,而且每拆一刀都要求有 focused tests 和 parity 证据陪着。
这一步看起来不像“做策略”,更像在给系统上手术前签知情同意书。但它非常重要,因为它改变了我的默认心态:
以前我会想,先把结构理顺,回头再验证结果。
后来我逼自己接受另一种更慢的顺序:先证明结果没被我碰歪,才配谈结构变漂亮。
真正让我觉得这个转变值得写进回忆录的,不是多了几份文档,而是系统里第一次出现了一种很严肃的态度:它不再把“重构”默认当成进步,而是把它当成一种需要举证的行为。
这也顺手修正了我对“工程治理”的误解。我原来总觉得治理是效率的反义词,是功能做完之后才补的流程外壳。后来才发现,在交易系统里,治理更像是防止你把未来收益偷换成当前自信的装置。没有它,所谓“我只是抽了一层”很容易变成“我偷偷改了一个世界,却没留指纹”。
而 6 月 3 日那串和 FeatureFrame、signal_snapshot、signal_candidates 有关的提交,让我更确信这条路是对的。因为它们传达的不是“我们又加了几个模块”,而是另一句更值得记住的话:
以后如果连回放、候选、风控边界都要共用同一份叙述,那这份叙述就必须先被定义、被冻结、再被复用。
我到这时才真正明白,系统成熟不只是“能自动跑”,也不只是“知道该停”。它还有第三层能力:
当你必须改它时,它能逼你证明自己没有把它改成另一个东西。
证据(2026-06-02 ~ 2026-06-03)
- 工程治理被正式写成工作计划和验收清单,明确要求在策略、特征、执行语义改动前先对照 baseline,并把 simulated-paper 仍视为 production-like safety surface。
- baseline 口径也被固定成可复用配置,并配套生成了 quick、5 年、10 年三层基线产物。
- 10 年基线报告显示系统开始把“漂亮收益”变成“必须可复核的口径”:compound
26493.86%、fixed677.80%不再只是一个孤立数字,而是治理基线的一部分。 - 数据/候选/特征边界被连续抽离成合同,而不是继续埋在回测引擎里:
3020c9b(Extract PIT universe data contract)、dc032f5(Extract corporate action data contract)、b36103a(Extract daily bar data contract)。 - 6 月 3 日继续把信号与回放叙述收束到共享边界:
12bf143(Separate signal snapshot and candidate generation)、00220d3(Route backtest replay through FeatureFrame)、75cc098(Wrap feature cache replay in FeatureFrame)、0e2ebb0(Harden FeatureFrame market risk boundaries)。
第十七章:我不再允许“恢复逻辑”替系统脑补现实
到了 6 月 4 日和 6 月 5 日,我原本以为前面的教训已经够清楚了:既然“券商才是现实”,那系统只要在提交前、对账时、领养仓位时尊重券商事实,应该就差不多了。
后来我才发现,这个想法还是有点天真。因为交易系统里最容易自我感动的地方,往往不是主流程,而是“恢复逻辑”“修复逻辑”“补救逻辑”这些听起来像善后、其实同样在改写现实的角落。你一边说自己尊重 broker truth,一边又偷偷允许本地状态去脑补现金、止损、订单归属,最后写出来的就不是恢复,而是同人小说。
这轮最刺耳的提醒,不是某一条报错,而是一连串很直白的提交标题:
Remove local cash and stop recovery fallbackRequire broker truth for paper mutationsRequire broker-confirmed paper recovery facts
它们像三记耳光,打在同一个地方:不能因为“这是恢复路径”就默认它比交易路径更无害。
我以前多少带着一点工程上的侥幸心理。总觉得如果主提交流程已经很严格,那恢复逻辑宽松一点也没关系,反正只是为了把系统从一个尴尬姿势扶正,不是真的要“重新发明交易语义”。但现实并不跟你讲这种情面。恢复逻辑一旦能自己补现金、补止损、补订单事实,它就已经在替你决定账户到底处于什么状态。它不再是修水管的人,它已经坐到了交易台上。
而这恰恰是量化系统里最危险的一类错觉:你以为自己只是在“把状态补齐”,实际上是在悄悄篡改“什么算已发生、什么算可继续、什么算仍然安全”的定义。平时它可能表现得很温柔,只在你最忙、最想偷懒、最希望系统“先自己跑过去”的时候,替你做一个看似合理的决定。真正出事时,你甚至很难第一时间说清楚,这到底是策略决定、执行决定,还是恢复逻辑的自由发挥。
这也是为什么我后来开始把一句话记得很死:
在交易系统里,补救路径不是后台工具,它是另一条执行路径。
一旦承认这一点,很多工程判断就会自动变得更严厉。
比如,券商侧没有确认的恢复事实,就不能算恢复成功。
比如,本地看到的现金、止损、生命周期记录,只能作为线索,不能自动升级成现实。
比如,哪怕 runner 还活着、日志还在刷、自动化看起来“没停”,你也不能因为它像个勤快员工,就放弃追问它到底是依据什么继续往前走的。
我慢慢意识到,前面几章里写的“券商才是现实”,到这里才算真正长出牙齿。它不再只是一个提交前的口号,也不再只是领养仓位时的审慎态度,而是被推进到了系统最容易耍小聪明的区域:恢复、修复、对齐、重试。这些地方以前最像工程细节,现在反而最像交易纪律。
所以这轮我最认同的决定,不是“又修掉了几个 bug”,而是把恢复逻辑也纳入同一条冷冰冰的约束里:
- 不能证明,就不要认定。
- 不能从 broker 证实,就不要当成事实。
- 不能确认事实连续,就不要假装系统有资格继续自动推进。
听上去很不近人情,但它其实是在替未来那个更忙、更容易犯错的自己兜底。因为真正的风险从来不是系统报错,而是系统带着一种“我帮你猜过了”的好意继续运行。
如果说前面我学会的是“系统要会说不”,那这两天我学会的是另一句更窄、也更难做到的话:
连恢复时,也不能为了把故事讲圆,就替现实补台词。
证据(2026-06-04 ~ 2026-06-05)
- 6 月 5 日凌晨的连续提交把同一条原则压得更实:
aa28e07(Remove local cash and stop recovery fallback)、6bec394(Require broker truth for paper mutations)、2ce26d5(Require broker-confirmed paper recovery facts)。 - 与这条主线相连的前置提交密集集中在 broker truth / stop recovery / parity gate:例如
19b07ba、658d860、81713a3、961f0b4、a30d84d、86e5148,说明这不是孤立修补,而是在把“券商事实优先”推进到恢复、提交、flatten、position adoption 的整条链路。 - 审计状态证据里能看到恢复路径为何危险:2026-06-04 先出现
deferred_stop_recovery_lifecycle_record_failed与 lifecycle 唯一键错误,随后又记录RECOVERED_PROTECTIVE_STOP;这提醒我,恢复逻辑若没有更强的事实约束,很容易在“已经补好”和“只是自以为补好”之间摇摆。 - 同期另一条 IBKR 审计轨迹也反复暴露“操作已发生”不等于“状态已可信”:flatten、cancel、reset 一连串事件之后,恢复与对账逻辑还在继续收紧,说明工程重点已经从“能不能发动作”转向“动作背后的事实是否可证实”。
- 当时的工作树本身也还不干净,这本身像一个提醒:这轮仍处在收口 broker truth 语义的过程中,不适合把系统想象成“已经完全稳定”。
日记摘录(来自其它会话摘要;仅记录转折点)
- 我一度差点把一个更宽松的默认值当成“只是操作体验调整”,后来复查才发现它其实是在动安全默认值: 其它会话在 2026-06-03 的治理复查里,把
blockCanceledBroker=false明确判成新的 P0 safety-default drift,而不是普通结构债务。这件事逼我承认,交易系统里最危险的改变,常常不是大重构,而是一句“默认先放宽一点吧”。 - runner 健康不等于系统清白,这条原则后来被审计流程反复钉实: 6 月 3 日和 4 日的 open / intraday reconcile 摘要都坚持把最新买入一致性、broker symbols、protective stops 放在 runner 心跳之前。这让我后来更容易接受一件不讨喜的事实:一个“看起来还活着”的自动化,完全可能正在依据错误的现实继续工作。
第十八章:我后来才明白,“容量”不是展示字段,而是执行语义
到了 6 月 5 日晚和 6 月 6 日凌晨,我又被一个看起来很小、其实很危险的问题拦了一下。
我原本有一种很自然的偷懒想法:maxNewOrders、候选前几名、paper parity 里的 expected buys,这些东西大致说的是同一回事。它们可能写在不同文件里,名字有点不同,但应该只是“展示层说法不一致”,不至于改变系统实际会买什么。
后来事实证明,这个想法还是太轻率了。
开盘后的只读审计先给了我一个不舒服的画面:运行日志里明明写着这轮共享信号下实际提交的是 MRVL、VSH、BB 三个名字,maxNewOrders=8;但另一边的 parity / reconcile 语义又开始把 STRL、MRVL、TWLO、VSH、OSCR 当成 expected buys 来看。到了 6 月 6 日的 LongBridge parity,系统甚至一边说 missingExpectedSymbols=[],一边又明确报出 extra_actual_symbol=BB,同时 MRVL、VSH 的止损检查仍然失败。
这种不一致最麻烦的地方,不是“报表不好看”,而是它会让人误以为自己在做对账,实际上却在拿两套不同的容量语义互相比较。你以为自己在检查执行是否偏离策略,结果可能只是 audit、plan 和 submit 对“这一轮到底允许拿几笔、优先拿哪些笔”各自说了一套话。
于是后来那几个提交我反而看得比表面更重:f295ac6 把 simulated paper 的 broker reconcile 再收紧,1583f0d 继续盯着 deferred stop recovery 的监控语义,而 c2a169d 直接把 paper entry selection 调回 strategy capacity 这条主线上。它们连起来说明的不是“又修了几个 if”,而是另一层更具体的认知变化:
容量不是报表参数,也不是 UI 上的数字。容量本身就是策略执行语义。
一旦这件事没有统一,系统就会出现一种很有欺骗性的稳定感:runner 还在跑,候选还在产出,订单也确实发过,日志里甚至还能看到“expected buys”这种很像答案的字段。但真正的问题是,这些字段未必在讲同一个现实。
这也是我这两天学到的另一课:交易系统里,连“该拿几笔仓位、先拿哪几笔、已有仓位算不算占用容量”这种听起来像调度细节的问题,最后都会升级成安全问题。因为只要容量语义漂了,后面的 parity、reconcile、stop coverage、甚至“到底是不是多买了一笔”都会跟着一起漂。
正文里我越来越少写“某次提交修复了某个 bug”,更多写“我当时误以为哪些概念天然一致”。因为很多真正的返工,都是从这种过度乐观的等号开始的。
证据(2026-06-05 ~ 2026-06-06)
- 最新提交继续沿着 paper execution 语义收紧:
f295ac6(Harden simulated paper broker reconciliation)、1583f0d(Harden paper stop recovery monitoring)、c2a169d(Align paper entry selection with strategy capacity)。 - 6 月 5 日开盘后的只读审计记录显示,运行日志里
paperPlanExecutionContract.maxNewOrders=8,但实际selectedBuyCount=3,提交符号是MRVL、VSH、BB;后续 reconcile 仍报 LongBridge 侧MRVL/VSH缺止损、IBKR 侧缺少预期符号且 order truth 不完整。 - 6 月 6 日的其它会话摘要里,LongBridge parity 审计给出的
expectedBuys.symbols是["STRL","MRVL","TWLO","VSH","OSCR"],actualSymbols是["BB","MRVL","OSCR","STRL","TWLO","VSH"],并继续把BB标成extra_actual_symbol,同时MRVL、VSH的 stop checks 失败。 c2a169d的变更把 entry candidate selection、shared target basket、latest paper buy parity 的 expected rows 和 position capacity 重新绑在一起,不再把maxNewOrders或“最新信号前几名”简单当成统一真相。这说明这轮修补已经从 broker truth 扩展到“容量 truth”。
日记摘录(来自其它会话摘要;仅记录转折点)
- runner 看起来活着,不代表它真的还在同一个现实里工作: 6 月 6 日的 intraday reconcile 摘要先读到 heartbeat 里
status: RUNNING,随后控制器视角又把 runner 判成DEAD,同时 tmux session 已不存在。这让我更不敢再把“还有心跳文件”当成自动化仍然可靠的证据。 - IBKR 的认证失败把另一个边界也钉得更实: 同一条摘要里,多券商 reconcile 直接撞上
401 Unauthorized,LongBridge 则继续暴露 parity / stop anomaly。它提醒我,多 broker 架构里最麻烦的不是某一边单独报错,而是一边已经无法给出事实,另一边还在继续制造需要解释的偏差。
第十九章:我原以为 alpha 还藏在信号里,后来发现它更多藏在“怎么持有”
写到这里的时候,我其实还有一个顽固的直觉没放下。
我一直觉得,真正值得兴奋的改进,大概率还应该来自“发现了更好的信号”。换句话说,如果某次结果突然更强,我会本能地先怀疑是不是筛股条件、节奏判断、趋势定义又往前推进了一步。至于仓位上限、初始部署、加仓节奏、time exit、super winner、chandelier 这些东西,我心里虽然知道重要,但总还是容易把它们归到“执行层优化”这一栏,像是在整理收益,而不是在决定收益。
6 月 7 日这批结果,逼着我把这个想法改掉了。
那天最有说服力的地方,不是又跑出了一组漂亮数字,而是同一条证据反复强调了一件事:final40 的信号核心根本没变。
研究报告、formal promotion、robustness validation、system default 更新,都在围绕同一个前提展开。信号没有偷偷换一套说法,变化集中在 portfolio behavior、risk、sizing、cash deployment、add-on、stop 和 time-exit 上。
这就让结果的含义完全不一样了。
如果信号没变,而 10 年和 5 年的表现、walk-forward、execution stress、formal parity 还在往前走,那我就不能再把它理解成“可能又找到了更聪明的选股方式”。更接近事实的说法反而是:我以前低估了“怎么持有、怎么分配、怎么限制自己”本身就是策略的一部分。
这件事对我冲击很大,因为它会反过来改写很多做研究时的习惯。
以前看到高收益,我第一反应常常是去追问信号;现在我会先问,这份收益到底是不是被一套更一致的持仓语义释放出来的。
以前“max position”“max new orders”“initial deployment cap”看起来像参数表里的小格子;现在它们更像纪律条款,改一个就是在改系统的性格。
以前我还会下意识把执行层看成离 alpha 稍远的地方;现在我更愿意承认,有些 alpha 根本不是被“找到”的,而是被“不乱拿、不乱加、不乱放大”保留下来的。
更关键的是,这次它没有停在研究目录里。
dbe4b29、c4551e7、8fd9d6a 这一串提交,已经不只是“候选更优”,而是在把组合行为语义正式提升成系统默认路径。紧跟着的 Python 3.13 迁移和 baseline parity 回归,又像是在追问另一个更严厉的问题:如果把运行时换掉,这套语义还站得住吗?
我后来反而很喜欢这种追问。因为它让“好结果”不再只是实验室里的一次截图,而开始像一个可以跨 runtime、跨报告、跨入口重复确认的系统选择。
如果说前面几章写的是“不要让系统替现实脑补”,那这一章更像另一种承认:
真正改变系统收益曲线的,不一定是更会猜市场,而可能是终于学会了如何约束自己持有市场。
证据(2026-06-07)
- 一份 2026-06-07 的策略基线文档明确写出:
final40信号核心固定不变,变化只来自 portfolio behavior、risk、sizing、cash deployment、add-on、stop 和 time-exit 控制;最高 10 年收益候选为53906.88%,5 年为5878.36%,而这份文档仍刻意把它标成研究基线,不冒充生产默认。 - 同日的组合行为验证报告把同一个结论做了更系统的验证:
455个实验都锁定final40信号核心,只比较组合行为与风险执行语义;其中maxPositionPct=42.5的 compound 候选在稳健评分和 10 年收益之间给出了最强的一组平衡。 - 另一份稳健性验证报告继续把这套配置拿去做 walk-forward、execution stress 和 no-progress exit 检查,说明它已经不只是“某次最优参数截图”,而是在被当成一条准备推广的正式语义审查。
- 提交链
dbe4b29(Add portfolio behavior validation candidates)、c4551e7(Promote portfolio behavior semantics to formal execution)、8fd9d6a(Set portfolio behavior strategy as system default)把研究结果推进到正式执行与默认配置层;这比单份报告更像真正的工程里程碑。 - 当天晚上的
9ffb4a7(Migrate runtime to Python 3.13)和一份 5 年基线回归结果又给出第二层证据:换 runtime 后,5 年 compound 仍记录5189.34%、fixed 仍记录481.04%,说明这次提升不是绑定在某台旧环境上的偶然产物。
第二十章:我以为观察层只是“把状态展示出来”,后来才发现它必须先学会不夺权
6 月 7 日以后,我原本以为下一阶段会轻松一点。
信号核心冻结了,组合行为也开始有了比较稳定的正式语义。按一种很自然的工程直觉,后面似乎就该进入“把这些事实展示给人看”的阶段:做一个更像样的 operator WebUI,做一个只读远程 monitor,把通知接到 SMTP 和 Bark,再补一层统一 API,让不同入口都能看到同一份状态。
这个想法表面上没错,但它差点让我低估了另一种风险。
我一开始下意识把这些工作叫作“观察层”。这个词听起来很安全,仿佛只要不直接下单、不直接改策略,它就只是系统外面的一层玻璃。后来做着做着我才发现,真正危险的地方恰恰在这里:只要观察层开始自己解释事实、自己补全缺口、自己发明一个“差不多能代表当前状态”的说法,它就会变成第二套控制面。
而交易系统最怕的,从来不是没有界面,而是同时存在两套现实。
更容易让人误判的是,这几天并不是只做了两三个“展示功能”。实际上,shared engine 的只读状态层、operator WebUI、readonly monitor、notification、统一 Web API v1、AOC、甚至 data governance 的 D2 到 D7,都在并行推进。它们看起来分散,像很多条并行施工线;但如果退一步看,会发现这些工作其实都在围绕同一个问题打转:以后无论是谁来看系统,看到的到底是不是同一份、同一层级、同一边界下的事实。
6 月 13 日到 15 日这几天,系统里发生的很多事,表面看像是 WebUI、monitor、通知和 API 的并行推进;更深一层看,其实是在反复回答同一个问题:
一个界面、一个通知、一个状态 API,到底是在“读取事实”,还是在“偷偷拥有事实”?
后来我越来越喜欢那些看起来有点保守、甚至有点扫兴的设计:
- runtime status 只能是 display-only summary,不能拿去决定 broker mutation。
- readonly monitor 只能消费缓存和 owner contract,不能把 fallback 包装成 canonical truth。
- notification 可以生成 daily status summary,但默认是
dry_run,而且明确写出它不是 trading authority。 - Web API v1 即使把路径全注册出来,mutation-shaped POST 也要故意返回 disabled
403,而不是先把入口做出来再说。 - operator WebUI 和 readonly monitor 必须是两个产品,不共享一个模糊的“都算 WebUI”的想象。
这些决定最有意思的地方,是它们看起来不像“往前做功能”,更像在给未来的自己设置护栏。可如果没有这些护栏,观察层迟早会从“帮助人理解系统”滑向“替系统解释世界”。
我后来才承认,真正成熟的交易系统,不只是把执行层和策略层分开;它还必须把“能看见什么”和“能据此做什么”彻底分开。
所以 6 月中旬这轮工作,给我的启发不是“我们终于有更完整的产品界面了”,而是另一个更冷一点的结论:
观察层不是装饰层。它也是安全边界。
证据(2026-06-13 ~ 2026-06-15)
- 一份运行时控制文档明确把
runtimeStatus.readiness定义成 display-only summary,禁止消费者用它决定 broker mutation;真正的执行决策仍必须回到 plan、submit、broker truth、reconciliation、data freshness 和 auto safety contracts。 - 另一份通知生产 runbook 把 SMTP/Bark 路径写成 observer-only:默认只做
dry_run,只有显式--send才允许受控外发,而且文档反复声明 notification state 不是 broker truth、submit readiness、protective-stop coverage 或 runtime control。 - 一份协调路线图在 2026-06-15 的接受记录里,把
f6c267b定义为 Shared EngineoperatorReadiness只读聚合,把5c58d26定义为 Unified Web API v1 facades 只读门面,把a318097/2a93f20定义为 service-control 发现接口和只读 consumer,并明确要求 POST start/stop/restart 保持 disabled403。 - 同一份路线图还把
3a9c245的 readonly monitor WMR2 和91a2514的 operator WebUI W14 分别记录成两个独立 surface:前者是 remote operations overview,后者是 operator workflow drilldown;这说明项目已经不再允许“只读监控”和“操作者面板”在目录或产品边界上混成一团。 - 同一时间段的 data governance D2 到 D7 也沿着同样的思路推进:manifest contract、refresh profile、planner、validator、CLI wrapper、offline builder 都被写成 evidence-only、non-authoritative、显式禁止直接触发生产数据动作的形状。它们表面上是数据治理,骨子里仍然是在防止“研究/观察工具”偷渡成生产 authority。
- 当时最新的通知状态快照也印证了这条边界:
deliveryHealth.status仍是dry_run,说明通知层没有偷偷越权。
日记摘录(来自其它会话摘要;仅记录转折点)
- 我原以为“monitor 上多显示一点东西”只是前端工作,后来才意识到 fallback 一旦被说得太像答案,就会变成新的 authority。 6 月 13 日的其它会话摘要专门把 Shared S10 的 monitor fallback 收口成
displayOnly、readyAuthority=none、coverageAuthority=none,并把继续扩张 runtime consumer 的冲动按下去。 - 我原以为通知系统一接上 SMTP/Bark 就算“更接近生产”,后来才明白真正的成熟是默认不越权。 从 runbook 到当前快照,这条链路最强调的不是“已经能发”,而是“即使能发,也必须先证明它只是在观察、不是在指挥”。
第二十一章:我以为终端只是皮肤,后来才知道操作者首先需要一个不吵闹、也不撒谎的界面
如果说上一章是在讲“观察层不能夺权”,那 6 月中旬另一条更具体的教训就是:操作者界面不只是审美问题,它本身会影响系统可信度,甚至会反过来影响运行时稳定性。
最开始我对 terminal operator console 的期待其实很朴素:把 auto runner 正在经历的事情,用更像人类会看的方式展示出来。最好是中文优先,最好能把 broker、plan、readiness、next action 都摆清楚,最好别再让一堆原始日志和 ws/auth 噪音把主 pane 挤满。
听起来像“UI 改造”,但真正做起来以后,我才发现这件事一点也不表面。
因为旧终端的问题不只是“不好看”。更麻烦的是,它会把三种完全不同的东西混在一起:
- 当前应该看的 operator state。
- 历史上发生过、但已经过去的过程噪音。
- 某些底层 transport / auth / progress 信息,它们看起来像事实,实际上只是实现细节的泄漏。
一旦这三种东西被混在一起,操作者看到的就不是“当前状态”,而是一个同时包含过去、现在和底层噪声的叠影。界面在说话,但它说的不是同一个时间点的现实。
更让我吃惊的是,后来为了把这些噪音压下去,系统甚至踩到了运行时边界。AOC-MVP7 那次对 fd capture 的修补,本来是为了处理 Python 层拦不住的底层写入,结果第一次 runtime apply 直接把 runner 打回了 zsh,并引出了 NoneType.write 这种本来不该出现在 broker preflight 路径里的错误。那一刻我才真正服气:原来“只是为了让终端安静一点”的改动,也足以伤到无人值守执行链路。
这件事把我的态度改得很彻底。
后来 AOC-MVP8 到 MVP12 一路做下去,我已经不再把它理解成“终端 polish”,而更像是在给操作者争取一种更诚实的观看方式:
- 当前状态应该尽量只保留当前 frame,而不是不断追加历史整块输出。
- transport incident 可以显示,但必须是 structured、display-only,不能伪装成 broker truth。
- owner gap 应该直接承认,而不是用一串看起来技术上很完整、实际却不帮助判断的字段去掩盖。
- 中文化也不只是翻译,而是把“操作者下一步该做什么”说成真的能用来判断的句子。
我以前会把终端看成系统最外层的一张皮。现在反而更愿意承认,终端是人和自动化之间最后一层解释器。
如果这层解释器既吵闹、又混杂历史残影、还把实现细节泄漏成“状态”,那操作者就会在最需要判断的时候,被迫先去分辨哪些是当前事实、哪些是旧帧、哪些只是噪音。
从这个意义上说,6 月中旬这些 AOC 波次真正修的,不只是样式,而是另一种执行风险:
不要让操作者在读界面的时候,再做一次日志考古。
证据(2026-06-13 ~ 2026-06-15)
- 一份协调路线图把 AOC-MVP5 到 AOC-MVP12 记录成一条连续的产品/运行时链路:从中文分组终端、噪音 suppression、fd capture、fixed-refresh frame、TTY repaint,再到 startup/countdown watch frame;这说明问题不是一次性“换皮”,而是多次逼近“当前状态应该怎样被正确看见”。
- 同一份路线图明确写出 AOC-MVP7 的 runtime apply 曾失败:run id
280bb5392dbd在第一轮 cycle 退出,pane 回到zsh,并指向 broker preflight 的AttributeError: 'NoneType' object has no attribute 'write'。这证明显示链路改动并不天然无害,它也可能伤到执行链路。 - 后续
271161e/e2fe05c、cf10636/7698d8f、994c4e7/c2273fe、f36b407/65b678f、acc29f4/e74e0a6又反复留下另一组证据:每一轮都要求 heartbeat、parity、run log crash signature 和主 pane 输出一起过关,说明系统已经把“操作者看见什么”当成 runtime 级验收,而不是纯前端润色。 - 其它会话在 2026-06-14 的 observability 摘要里还记录了一次很典型的中间结论:raw ibind/WebSocket stderr 被压进结构化的
recentTransportErrors,并明确标注displayOnly=True、authority=operator_terminal_observability_only。这说明项目没有试图“隐藏问题”,而是在把 transport 噪音重新安放到不夺权的位置。
日记摘录(来自其它会话摘要;仅记录转折点)
- 我原以为 suppress 噪音就是少打印几行,后来才发现底层写入路径根本不一定走 Python 那层钩子。 6 月 14 日的 observability 摘要里,先是把
IbkrWsClient: on_error、timeout、handshake 500/504、SSL EOF 等字符串压进 display-only incident summary,随后又因为并发 deque 迭代问题补了锁和回归测试。 - 更重要的不是“界面终于干净”,而是系统开始承认 transport incident 可以被看见,但不该借此冒充 broker 状态。 这条边界其实和 notification、monitor、Web API 那条线是同一个教训,只是它发生在终端里。
第二十二章:我以为回测只要跑出一个终值,后来才知道它也必须能被追问
如果说前面那些审计已经让我不再轻易相信“漂亮数字”,那 6 月 16 日这轮工作更像是把这种不信任正式写进了产品边界里。
我以前虽然嘴上总说要审计回测,可真到系统形态上,回测依然很容易被当成一个终值、一张权益曲线、或者最多再加一张 trades CSV。它当然比一句口头结论强,但本质上还是太像一个已经被处理完的答案。人看到一个 748%、5189% 或某条看起来很顺的曲线时,第一反应仍然是先被说服,而不是先去追问:到底是哪几只股票、哪几次加仓、哪一段回撤,把这个结果推到了这里?
这正是我后来觉得不够的地方。
因为当 operator WebUI、readonly monitor、Web API 都已经被迫学会“只读、不夺权、讲清 owner contract”以后,研究层如果还停留在“给你一个最终结果,剩下自己翻文件”,那它其实还保留着一种旧时代的特权:它可以影响判断,却不用及时回答追问。
6 月 16 日的新变化,看起来像一组 readonly backtest 功能:WebUI 接上 snapshot facade,后端补 data pipeline read model,回测 run 可以流出 live progress curve,还能按单个 symbol 打开 review 页面,看 K 线、买卖点、加仓、止损、narrative rows 和 risk context。表面上这像“研究界面更完整了”,但我后来更愿意把它理解成另一种约束:
回测结果也必须像 broker truth、runtime status、notification 一样,成为一个可以被追问、但不能自己偷渡成 authority 的只读对象。
这条边界很微妙,也很重要。
symbol review 不是为了把图做漂亮,而是为了逼系统回答:“这笔交易为什么在这里出现,后来为什么在这里结束?”
progress curve 不是为了让跑任务的时候更有动静,而是为了避免回测再次只留下一个已经结案的终值。
artifact freshness、selected-run artifact path、display-only marker authority 这些看起来很扫兴的字段,也不是产品装饰,而是在防止系统把“最新文件”“默认曲线”“图上的箭头”再次包装成不需要解释的事实。
我后来才承认,真正可靠的研究工具不是“能快速给我一个高收益答案”,而是“当我开始怀疑这个答案时,它不会逼我去做第二轮文件考古”。
所以这一轮最值得记住的,不是 WebUI 又多了几个接口,也不是回测页面终于更像产品,而是一个更底层的变化:
从这一天开始,回测不再只是一个结果文件;它开始被要求成为一个可审问、可定位、可回放上下文的只读证据对象。
证据(2026-06-16)
- 提交链
22aee68(Add operator WebUI snapshot core facade)、68e2599(Wire operator WebUI readonly read models)、e1da110(Add readonly WebUI API facade pack)先把 operator WebUI 和 Web API 的 backtest/broker/data/pipeline 读取路径接起来,明确它们是在消费只读 read model,而不是直接拥有回测逻辑。 4596985(Enrich readonly data pipeline read model)把数据管线事实补进统一只读门面,说明这次不是“多一条图表数据”,而是在回答“这次回测建立在怎样的数据准备事实上”。eae94fb(Stream readonly backtest progress curves)把 live progress curve 从回测执行链路流到只读服务;配套测试也专门验证 selected run 可以拿到 live curve / event logs,而不是只剩最终 artifacts。eba264d(Add readonly backtest symbol review)新增了单标的只读回测复盘接口,并把它定义成 “Single-symbol K-line review read model”,只返回 candles、signal/buy/add/sell/stop markers、narrative rows 和 risk context,并特别注明 “Does not start a backtest or own stop truth.”- 同一组测试还把 marker contract 钉死成
readOnly=true、actionable=false,并要求 payload 带上artifactFreshness、backtestProvenance、selected-run artifact path、tradesPreview、eventsPreview等字段。也就是说,系统不仅要给结果,还要说明“这是谁的结果、从哪份 artifact 来、现在新不新鲜”。 - 当天补上的接口约束与 same-origin 限制,也进一步说明这套能力被刻意收口在 readonly facade 里,而不是让前端自己发明回测事实。
第二十三章:我以为缺的是几天记录,后来发现缺的是“我为什么一直追问”
这次重看回忆录,我才发现前面的写法仍然太像“系统自己在成长”。它记录了很多提交、边界和 fail-closed 规则,却漏掉了一条更重要的暗线:我在很多会话里反复问的,其实不是“今天又做了什么”,而是“这个系统到底还能不能被我信任、被我维护、被我交给无人值守流程”。
5 月 27 日那个问题很典型:回测和券商交易到底是不是同一套信号和策略层?这不是一个代码洁癖问题。如果回测在一个世界里选股,纸面交易在另一个世界里下单,那么后面所有收益、止损、对账、WebUI、通知都只是建立在错觉上。那次审查最后发现主路径已经共享,但还留下一个 diagnostic fallback。这个结果比“通过”更有价值,因为它第一次把“共享主路径”和“非权威诊断路径”分开了。
6 月 3 日我又问,为什么这个系统变得这么复杂。现在回头看,这个问题也不是抱怨代码多,而是在问一个更大的因果:为什么每次开发最后都落到安全边界、owner contract、fail-closed、兼容迁移、运行状态证明?答案其实并不好听:因为系统已经从研究脚本进入 unattended broker execution,它不再只负责给我一个策略想法,而是要负责数据新鲜度、信号可追溯、券商事实、保护性止损、对账、恢复、通知、终端和 WebUI 的一致口径。复杂不是凭空来的,复杂来自“每一个以前可以省略的现实问题,都开始索要自己的 owner”。
后来关于并行开发、autopush、VPS、R2、hooks 的问题,也都属于同一条线。它们表面看是工程流程,其实是在回答:当系统越来越大时,我不能再让所有事实都住在一个本地目录、一个终端窗口、一个 Codex 会话、一次手动上传里。于是 main coordinator、固定 worktree、feature branch 自己推自己、VPS 按 SHA 部署、数据同步从全量上传变成增量验证、R2 成为可核验的远端事实,这些东西才逐渐出现。
我之前漏掉这些,是因为我把“有叙事价值”理解得太窄,只看某一天有没有新的产品能力。可真正的转折有时不是多一个页面、一个 contract、一个测试,而是用户的问题把系统往后推了一步:
“不要继续小修小补。”
“为什么复杂成这样?”
“回测和交易是不是同一个信号层?”
“VPS 上怎么保证跑的是最新验证过的代码?”
“R2 为什么要重复上传?”
“这些 hooks 没有效果,真正能约束行为的是什么?”
这些问题把项目从“写更多功能”推向“证明每个功能有唯一事实来源、可运行边界和清理旧逻辑的责任”。如果说前面的章节记录了系统学会说“不”,这一章更像是补上另一个事实:很多关键进展不是系统主动变聪明,而是我不断追问它哪里还在自欺。
到了 6 月 17 日,这条暗线变得更明显。那一天的提交密度很高,如果只看标题,很容易把它们归类为“operator console 又打磨了一天”。但完整看下来,它其实把几个之前分散的现实问题一起推到前台:执行日公司行动事实必须进入 operator 输出;R2/data governance 不只是离线数据工程,而要影响自动运行前的数据门;broker fact、strategy expected actions、signal-day buy slot、old positions in new-buy target slots 都不能靠界面再解释一遍。
这不是一个漂亮的新功能章节,而是一种更笨也更重要的收束:系统开始把“今天为什么不能买、为什么等数据、为什么某个仓位不占新买槽、为什么一个 corporate action 会影响执行日判断”这些操作者真正会问的问题,压回 owner contract 和终端/WebUI 的共同投影里。
我后来才明白,回忆录也不能只记录代码做成了什么。它还要记录我什么时候开始不满足于“代码有改动”,开始要求系统证明它没有保留第二套真相。
证据(2026-05-27 ~ 2026-06-17)
- 2026-05-27 的其它会话摘要记录了 “backtest 和 broker trading 是否严格消费同一信号/策略层” 的只读审查:主路径落在
FeatureFrame -> signalContract -> ranked_candidates/backtest_candidates_from_feature_frame -> paper_plan_engine.build_order_plans -> paper_service.submit_selected,但latest_paper_buy_parity_service仍有 diagnostic fallback,不能当执行 authority。 - 2026-06-03 的复杂度回看记录了我要求解释系统复杂度的那次回看:结论不是“代码太多”,而是“too many overlapping truth surfaces”,维护风险集中在半迁移的 raw-dict 语义和新 typed contracts 之间。
- 2026-06-06 的摘要把并行开发方式收敛成 main coordinator + per-task worktree/branch,并把 autopush 从“所有分支都推 main”的危险行为,改成 main coordinator 推 main、feature/worktree 只推自己的分支。
- 2026-06-12 的部署与数据链路复盘明确了运行环境应该跑 exact SHA release,运行数据应与发布树分离,部署要 stop/swap/smoke/restart/heartbeat,而不是追随开发机的即时状态;同日的数据同步复盘也把全量上传改成 incremental publish / append-only manifest / remoteTruth verified。
- 2026-06-13 的治理摘要则记录了 hooks 没有实际约束力后的转向:真正需要的是主规则里的强制执行与架构纪律,而不是 reminder-only hooks。
- 2026-06-16 ~ 2026-06-17 的提交群补上了这条暗线的代码侧证据:
74240ad到bc344745把 R2 数据同步纳入 auto submit 前置;07cee8c、3d0cc785之后的 execution-day corporate-action 事实被投到 operator 输出;408cb44加强 broker replay action coverage;a6c0252加入 embedded strategy expected actions contract;6f6cc5e和4a7cf9e修正 signal-day buy slot 与 old-position target-slot 语义。
日记摘录(来自其它会话摘要;仅记录转折点)
- 5 月 27 日,我追问的不是函数有没有复用,而是回测和交易是不是同一个现实。 摘要显示主路径已经共用
FeatureFrame/signalContract/ranked_candidates,但仍存在只能用于诊断的 fallback。这个细节应该被记住,因为它解释了后来为什么总要区分 authority 和 diagnostic。 - 6 月 3 日,我第一次把“系统复杂”当成问题本身,而不是把复杂当成进度。 那次只读审查把复杂度归因到 overlapping truth surfaces、半迁移 contract、CLI/runner/paper-submit 编排层,而不是简单怪罪代码量。
- 6 月 6 日,并行开发从“多开几个 Codex”变成了有主协调和隔离工作树的工程制度。 main 负责集成,feature/worktree 只在自己的分支里推进,策略语义、sizing、stop、submit、broker mutation 这类边界必须串行。
- 6 月 12 日,数据和部署都不再被允许依赖开发机的即时状态。 运行环境要按验证过的 SHA 部署,数据同步要增量验证,开发机只做 dev/research,不再默认承担生产 writer 的角色;这其实是把“无人值守”从代码命令扩展到运行环境。
- 6 月 13 日,我开始不再接受只会提醒、不会约束的治理机制。 hooks 被判断为 advisory only,真正要写进主规则的是禁止小修小补、禁止长期 wrapper、禁止旧入口和新 owner 并存、禁止把安全边界当成果。
- 6 月 17 日的后续提交说明,操作者界面不是“更多字段”,而是把数据、券商、策略、公司行动、仓位槽位这些原本分散的事实拉回同一条解释链。 这一天的
execution CA、broker fact、strategy expected actions、signal-day buy slot相关提交,不值得写成 UI 日志,但值得作为“第二套真相继续被压缩”的证据。
第二十四章:我以前把止损修复当成补丁,后来才承认它其实是主链的一部分
6 月 18 日到 19 日这波变化,表面上看很像典型的工程事故后遗症:一串和 protective stop、reconcile、auto recovery、broker truth 有关的提交,标题一个比一个像“再补一个洞”。如果只看 commit 名字,我也很容易把它们归到“昨天的问题今天继续修”。
后来我才意识到,这次真正变化的不是修了多少个止损 bug,而是系统终于承认了一件以前总想回避的事:保护性止损修复不是提交链路外的一次善后,它本来就属于无人值守交易主链的风险收缩动作。
这件事为什么重要?因为在更早的直觉里,submit window、data wait、preflight gate、same-symbol safety 这些规则像海关,默认思路总是“先把门关好,再说别的”。可止损修复恰好不一样。它不是“再开一笔新仓”,也不是“趁着窗口再搏一下”;它是在系统已经暴露风险时,把风险往回收。如果连这种动作都被和新开仓放进同一个阻断抽屉里,那么所谓 fail-closed 最后就会演变成另一种更隐蔽的自欺:看起来没有乱下单,实际上却让缺失止损、错误止损、错配仓位在自动链路里继续悬着。
6 月 18 日先发生的,其实不是代码,而是一轮只读审计。那次审计把问题说得很难听,但也很准确:expectedBuys、targetBasket、monitor raw snapshot、progress cycle、auto recovery 这些路径如果继续互相借字段、互相帮忙解释,就会把“显示层不夺权”的老问题,悄悄拖回修复链路本身。换句话说,真正危险的不是“界面说错一句话”,而是“恢复逻辑在借错一套事实做决定”。
于是接下来的提交开始出现一种以前没那么明确的姿态:系统不再只报告 protective stop mismatch,而是给这件事安排 owner、contract、plan、broker evidence、pre-cycle 行为和 auto loop 里的归宿。Fix per-broker stop expectation reconcile、Auto repair preflight stop mismatches、Submit protective stop repairs per plan、Repair strategy-owned stop price mismatches automatically、Route stop mismatch recovery through auto loop 这一串名字连起来看,像是在把一件原本散落在 reconcile、submit、operator 输出和人工介入之间的脏活,硬生生收编回正式流程。
我后来才承认,这种收编比“再加一个安全门”更困难。加门很容易,门上贴个 BLOCKED 就行;真正难的是承认系统必须分得清两件经常被混在一起的事:
新开风险的 mutation,应该更保守;
已经发生后的风险收缩修复,反而应该在 broker truth 足够明确时尽量自动完成。
如果说前几章一直在压缩“第二套真相”,这一章真正压缩的,是另一种更讨厌的幻觉:把所有 mutation 都当成同一种危险动作。 事实不是这样。对无人值守系统来说,有些 mutation 是冒险,有些 mutation 是补洞;如果你拒绝区分它们,系统表面上会越来越谨慎,实际上却会越来越擅长把问题留到明天。
证据(2026-06-18 ~ 2026-06-19)
- 2026-06-18 的只读审计明确把问题定性为 owner-contract 缺口,而不是显示层小修小补:
expectedBuys必须回到 plan owner,monitor/status 必须只消费 owner snapshot,targetBasket不得继续参与 auto recovery identity。 - 同期新写出的主链合同状态机把
ProtectiveStop -> BrokerTruthReconcile -> AutoRecovery画进主链,并单独写明 “Protective stop and repair” 是 owner stage,显示层不能决定 missing-stop repair。这说明项目自己已经开始把“修止损”从外围补丁语言改写成 mainline contract 语言。 - 2026-06-18 晚到 2026-06-19 凌晨的提交链,把这个判断落成了具体 owner 行为:
383151fe、c125ce45、3aaed40f、ac5b134e、0ce5af65、4901e2bf、0cec2ef2、5c1d1eba、49d1f8a2、bb798548、8c1288a0、d3750f3d、94935f59,主题从 per-broker stop expectation reconcile、preflight mismatch repair、broker contract enrichment,一路收口到 “auto loop 接管 stop mismatch recovery”。 - 同一波提交还顺手暴露出另一个关键边界:
Scope same-symbol safety to entry mutations、Scope broker position adoption to current order、Fix IBKR paper mutation authority gate这些名字说明系统开始认真区分“防重复开仓”与“为已有仓位补止损/领养 broker position”不是同一类动作。这个区分以前如果做得不够清楚,就会让 fail-closed 误伤真正的风险修复。
日记摘录(来自其它会话摘要;仅记录转折点)
- 6 月 17 日,我开始把“避免人工介入”理解成分类问题,而不是再发明一个主管系统。 那份摘要最后收敛到一个很朴素的结论:
auto_recovery_policy应该是 next-action owner,manual avoidance 主要靠 recoverable / unrecoverable 的分类边界,而不是长出第二个 supervisor。这句不起眼的话,其实给后面的 stop repair 主链化铺了路。 - 6 月 18 日,只读审计第一次把“显示层别撒谎”和“修复链路别借错事实”连成了一件事。 它不是在嫌界面字段难看,而是在指出:如果
expectedBuys、targetBasket、monitor raw blob 还在不同层被重新解释,auto recovery 最后也会踩进同一个坑。这让我意识到,观测边界和修复边界其实是同一场战争。 - 6 月 18 日晚到 19 日凌晨,项目终于停止把缺失止损当“等窗口再说”的附属事件。 从
Allow preflight stop repair during data wait到Run pre-cycle stop repair before data wait,再到Route stop mismatch recovery through auto loop,叙事已经很清楚:系统宁可更早地修补已知风险,也不愿继续把它留在阻断消息里过夜。 - 6 月 18 日,我终于把“系统债很多”翻译成了一个更难听、但更有用的问题:哪些债会真的阻塞实盘。 那次直白评估没有再纠结“22 万行是不是全是屎”,而是把结论压到更可执行的层级上:不是全量还债优先,而是只先还会破坏唯一交易主链、券商 truth、保护性止损、现金约束和 kill switch 的债。这像是另一种成熟:不再把治理当成无限延期实盘的体面借口。
- 6 月 18 日晚,我也第一次把“回放”拆成了两种根本不同的问题。 一种是拿生产事故做 replay,去解释昨天为什么出事;另一种是造一个纯沙盒券商,让正式 auto chain 面对一套假的 cash、positions、orders 和 stops,看看今天这条链本身会怎么走。以前我总把这两件事混在一起,后来才明白:诊断真实事故和排练未来行为,需要的根本不是同一套证据。
第二十五章:我以为还差几个参数,后来才承认缺的是一份正式合同
6 月 20 日到 21 日这两天,表面上看像是我又掉回了自己最擅长的坑里:改配置名、补 policy、加 validation、调 WebUI、再顺手做几份 before/after 报告。要是只看 commit 标题,这一段很容易被误读成一种熟悉的、略带疲惫感的工程景象:
“昨天说系统需要治理,今天就开始写更多 contract。”
“昨天说别再小修小补,今天怎么又多了十几个策略变体名。”
“昨天还在谈实盘优先级,今天怎么又跑回回测和前端工作台了。”
我一开始也差点这么理解。因为这几天最容易让人烦躁的地方,不是没有进展,而是进展看起来太像“写更多规则”。newBuySelectionPolicy、pendingEntryReservationPolicy、pendingEntryPrefilterPolicy、pendingAddReservationPolicy、positionLifecycleMetricPolicy、initialDeploymentCapScope,这些名字一个接一个出现,很像系统又长出了一层新的术语墙。一个不小心,就会让人怀疑我是不是又在做那种项目后期最常见的幻觉:把旧 wrapper 的直觉,翻译成更多枚举值和更多 JSON 字段,然后假装这就叫收敛。
后来我才意识到,这次真正变化的不是“参数更多了”,而是研究里的暗知识,第一次被逼着写成正式合同。 以前很多行为其实不是没有答案,而是答案散落在研究 wrapper、回测观察、临时解释和历史直觉里。比如 D90 到底是“候选能补位直到可执行槽位填满”,还是“高 rank 槽位一旦锁定,后面的低 rank 不该再偷偷顶上来”;比如 pending BUY 和 pending ADD 到底该怎么预留现金;比如持仓止损的 ATR/ADR 生命周期,到底该从 held position 自己的快照里读,还是可以被当天 buyCandidates 里的同 symbol 行刷新。以前这些问题一半靠代码、一半靠记忆、一半靠“你应该知道当时是怎么想的”。而系统只要继续这样活着,就永远会在“研究说的是这个”“正式引擎跑出来的是那个”“WebUI 展示的又像第三种意思”之间来回横跳。说得更直接一点,研究 wrapper 和正式引擎长期语义不一致,本身就是这套系统反复制造假信心的来源之一:研究那边赢的,未必是正式引擎真正会执行的;正式引擎里被保留下来的,又未必还是研究当年相信的那套语义。
所以 6 月 20 日真正重要的动作,并不是又发现了一个更好的调参组合,而是把这些模糊语义一层层从“研究线索”压回“唯一可运行合同”。strict_rank_slots 不再只是一个看起来更顺眼的结果,而是被先验证、再归档、最后明确标成历史证据;pendingEntry* 和 pendingAdd* 不再只是 quick-run 里差几个点收益的玄学选项,而是被拆成谁拥有、谁消费、谁只准展示、谁已经退役;D90 也不再允许保留一堆“你如果懂历史就知道哪个更接近真正版本”的研究包袱,而是被压成一个正式 canonical config,旧 wrapper 和 sweep variants 直接退场。
这件事为什么比“再优化一点收益”更值得记?因为我终于承认了一个很工程、也很残酷的事实:研究结果如果不能被写成正式合同,它迟早会变成一种口口相传的迷信。 当系统还小的时候,这种迷信靠记忆能撑住;当系统已经有 backtest、paper、auto、replay、WebUI、readonly monitor、operator snapshot、jobs/evidence、daily ledger、research report 这些入口同时存在时,它就会立刻分裂成多套版本。你今天在研究报告里说“严格 rank 更像当年的直觉”,明天 paper plan 可能还在补低 rank,后天 WebUI 又把某个 retired config 当成可运行对象给操作者看。最后大家都说自己在尊重策略,实际上谁也说不清到底哪一份才是策略。
6 月 20 日的另一条线也很有意思:我原以为 operator WebUI 后面这一段,只会是“把只读页面做得更好看一点”。结果它开始逼我面对另一个问题:如果前端只能展示结果,而不能展示证据,它其实还是会把人重新推回猜谜。 所以 operator job evidence workbench、BacktestEvidencePanels、owner coverage board、read-model coverage、job events adapter 这些东西看起来都像“界面细节”,其实不是为了华丽,而是为了把一句非常朴素的话变成产品事实:
系统不是只要给你一个结论;
它还得把“这个结论是由哪些 owner contract、哪些 artifacts、哪些 before/after 验证支撑出来的”一起摆出来。
我后来才明白,这正好和前面几章收束到一起。前面是在压缩第二套交易真相;这一章是在压缩第二套研究真相。它们的敌人不一样,症状却几乎一样:一边是 monitor/CLI/Web 乱推断交易事实,一边是研究 wrapper、中间配置、历史命名乱占正式策略的位置。你如果不把后者也收掉,前面那些关于 owner contract、唯一主链、只读展示、证据边界的纪律,最后还是会在“策略其实到底是哪一个”这里重新漏水。
所以这两天最值得记住的,不是某个 D90 变体到底多赚了几个点,而是我终于把另一个长期偷懒的地方堵上了:以后系统不能再依赖“大家大概知道真正版本是哪个”。它必须把“真正版本是哪个、为什么是它、历史候选为什么退役、验证证据放在哪”一次写清楚。
证据(2026-06-20 ~ 2026-06-21)
- 一份 2026-06-21 改写的 D90 行为合同文档明确要求:只允许一个 D90 runnable config,旧 wrapper / parameter sweep 变体正式退役,
newBuySelectionPolicy、pendingEntryReservationPolicy、pendingEntryPrefilterPolicy、pendingAddReservationPolicy、positionLifecycleMetricPolicy、initialDeploymentCapScope被一起写成正式 owner 语义。 - 配套的验证文档也明确降级成 “historical validation evidence only”。这很关键:它们不再冒充当前可运行真相,而是退回“为什么当时会做出这个正式合同”的证据对象。
- 2026-06-20 ~ 2026-06-21 的提交链把这个判断落成了代码和配置动作:
d8add71b、3567ad42、2ae66fd0、0f3d41f4、d773680b、8f0115b0、e580a198、6b2b59a7、909994e5、495b32bb、9c81f129、97d5713c。它们一路从 strict rank slot、pending reservation/prefilter/add reservation,收口到 formal D90 contract、portfolio behavior config contracts、explicit formal strategy contracts 和 portfolio behavior contract consumption。 - 一组 2026-06-21 的验证产物提供了 before/after 对照的 backtest、trades、daily-ledger 和 skipped artifacts,说明这次不是只改文档命名,而是把“默认策略合同显式化”之后重新跑了一轮完整验证。
- operator WebUI 这条线在同一时间也发生了一个语义升级:
a7e83387(Add operator job evidence workbench)、5340665d(Improve operator WebUI evidence review UX)、d95d987f(Expose operator owner coverage board)、3415fa64(Expose operator read model coverage)、db996890(Adapt operator job events for webui)说明 WebUI 开始把“证据、coverage、job events、read-model provenance”本身当作产品内容,而不再只展示一个结果面板。 a63ec2c3、db85c79c、0fcd3989继续清理旧 Web/API 痕迹:obsolete prototype references、legacy routes、legacy control aliases 被退役。这说明“正式合同”不只作用在策略,也开始作用在前端入口的历史包袱上。
日记摘录(来自其它会话摘要;仅记录转折点)
- 6 月 18 日那次直白评估,实际上给这两天的 formal contract 收口定了一个很狠的验收标准:只还阻塞实盘的债。 这句话后来看起来像治理口号,但它真正的落地方式,不是“少写代码”,而是把研究里最会拖出歧义的那部分先写成唯一正式合同。
- 6 月 20 日,我终于不再允许“研究里的最好版本”和“系统里的正式版本”长期分居。
strict_rank、pending reservation、prefilter、ADD reservation这些东西以前都像“差一点就能追上旧 wrapper 的线索”,现在它们第一次被要求明确回答:谁已经退役,谁只是历史证据,谁才是今天唯一可运行的合同。 - 6 月 20 日,operator WebUI 也开始学会展示“为什么相信这件事”,而不只是展示“现在是什么”。 证据工作台、owner coverage board、read-model coverage、job events adapter 这些东西看起来像 UI 打磨,但它们其实是在阻止另一个老问题重演:前端只看到结论,却看不到结论背后的 owner contract 和 artifacts。
第二十六章:复盘以后,我把三条旁支补回主线边缘
这次重新翻会话摘要时,我发现前面的回忆录有一个隐蔽问题:它太容易把故事讲成“策略系统一路长大”。这当然是主线,但不是全部。
有些东西看起来像旁支,甚至不在“回测收益”或“自动交易链路”里,却改变了我后来判断系统的方式。它们不应该抢主线的位置,但如果完全不写,读者会误以为这些纪律是后来突然冒出来的。
第一条暗线,是只读券商读路径。
在两篇 X 文章真正催生量化策略之前,我已经做过一次很具体的尝试:让系统只读地读取券商账户、持仓和行情,再把这些信息转成每天的市场/持仓简报。那时的目标还不是自动下单,而是先让系统能像一个交易员那样说清楚:现在持有什么,风险在哪里,哪些机会只是观察,哪些机会需要触发价、失效价和仓位上限。
这条支线后来没有成为量化引擎的主角,但它先教会了我一件事:只读不是“低级功能”,只读本身就是一种产品能力。 一个插件报 READY,不代表账户、持仓、现金、PnL、期权链都可信;某些字段缺失时,系统应该明确说缺失,而不是用其它字段凑一份看起来完整的账户摘要。这个教训后来迁移到 paper auto、monitor、WebUI 和 notification 上,变成同一条纪律:观察层可以帮助理解现实,但不能自己编现实。
第二条暗线,是运行时 provenance。
我以前容易把“系统已经重启到新版本”当成一个单一事实。后来运行时应用和 operator 终端反复给我上课:一套自动化可以正确加载某个 commit,heartbeat 也能证明启动分支、dirty 状态、启动时间和运行进程都对,但操作者终端仍然可能显示旧 frame、重复 frame 或残留历史内容。
这听起来像 UI 问题,其实不是。它说明证据本身也分层:运行时加载证据只能说明“机器实际跑了哪份代码”,不能自动说明“人眼看到的是不是当前事实”。如果把这两层混在一起,就会出现一种很危险的满足感:底层已经对了,于是上层也被默认当成对的。后来我才更愿意把验收拆开说:runtime/load status、operator visible status、user goal acceptance,不是同一个东西。
第三条暗线,是旧入口退役。
这一点尤其容易漏,因为它不像缺止损那样刺眼,也不像回测收益那样有数字。某个旧 TUI 入口被移出命令或注册表,看起来像“退役完成了”;但如果文档、测试、残留代码和操作者心智还在引用它,那么它就仍然活着,只是换了一种更难发现的方式活着。
这件事和第二十五章的正式策略合同其实是同一个问题。旧入口、旧 wrapper、旧研究变体、旧控制别名,表面形态不同,本质都在问一句话:系统到底允许几份现实同时存在? 如果只是把旧路径藏起来,而不是端到端退役,它迟早会在测试、文档、UI、脚本或人的记忆里重新出现。
所以,这次复盘后我更愿意把一些支线留在主线边缘:只读券商插件、交易员式简报、Trump 披露监控、runtime provenance、旧 TUI 退役、自动化噪音清理,它们不一定都值得各写一章,但它们共同塑造了这套系统的性格。
它们让我从“能不能找到好策略”,一步步走到另一个问题:
当系统越来越大、入口越来越多、会话越来越分散时,我还怎么知道自己看到的是同一个现实?
这可能是这一个月真正贯穿始终的问题。
证据(2026-05-12 ~ 2026-06-18)
- 2026-05-12 的只读券商工作流确认:账户/持仓读取、行情读取、历史 OHLCV、交易员式简报和候选研究可以组成一条独立的“观察能力”,但它必须保持 read-only,并且不能把缺失的账户字段脑补成完整事实。
- 同一条支线还把每日输出从泛泛市场评论改成了带触发价、失效价、仓位上限和行动状态的交易员式简报,同时把公开披露监控从主市场简报里分离出来,避免不同信息源混成一个结论。
- 2026-06-13 到 2026-06-15 的运行时应用复盘确认:runtime provenance 能证明加载了哪份代码,但不能自动证明操作者终端展示的是当前事实;可见终端仍可能因为旧 frame 或重复 frame 未通过产品验收。
- 2026-06-15 的 auto-recovery cycle facts 收口说明:raw
BROKER_WAIT、TARGET_MISMATCH、continueAutomation这类摘要字段不能继续驱动 next action;自动循环需要显式 cycle facts contract,否则必须 fail-closed。 - 2026-06-18 的只读审计确认:旧 TUI 退役不是移除一个命令就结束;只要文档、测试、注册表或残留实现仍然引用旧入口,它就仍然是迁移未完成,而不是历史已经清空。
附录 A:关键时间线
| 日期 | 记住的事 |
|---|---|
| 2026-05-12 | 只读券商读取和交易员式简报支线成形:系统先学会“看见账户”,再谈执行账户 |
| 2026-05-15 | 从两篇 X Articles 提炼出双引擎策略框架 |
| 2026-05-15 | 第一版静态 54 股回测:150.97%,最大回撤 -25.93% |
| 2026-05-15 | 点时 NASDAQ-100:60.92%,最大回撤 -22.76% |
| 2026-05-15 | 点时 S&P 500:92.75%,最大回撤 -31.93% |
| 2026-05-16 | V2 high-beta pyramid 出现,回测约 528.81% |
| 2026-05-17 | FIX.US / MRVL.US 模拟订单提交到 LongBridge |
| 2026-05-18 | 本地卖出计划与 LongBridge 空持仓冲突,卖单被阻断 |
| 2026-05-19 | V6 基线约 748.76%,但 prelive = FAIL_FOR_FULL_AUTO_LIVE |
| 2026-05-21 | 发现 score 不是胜率概率,和 pnlPct 相关很弱 |
| 2026-05-21 | vectorbt / Backtrader / canonical 差异开始系统化审计 |
| 2026-05-23 | 建立 git 基线,系统从原型转工程 |
| 2026-05-24 | Task 018 因短历史 fail-closed |
| 2026-05-26 | 严格 5 年 820.54%,审计置信度 44/100 Low |
| 2026-05-26 | 10 年 16375.37%,审计置信度 26/100 Low |
| 2026-05-27 | 只读审查确认 backtest / scan / paper 主路径共用 FeatureFrame 与 signalContract,同时把 diagnostic fallback 标成非权威 |
| 2026-05-30 | IBKR 纸面桥接出现断桥后,补齐恢复/验证/会话策略与审计证据链 |
| 2026-05-30 | 把“从证据出发的最小改动流程”固化为耐用标准 |
| 2026-06-01 | 纸面系统开始“领养”券商仓位:止损验证成为硬门槛;preflight reconcile 有等待上界;auto runner 状态更面向操作者 |
| 2026-06-02 | 工程治理 baseline 被正式固化:quick / 5y / 10y 不再只是临时回测 |
| 2026-06-03 | FeatureFrame、signal_snapshot、signal_candidates 开始成为共享叙述边界;同日的复杂度复盘把债务指向 overlapping truth surfaces |
| 2026-06-04 | deferred stop recovery 与 flatten/cancel 审计轨迹暴露出“恢复逻辑”本身也在改写系统现实 |
| 2026-06-05 | 去掉本地 cash / stop recovery fallback,并把 paper mutation / recovery facts 全部收紧到 broker truth |
| 2026-06-06 | 发现 capacity / expected buys / 实际提交并不天然同义,paper entry selection 被重新绑回 strategy capacity |
| 2026-06-06 | 并行开发被重新定义为 main coordinator + one worktree per task,交易语义和 broker mutation 必须串行 |
| 2026-06-07 | 正式承认 final40 的信号核心可以冻结,新的系统默认改进主要来自 portfolio behavior 语义,而不是再换一套选股故事 |
| 2026-06-12 | VPS/CI/CD 设计把运行权威从开发机即时状态转向 exact SHA release、外置运行数据和受控 heartbeat 验证 |
| 2026-06-12 | Parquet/R2 数据链路从重复全量上传转向增量验证、remoteTruth 和 writer lease |
| 2026-06-13 | 开始把 durable runner、monitor、WebUI、notification 的状态统一收口成只读 owner contract,而不是让每个入口各自解释“系统现在怎样” |
| 2026-06-13 | reminder-only hooks 被判定不足,治理转向中文主提示里的强制架构纪律和验收标准 |
| 2026-06-14 | Shared Engine、readonly monitor、operator WebUI 开始围绕同一份只读状态层收敛,但仍强行保持 surface 分离,不把监控和操作混成一个产品 |
| 2026-06-15 | 统一 Web API v1、notification readiness/dry-run、AOC 连续验收共同说明:观察层可以更完整,但不能更有权 |
| 2026-06-15 | runtime provenance 和操作者可见验收被拆开:加载了正确代码,不等于终端已经正确呈现当前事实 |
| 2026-06-15 | auto loop 的 next action 被压回 explicit cycle facts,raw status 不再足够驱动恢复决策 |
| 2026-06-16 | readonly backtest run/symbol review 落地后,回测开始从“结果文件”变成可追问的只读证据对象 |
| 2026-06-17 | execution-day corporate-action、broker facts、strategy expected actions 和 target-slot 语义继续被压回 owner contract 与 operator 投影 |
| 2026-06-18 | 只读审计把 expectedBuys、targetBasket、monitor raw snapshot、auto recovery 的耦合定性为 owner-contract 问题,而不是字段级小修补 |
| 2026-06-18 | 旧 TUI 退役被重新理解为端到端迁移问题:命令消失不等于文档、测试、注册表和残留实现都已退出 |
| 2026-06-18 | 直白的系统评估把“工程债很多”收束成更可执行的优先级:只先还会阻塞实盘安全闭环的债 |
| 2026-06-18 | 回放语义被拆成两类:生产事故 replay 继续服务诊断,纯沙盒 broker replay 则用来排练 auto chain 本身 |
| 2026-06-18 | protective-stop mismatch / repair 被正式收口进 owner contract、preflight 行为和 broker evidence,不再只是 reconcile 之后的附属处理 |
| 2026-06-19 | stop mismatch recovery 被路由进 auto loop;系统开始明确区分“新增风险的 mutation”和“收缩风险的修复 mutation” |
| 2026-06-20 | D90、strict-rank、pending reservation/prefilter/ADD reservation、lifecycle metric 开始从研究线索被压成 formal strategy contract 体系 |
| 2026-06-20 | operator WebUI 不再只展示结果,开始把 evidence review、owner coverage、read-model coverage、job events 一并纳入只读工作台 |
| 2026-06-21 | formal strategy contract 被进一步收口成唯一 canonical config,旧 wrapper / retired variants 从默认叙事里退场;portfolio behavior contract consumption 也被统一 |
附录 B:关键决定
| 阶段 | 决定 | 为什么 |
|---|---|---|
| X 文章后 | 不承诺稳健盈利,只做可验证系统 | 文章不能替代证据 |
| 第一版回测后 | 不把 150.97% 当作策略确定性收益 |
自选热门股偏差太大 |
| 扩大股票池 | 用点时 NASDAQ-100 / S&P 500 测试 | 降低幸存者偏差 |
| V2 纸面交易 | 提交前必须只读检查券商账户 | 本地状态不能替代券商事实 |
| V6 出现后 | 升级信号源,不开全自动实盘 | prelive 未通过 |
| 架构阶段 | 先补 git / service / contracts / docs | 原型期边界太乱 |
| 安全审计 | cash 是硬约束,买入必须有止损 | 防止融资和裸买入风险 |
| 数据审计 | unadjusted 数据语义保持一致 | 防止复权污染回测 |
| Task 018 | coverage 不足就 fail-closed | 不为通过验证削弱门槛 |
| 10 年审计 | 巨额收益只作为现象,不作为生产证据 | 证据质量仍低 |
| IBKR 纸面桥接 | 把连接可靠性当成需求:可恢复或 fail-closed + 审计可追溯 | “没下单”不等于“状态可信” |
| 只读券商读取 | 把账户/持仓/行情读取标准化为 read-only 能力,并明确缺失字段不能被推断补齐 | 观察账户也是交易系统的一部分,但不能因此拥有交易事实 |
| 工作流 | 写下可复用的开发流程标准 | 防止靠记忆反复踩坑 |
| 工程治理 | 先冻结 baseline 和 contracts,再做结构性拆分 | 防止“只是重构”悄悄改掉策略语义 |
| 信号一致性 | 区分 authoritative execution path 与 diagnostic fallback | 防止用诊断路径证明交易路径一致 |
| 恢复与修复路径 | 恢复逻辑也必须以 broker truth 为准,不能本地脑补 cash / stop / ownership | 补救路径同样在改写交易现实 |
| 组合行为推广 | 在信号核心不变的前提下,把 portfolio behavior 作为正式执行语义与系统默认来验证和推广 | 逼自己承认收益改善不一定来自“更聪明的信号” |
| 并行开发 | main coordinator 负责集成;每个任务使用独立 worktree/branch;交易语义和 broker mutation 串行 | 提高速度不能换来事实源冲突 |
| 运行部署 | 运行环境应运行验证过的 exact SHA release,开发机保持 dev/research 角色 | 防止无人值守运行依赖开发机即时状态 |
| 数据同步 | R2/Parquet 采用增量验证、remoteTruth 和 writer lease,而不是每天重复全量上传 | 数据新鲜度必须可证明,也必须可承受 |
| 治理机制 | 弱 hooks 不能替代强执行纪律;验收要看目标、旧逻辑清理和 owner 边界 | 提醒不等于约束,handoff 不等于完成 |
| 观察层建设 | monitor、WebUI、runtime status、notification 都只能消费 owner contract,不能长出第二套 authority | 防止“只读展示”慢慢演变成另一套控制面 |
| 通知外发 | 先 readiness / dry-run,再显式 send gate;通知失败也不能改变交易安全门 | 观察层可以提醒人,但不能替系统作主 |
| 操作者终端 | 把“安静、当前、中文可判断”视为 runtime 级需求,而不是样式优化 | 否则操作者会在界面里再做一次日志考古 |
| 运行时 provenance | 把“加载了哪份代码”和“操作者看见什么”拆开验收 | 正确运行不等于正确呈现,不能用底层证据替代产品验收 |
| 自动循环事实 | next action 必须依赖 explicit cycle facts,而不是 raw status 摘要字段 | 否则恢复逻辑会在看似合理的状态词里重新获得自由发挥空间 |
| 回测观察层 | 回测 run、progress curve、symbol review 也必须走只读 facade,并携带 provenance / freshness / non-authoritative marker 语义 | 防止高收益结果再次以“默认答案”姿态绕过追问 |
| 操作者事实链 | execution-day CA、broker facts、strategy expected actions、slot accounting 必须共同解释“今天系统为什么这样做” | 界面不应替不同 owner 重新发明一套解释 |
| 止损修复主链化 | protective-stop repair 是风险收缩动作,不应被 submit window / data wait / entry safety 误当成新开风险而一并阻断 | 否则系统会把“没有继续下单”误包装成“已经足够安全” |
| 恢复语义 | 必须区分 entry mutation 与 repair mutation,并让 auto loop 在 broker truth 足够明确时优先完成后者 | 无人值守系统不能只会阻断,还得会把已知风险收回去 |
| 实盘优先级 | 不为“全量还债”无限延期实盘,只先还阻塞实盘安全闭环的债 | 否则治理会变成另一种看起来高尚的拖延 |
| 回放语义 | 生产事故 replay 和纯沙盒 broker replay 必须分开定义、分开验收 | 诊断过去和排练未来依赖的事实源并不相同 |
| 正式策略合同 | 研究里有效的线索,必须被压成唯一 runnable config 和共享 owner contract;退役变体只能做历史证据 | 否则系统会长期依赖口口相传的“真正版本” |
| 研究/正式语义一致性 | research wrapper 里的收益逻辑、skip 语义和正式引擎执行语义不能长期分居 | 否则回测里赢的东西,未必是系统真正会执行的东西 |
| 旧入口退役 | 旧 TUI、旧 wrapper、旧控制别名必须端到端退役,不能只移除一个入口 | 半退役路径会继续在文档、测试、UI 或人的记忆里制造第二套现实 |
| 证据工作台 | WebUI 不只展示结果,还要展示 job evidence、owner coverage、read-model coverage 和 before/after artifacts | 否则前端会把操作者重新推回猜谜,而不是审阅证据 |
附录 C:代码演进索引
为了后续公开发布,这里不再列内部目录和文件清单,只保留对外有叙事价值的演进线索。
V1 / V2 阶段的核心形态可以概括成四件事:
- 一套最小可跑的策略配置与回测脚本。
- 一套把市场状态、候选筛选和日线执行语义串起来的 Node 工具。
- 一套会自己累计状态、日志、订单和权益曲线的纸面账户原型。
- 一份不断被补写的策略规则说明,试图把文章直觉压成可执行规则。
V6 阶段的关键提交:
| Commit | 主题 |
|---|---|
20b248e |
建立版本控制基线 |
ad02af0 |
task service request contract |
171b1a9 |
market data service contracts |
890c8da |
data health service contracts |
58b966b |
backtest artifact contracts |
0b6b1bc |
阻止无初始止损买入提交 |
935b661 |
reconciliation 要求保护性止损 |
c628174 |
same-bar ambiguity report |
dd22acd |
data integrity report |
66bfe97 |
只读 paper TUI monitor |
18336af |
replay comparison report |
d8cd38b |
shared strategy engine |
854674f |
shared entry sizing planner |
ef66879 |
shared add-on planning checks |
e7af45a |
移除 live update CLI subprocess |
0686b7e |
strategy CLI 走 services |
4ebac6b |
execution lock 和 live paper TUI |
f4f0c4d |
paper workflow gates |
bf660d9 |
DuckDB import transactions |
3c3c11d |
operator readiness gates |
358a703 |
Harden IBKR paper order verification |
d9b2f22 |
Recover IBKR paper session bridge failures |
1609e55 |
Clarify IBKR paper session reuse policy |
2cc4314 |
docs: add development workflow standard |
2df2f6a |
Adopt broker positions with verified stops |
0dc11b1 |
Allow verified broker position adoption in preflight |
52509de |
Bound broker preflight reconcile waits |
bbb3e58 |
Keep auto paper running during submit window waits |
7482d5f |
Render paper auto runner status for operators |
3020c9b |
抽离 PIT universe data contract |
dc032f5 |
抽离 corporate action data contract |
b36103a |
抽离 daily bar data contract |
12bf143 |
分离 signal snapshot 与 candidate generation |
0e2ebb0 |
Harden FeatureFrame market risk boundaries |
19b07ba |
Harden deferred paper stop recovery |
658d860 |
Use broker truth for paper parity and plan blockers |
81713a3 |
Recover missing stops from broker truth |
961f0b4 |
Gate submit lifecycle on broker truth |
a30d84d |
Use broker-owned evidence for position adoption |
86e5148 |
Use broker truth for paper cancel flatten parity |
6bec394 |
Require broker truth for paper mutations |
aa28e07 |
Remove local cash and stop recovery fallback |
2ce26d5 |
Require broker-confirmed paper recovery facts |
f295ac6 |
Harden simulated paper broker reconciliation |
1583f0d |
Harden paper stop recovery monitoring |
c2a169d |
Align paper entry selection with strategy capacity |
dbe4b29 |
Add portfolio behavior validation candidates |
c4551e7 |
Promote portfolio behavior semantics to formal execution |
8fd9d6a |
Set portfolio behavior strategy as system default |
58bb563 |
Add strategy robustness validation runner |
9ffb4a7 |
Migrate runtime to Python 3.13 |
18ae782 |
add read-only auto runtime status contract |
e97073b |
enable verified R2 data sync gates |
fb831f2 |
improve auto operator status visibility |
f6c267b |
shared engine operator readiness read model |
36a0664 |
close notification production readiness |
7df6f1a |
read notifications from system config |
91a2514 |
operator workflow drilldown workspace |
5c58d26 |
unified Web API v1 facades |
dd34e23 |
shared engine service-control contracts |
2a93f20 |
Web API consumes shared service-control status |
f36b407 |
fix auto operator terminal frame repaint |
acc29f4 |
drive startup auto operator watch frame |
22aee68 |
operator WebUI snapshot core facade |
68e2599 |
wire operator WebUI readonly read models |
4596985 |
enrich readonly data pipeline read model |
eae94fb |
stream readonly backtest progress curves |
eba264d |
add readonly backtest symbol review |
e0eaaf8 |
complete auto recovery cycle facts contract |
74240ad |
automate Parquet R2 data sync before auto submit |
7e0528b |
use incremental R2 verification in auto sync |
9de5a27 |
require remote sync before auto data gate passes |
b4c699f |
use target basket contract for plan reconciliation |
24cd673 |
stabilize local readonly operator WebUI bootstrap |
fea0f19 |
show execution-date corporate action facts |
408cb44 |
strengthen broker replay action coverage |
a6c0252 |
add embedded strategy expected actions contract |
03b29bb |
add auto terminal renderer boundary |
6f6cc5e |
fix signal-day buy slot accounting |
4a7cf9e |
fix old positions in new-buy target slots |
383151fe |
fix per-broker stop expectation reconcile |
c125ce45 |
auto repair preflight stop mismatches |
3aaed40f |
respect protective stop recovery authority |
ac5b134e |
fix protective stop repair ownership evidence |
0ce5af65 |
submit protective stop repairs per plan |
4901e2bf |
auto-adopt broker positions for stop recovery |
0cec2ef2 |
repair recoverable preflight missing stops |
5c1d1eba |
fix protective stop entry-fill lifecycle owner |
49d1f8a2 |
fix pre-cycle recovery gating |
bb798548 |
allow preflight stop repair during data wait |
8c1288a0 |
run pre-cycle stop repair before data wait |
d3750f3d |
repair strategy-owned stop price mismatches automatically |
94935f59 |
route stop mismatch recovery through auto loop |
d8add71b |
Add formal strict new-buy rank slot policy for strategy validation |
3567ad42 |
Add formal pending entry reservation policy |
2ae66fd0 |
Add formal pending entry prefilter policy |
0f3d41f4 |
Add formal pending ADD reservation policy |
d773680b |
Formalize D90 pending reservation contracts |
8f0115b0 |
Standardize D90 behavior mode contract |
e580a198 |
Formalize position lifecycle metric policy |
6b2b59a7 |
Add formal candidate-conditioned lifecycle metric policy |
9951641e |
Add formal backtest daily ledger reconciliation |
c76df10d |
Add research credibility gate runner |
a7e83387 |
Add operator job evidence workbench |
5340665d |
Improve operator WebUI evidence review UX |
d95d987f |
Expose operator owner coverage board |
3415fa64 |
Expose operator read model coverage |
db996890 |
Adapt operator job events for webui |
a63ec2c3 |
Retire obsolete web API prototype references |
db85c79c |
Retire legacy web API routes |
0fcd3989 |
Retire legacy web control aliases |
909994e5 |
Standardize formal D90 strategy contract |
495b32bb |
Standardize portfolio behavior config contracts |
9c81f129 |
Require explicit formal strategy contracts |
97d5713c |
Unify portfolio behavior contract consumption |
附录 D:代码量变动统计(git numstat 汇总)
我后来发现,很多“系统变得更强了”的错觉,来自一种很偷懒的脑补:看一眼 commit 数量,或者看一眼“改了很多行”,就自动脑补成“做了很多正确的事”。
所以这里留一个很粗糙、但很诚实的统计:只记录代码量的变化,不替它解释意义(尤其是当某一天出现几十万行级别的 churn 时,这个数字本身更像是在提醒你:今天很可能发生了重构、归档、自动生成或批量迁移,而不是“天降 alpha”)。
以下统计以当时的主仓库为准,按 2026-05-30 起至 2026-06-21 的 git log --numstat 聚合。6 月 8 日和 6 月 10 日没有新增提交,所以表里没有单独列出:
| 日期 | 变动文件数(累计) | 新增行 | 删除行 |
|---|---|---|---|
| 2026-05-30 | 422 | 18,489 | 1,535 |
| 2026-05-31 | 430 | 432,353 | 421,823 |
| 2026-06-01 | 347 | 13,930 | 1,368 |
| 2026-06-02 | 199 | 10,264 | 2,290 |
| 2026-06-03 | 82 | 3,945 | 1,037 |
| 2026-06-04 | 267 | 7,186 | 823 |
| 2026-06-05 | 339 | 15,020 | 1,521 |
| 2026-06-06 | 2,930 | 26,764,195 | 528 |
| 2026-06-07 | 337 | 5,986 | 3,448 |
| 2026-06-09 | 16 | 584 | 36 |
| 2026-06-11 | 124 | 9,717 | 2,237 |
| 2026-06-12 | 484 | 30,456 | 7,807 |
| 2026-06-13 | 346 | 44,590 | 2,293 |
| 2026-06-14 | 658 | 26,032 | 3,705 |
| 2026-06-15 | 292 | 31,216 | 4,366 |
| 2026-06-16 | 644 | 37,219 | 5,111 |
| 2026-06-17 | 209 | 9,247 | 1,207 |
| 2026-06-18 | 308 | 16,451 | 7,906 |
| 2026-06-19 | 533 | 28,939 | 27,219 |
| 2026-06-20 | 502 | 19,708 | 19,198 |
| 2026-06-21 | 38 | 697 | 1,316 |
补记到 2026-06-21 后,区间总计为:9,507 files,+27,526,224 / -516,774。这个数字继续变大,并不意味着系统突然更接近可用;它更像是在提醒我,最近这几天真正发生的不是“又写了很多代码”,而是“系统终于开始把研究语义、运行语义和展示语义分别钉在正式合同和证据对象上”。如果只看 churn,很容易把这段历史误读成“又一波大改”;但如果把它放回前面几章,它其实更像一次迟来的清账。
尾声:这不是一个完成品
这份回忆录记录的不是一个已经成功的量化系统。
它记录的是我第一次把一个交易想法逼近真实系统时,逐渐学会怀疑自己的过程:每当我想说“差不多了”,系统就会用事实提醒我“还早呢”。
如果以后这个系统真的变得可用,最值得记住的也许不是某一次回测收益,而是这些早期的阻断、返工、怀疑和边界。因为它们让系统从一个会报喜的脚本,慢慢变成一个会说“不”的工具。