时间范围: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

我没有因此失望,反而觉得这是更可信的进展。

因为它说明两件事同时成立:

  1. 第一版不是纯粹靠未来函数或代码错误赚钱。
  2. 第一版确实含有明显的主题偏差、股票池偏差和数据缺口。

这是第二个重要转变:我开始不再只看收益,而是看收益在被去偏差之后还剩多少。

后来我问,胜率低是不是没有严格遵守策略。答案也很重要:不是。趋势突破系统本来就可能胜率不高,它的关键不是每一笔都准,而是小亏损可控,少数大趋势足够大。

从那时开始,我对“胜率”这件事不再那么执着。我更在意的是:输的时候是不是按规则输,赢的时候是不是能真的拿住。

第三章:纸面交易暴露了“两个真相”

回测能跑之后,我自然想让系统每天跟踪——人一旦尝到“自动化”的甜头,就很容易想把它升级成“每天都给我一个答案”。

于是项目进入 V2 阶段,出现了高 beta 金字塔纸面交易。这个版本回测接近 528.81%,看起来比前面的版本更有攻击性。它生成过两笔 LongBridge 模拟买入:

标的 方向 数量 Setup LongBridge 订单号
FIX.US Buy 3 pocket_pivot 1240354460052504576
MRVL.US Buy 45 pocket_pivot 1240354703062089728

那时候最容易犯的错,是把“本地策略状态”和“券商账户状态”混为一谈。就像把自己备忘录里写的“我有钱”当成银行余额。

本地纸面账户里可能显示我有 FIXMRVL,也可能生成卖出计划。但 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 被改造成更像券商工作台:

  • 顶部只放压缩状态。
  • SignalsPlans 放在主区域。
  • 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 根日线也不是随便设出来的门槛。它来自策略自己的指标需求:

  • ret126
  • ret63
  • 52 周高低点
  • SMA200
  • warmup

如果数据不够,很多指标看起来能算,实际已经不完整。于是后续形成了“两遍跑”的原则:

  1. 先用当前数据跑,知道当前结论是什么。
  2. 补齐更长历史后再跑,比较结论是否只是样本窗口幻觉。

这也是系统从“测收益”变成“测结论是否稳定”的过程。

第十二章:最夸张的数字,最低的置信度

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 的纸面环境不是一个本地函数调用,它更像一座有风、有雾、还会临时修路的桥:带会话、带桥接、会断、会抖动、会恢复失败。你可以把提交权限关掉,但你关不掉“半连接状态”带来的幻觉——一边是本地的“我以为还活着”,另一边是券商的“其实早断了”。

断桥真的发生时,我才意识到:如果系统缺少恢复策略和审计证据链,那么“我没下单,所以安全”就像在黑暗里摸到墙就宣布自己已经到家。因为下一次你想把开关打开时,你甚至不知道自己站在哪个状态上:是“干净的起点”,还是“昨天的烂尾现场”。

于是那天我做了两个决定:

  1. 不把“偶发断线”当成环境噪声,而是把它当成需求:要么能恢复,要么能 fail-closed,并且能解释发生了什么。
  2. 把这类系统建设写成耐用流程标准:以后每次改动都必须从当前证据开始,而不是从记忆开始——记忆这种东西,在交易系统里属于最不该当作依赖的第三方服务。

这听起来像是在写项目管理,但对交易系统来说,它其实是一种自保:我宁愿慢一点,也不想再被“看起来能跑”的假象牵着走。

证据(2026-05-30)

  • 近期提交集中在 IBKR 纸面桥接的恢复与验证(例如 d9b2f224c233be990d68a75008db),并补齐了“会话复用策略”“订单验证”等边界说明。
  • 纸面运行开始留下可追溯的审计日志、转发快照和账户状态证据。
  • 开发流程标准和 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 生命周期持久化等:例如 f9cd5951132d2a328e603 及其前后多条 commit)。
  • “final40” 的过拟合/窗口敏感性验证也被落成正式报告,专门比较了 compound vs fixed、10y vs 5y 以及多窗口拆解下的差异。

第十五章:纸面交易终于承认“券商才是现实”,于是我学会了“领养”与“等到有上限”

在第十四章里,我把自动化当成一个需要回执的“产品能力”。我以为做到这一步,我就能安心一点:至少它做了什么我看得见。

但很快,一个更扎心的问题冒出来:我看得见,不代表我看得对。
因为当系统跨越了“本地推演”和“券商事实”两张表,它最大的敌人从来都不是 bug,而是“你以为你们在同一个世界里”。

我曾经习惯用一种很偷懒的叙述安慰自己:纸面账户是我自己维护的状态机,所以它应该更“干净”、更“可控”。券商那边的持仓和订单,只要我不让它真的成交、或者不把真实权限打开,就不会对我构成威胁。

后来我被现实按在地上摩擦了一次:券商那边可能已经有仓位/有止损/有订单,而我的纸面状态却还在“从零开始”的童话里。你可以继续假装看不见,但那样你就把系统推向一种很危险的未来——某天你真想把自动化开大时,你会发现自己连起点都不是同一个。

这件事迫使我承认一个不怎么优雅、但很诚实的事实:纸面系统不是“模拟出来的世界”,它只是“对真实世界的一种解释”。
如果解释和现实不一致,你要么纠错,要么承认自己暂时没有资格继续解释。

于是我做了两个看起来很工程化、但其实很“心理建设”的决定:

  1. 允许“领养”券商仓位,但前提是它必须通过验证:尤其是保护性止损这件事,必须能证明。
  2. 把“等待券商给出答案”变成一个有上限的行为:自动化可以等,但不能无限等,更不能因为等而把整台机器冻死在 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_PLANENGINEERING_GOVERNANCE_BASELINES 这种文档补齐,明确哪些改动算“行为保持”,哪些一旦碰到就必须重新对账。
  • 再去拆数据合同、候选合同、snapshot 合同、FeatureFrame 边界,而且每拆一刀都要求有 focused tests 和 parity 证据陪着。

这一步看起来不像“做策略”,更像在给系统上手术前签知情同意书。但它非常重要,因为它改变了我的默认心态:

以前我会想,先把结构理顺,回头再验证结果。
后来我逼自己接受另一种更慢的顺序:先证明结果没被我碰歪,才配谈结构变漂亮。

真正让我觉得这个转变值得写进回忆录的,不是多了几份文档,而是系统里第一次出现了一种很严肃的态度:它不再把“重构”默认当成进步,而是把它当成一种需要举证的行为。

这也顺手修正了我对“工程治理”的误解。我原来总觉得治理是效率的反义词,是功能做完之后才补的流程外壳。后来才发现,在交易系统里,治理更像是防止你把未来收益偷换成当前自信的装置。没有它,所谓“我只是抽了一层”很容易变成“我偷偷改了一个世界,却没留指纹”。

而 6 月 3 日那串和 FeatureFramesignal_snapshotsignal_candidates 有关的提交,让我更确信这条路是对的。因为它们传达的不是“我们又加了几个模块”,而是另一句更值得记住的话:

以后如果连回放、候选、风控边界都要共用同一份叙述,那这份叙述就必须先被定义、被冻结、再被复用。

我到这时才真正明白,系统成熟不只是“能自动跑”,也不只是“知道该停”。它还有第三层能力:

当你必须改它时,它能逼你证明自己没有把它改成另一个东西。

证据(2026-06-02 ~ 2026-06-03)

  • 工程治理被正式写成工作计划和验收清单,明确要求在策略、特征、执行语义改动前先对照 baseline,并把 simulated-paper 仍视为 production-like safety surface。
  • baseline 口径也被固定成可复用配置,并配套生成了 quick、5 年、10 年三层基线产物。
  • 10 年基线报告显示系统开始把“漂亮收益”变成“必须可复核的口径”:compound 26493.86%、fixed 677.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 fallback
  • Require broker truth for paper mutations
  • Require 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:例如 19b07ba658d86081713a3961f0b4a30d84d86e5148,说明这不是孤立修补,而是在把“券商事实优先”推进到恢复、提交、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,这些东西大致说的是同一回事。它们可能写在不同文件里,名字有点不同,但应该只是“展示层说法不一致”,不至于改变系统实际会买什么。

后来事实证明,这个想法还是太轻率了。

开盘后的只读审计先给了我一个不舒服的画面:运行日志里明明写着这轮共享信号下实际提交的是 MRVLVSHBB 三个名字,maxNewOrders=8;但另一边的 parity / reconcile 语义又开始把 STRLMRVLTWLOVSHOSCR 当成 expected buys 来看。到了 6 月 6 日的 LongBridge parity,系统甚至一边说 missingExpectedSymbols=[],一边又明确报出 extra_actual_symbol=BB,同时 MRVLVSH 的止损检查仍然失败。

这种不一致最麻烦的地方,不是“报表不好看”,而是它会让人误以为自己在做对账,实际上却在拿两套不同的容量语义互相比较。你以为自己在检查执行是否偏离策略,结果可能只是 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,提交符号是 MRVLVSHBB;后续 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,同时 MRVLVSH 的 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 根本不是被“找到”的,而是被“不乱拿、不乱加、不乱放大”保留下来的。

更关键的是,这次它没有停在研究目录里。

dbe4b29c4551e78fd9d6a 这一串提交,已经不只是“候选更优”,而是在把组合行为语义正式提升成系统默认路径。紧跟着的 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 Engine operatorReadiness 只读聚合,把 5c58d26 定义为 Unified Web API v1 facades 只读门面,把 a318097 / 2a93f20 定义为 service-control 发现接口和只读 consumer,并明确要求 POST start/stop/restart 保持 disabled 403
  • 同一份路线图还把 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 收口成 displayOnlyreadyAuthority=nonecoverageAuthority=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 / e2fe05ccf10636 / 7698d8f994c4e7 / c2273fef36b407 / 65b678facc29f4 / e74e0a6 又反复留下另一组证据:每一轮都要求 heartbeat、parity、run log crash signature 和主 pane 输出一起过关,说明系统已经把“操作者看见什么”当成 runtime 级验收,而不是纯前端润色。
  • 其它会话在 2026-06-14 的 observability 摘要里还记录了一次很典型的中间结论:raw ibind/WebSocket stderr 被压进结构化的 recentTransportErrors,并明确标注 displayOnly=Trueauthority=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=trueactionable=false,并要求 payload 带上 artifactFreshnessbacktestProvenance、selected-run artifact path、tradesPrevieweventsPreview 等字段。也就是说,系统不仅要给结果,还要说明“这是谁的结果、从哪份 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 的提交群补上了这条暗线的代码侧证据:74240adbc344745 把 R2 数据同步纳入 auto submit 前置;07cee8c3d0cc785 之后的 execution-day corporate-action 事实被投到 operator 输出;408cb44 加强 broker replay action coverage;a6c0252 加入 embedded strategy expected actions contract;6f6cc5e4a7cf9e 修正 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 CAbroker factstrategy expected actionssignal-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 日先发生的,其实不是代码,而是一轮只读审计。那次审计把问题说得很难听,但也很准确:expectedBuystargetBasket、monitor raw snapshot、progress cycle、auto recovery 这些路径如果继续互相借字段、互相帮忙解释,就会把“显示层不夺权”的老问题,悄悄拖回修复链路本身。换句话说,真正危险的不是“界面说错一句话”,而是“恢复逻辑在借错一套事实做决定”。

于是接下来的提交开始出现一种以前没那么明确的姿态:系统不再只报告 protective stop mismatch,而是给这件事安排 owner、contract、plan、broker evidence、pre-cycle 行为和 auto loop 里的归宿。Fix per-broker stop expectation reconcileAuto repair preflight stop mismatchesSubmit protective stop repairs per planRepair strategy-owned stop price mismatches automaticallyRoute 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 行为:383151fec125ce453aaed40fac5b134e0ce5af654901e2bf0cec2ef25c1d1eba49d1f8a2bb7985488c1288a0d3750f3d94935f59,主题从 per-broker stop expectation reconcile、preflight mismatch repair、broker contract enrichment,一路收口到 “auto loop 接管 stop mismatch recovery”。
  • 同一波提交还顺手暴露出另一个关键边界:Scope same-symbol safety to entry mutationsScope broker position adoption to current orderFix 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 日,只读审计第一次把“显示层别撒谎”和“修复链路别借错事实”连成了一件事。 它不是在嫌界面字段难看,而是在指出:如果 expectedBuystargetBasket、monitor raw blob 还在不同层被重新解释,auto recovery 最后也会踩进同一个坑。这让我意识到,观测边界和修复边界其实是同一场战争。
  • 6 月 18 日晚到 19 日凌晨,项目终于停止把缺失止损当“等窗口再说”的附属事件。Allow preflight stop repair during data waitRun 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。”
“昨天说别再小修小补,今天怎么又多了十几个策略变体名。”
“昨天还在谈实盘优先级,今天怎么又跑回回测和前端工作台了。”

我一开始也差点这么理解。因为这几天最容易让人烦躁的地方,不是没有进展,而是进展看起来太像“写更多规则”。newBuySelectionPolicypendingEntryReservationPolicypendingEntryPrefilterPolicypendingAddReservationPolicypositionLifecycleMetricPolicyinitialDeploymentCapScope,这些名字一个接一个出现,很像系统又长出了一层新的术语墙。一个不小心,就会让人怀疑我是不是又在做那种项目后期最常见的幻觉:把旧 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 workbenchBacktestEvidencePanels、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 变体正式退役,newBuySelectionPolicypendingEntryReservationPolicypendingEntryPrefilterPolicypendingAddReservationPolicypositionLifecycleMetricPolicyinitialDeploymentCapScope 被一起写成正式 owner 语义。
  • 配套的验证文档也明确降级成 “historical validation evidence only”。这很关键:它们不再冒充当前可运行真相,而是退回“为什么当时会做出这个正式合同”的证据对象。
  • 2026-06-20 ~ 2026-06-21 的提交链把这个判断落成了代码和配置动作:d8add71b3567ad422ae66fd00f3d41f4d773680b8f0115b0e580a1986b2b59a7909994e5495b32bb9c81f12997d5713c。它们一路从 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”本身当作产品内容,而不再只展示一个结果面板。
  • a63ec2c3db85c79c0fcd3989 继续清理旧 Web/API 痕迹:obsolete prototype references、legacy routes、legacy control aliases 被退役。这说明“正式合同”不只作用在策略,也开始作用在前端入口的历史包袱上。

日记摘录(来自其它会话摘要;仅记录转折点)

  • 6 月 18 日那次直白评估,实际上给这两天的 formal contract 收口定了一个很狠的验收标准:只还阻塞实盘的债。 这句话后来看起来像治理口号,但它真正的落地方式,不是“少写代码”,而是把研究里最会拖出歧义的那部分先写成唯一正式合同。
  • 6 月 20 日,我终于不再允许“研究里的最好版本”和“系统里的正式版本”长期分居。 strict_rankpending reservationprefilterADD 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_WAITTARGET_MISMATCHcontinueAutomation 这类摘要字段不能继续驱动 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 主路径共用 FeatureFramesignalContract,同时把 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 FeatureFramesignal_snapshotsignal_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 只读审计把 expectedBuystargetBasket、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-21git 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,很容易把这段历史误读成“又一波大改”;但如果把它放回前面几章,它其实更像一次迟来的清账。

尾声:这不是一个完成品

这份回忆录记录的不是一个已经成功的量化系统。

它记录的是我第一次把一个交易想法逼近真实系统时,逐渐学会怀疑自己的过程:每当我想说“差不多了”,系统就会用事实提醒我“还早呢”。

如果以后这个系统真的变得可用,最值得记住的也许不是某一次回测收益,而是这些早期的阻断、返工、怀疑和边界。因为它们让系统从一个会报喜的脚本,慢慢变成一个会说“不”的工具。

2026-06-21
Contents
  1. 序章:最开始只是两篇文章
  2. 第一章:我第一次以为“回测出来了”就是进展
  3. 第二章:为了去偏差,我开始扩大股票池
  4. 第三章:纸面交易暴露了“两个真相”
  5. 第四章:V6 看起来更强,但也更危险
  6. 第五章:漂亮的回测数字开始互相打架
  7. 第六章:我终于承认,项目架构一开始没打好
  8. 第七章:5 月 23 日,项目从原型变成工程
  9. 第八章:最严肃的一次安全审计
  10. 第九章:界面也会让人做错事
  11. 第十章:Task 018 和 fail-closed 的代价
  12. 第十一章:10 年数据和 253 根日线
  13. 第十二章:最夸张的数字,最低的置信度
    1. 中场复盘:我后来真正记住的几件事
      1. 第一,文章给的是方向,不是证据
      2. 第二,漂亮回测最需要被怀疑
      3. 第三,两个引擎会制造两个真相
      4. 第四,本地状态不能替代券商事实
      5. 第五,最好的自动化不是自动下单
  14. 第十三章:纸面交易的断桥,逼我把“可靠性”当成需求
    1. 证据(2026-05-30)
  15. 第十四章:我不再相信“自动化会乖乖照做”,于是给它装上了回执
    1. 证据(2026-05-30 ~ 2026-05-31)
  16. 第十五章:纸面交易终于承认“券商才是现实”,于是我学会了“领养”与“等到有上限”
    1. 证据(2026-06-01 ~ 2026-06-02)
    2. 日记摘录(来自其它会话摘要;仅记录转折点)
  17. 第十六章:我终于不敢一边重构,一边假设“语义应该没变”
    1. 证据(2026-06-02 ~ 2026-06-03)
  18. 第十七章:我不再允许“恢复逻辑”替系统脑补现实
    1. 证据(2026-06-04 ~ 2026-06-05)
    2. 日记摘录(来自其它会话摘要;仅记录转折点)
  19. 第十八章:我后来才明白,“容量”不是展示字段,而是执行语义
    1. 证据(2026-06-05 ~ 2026-06-06)
    2. 日记摘录(来自其它会话摘要;仅记录转折点)
  20. 第十九章:我原以为 alpha 还藏在信号里,后来发现它更多藏在“怎么持有”
    1. 证据(2026-06-07)
  21. 第二十章:我以为观察层只是“把状态展示出来”,后来才发现它必须先学会不夺权
    1. 证据(2026-06-13 ~ 2026-06-15)
    2. 日记摘录(来自其它会话摘要;仅记录转折点)
  22. 第二十一章:我以为终端只是皮肤,后来才知道操作者首先需要一个不吵闹、也不撒谎的界面
    1. 证据(2026-06-13 ~ 2026-06-15)
    2. 日记摘录(来自其它会话摘要;仅记录转折点)
  23. 第二十二章:我以为回测只要跑出一个终值,后来才知道它也必须能被追问
    1. 证据(2026-06-16)
  24. 第二十三章:我以为缺的是几天记录,后来发现缺的是“我为什么一直追问”
    1. 证据(2026-05-27 ~ 2026-06-17)
    2. 日记摘录(来自其它会话摘要;仅记录转折点)
  25. 第二十四章:我以前把止损修复当成补丁,后来才承认它其实是主链的一部分
    1. 证据(2026-06-18 ~ 2026-06-19)
    2. 日记摘录(来自其它会话摘要;仅记录转折点)
  26. 第二十五章:我以为还差几个参数,后来才承认缺的是一份正式合同
    1. 证据(2026-06-20 ~ 2026-06-21)
    2. 日记摘录(来自其它会话摘要;仅记录转折点)
  27. 第二十六章:复盘以后,我把三条旁支补回主线边缘
    1. 证据(2026-05-12 ~ 2026-06-18)
  28. 附录 A:关键时间线
  29. 附录 B:关键决定
  30. 附录 C:代码演进索引
  31. 附录 D:代码量变动统计(git numstat 汇总)
  32. 尾声:这不是一个完成品

⬆︎TOP