时间范围:2026-05-12 至 2026-07-10
写作定位:这不是开发日志,也不是提交记录。它是一份面向分享的回忆录:记录一个交易想法如何从两篇 X 文章,变成一套越来越严肃、越来越不敢轻易相信自己的量化系统。
AI 协作说明:这份回忆录由 AI 协助我整理、追问和写作,用来记录我从交易想法出发,逐步构建、怀疑、修正一套量化系统的过程。里面的判断、取舍和反思来自项目推进中的真实证据与对话复盘,AI 主要承担梳理脉络、改写表达和补齐遗漏的工作。

更新原则:这份文档以后会不定期更新,但不会按“今天改了哪个文件”继续堆流水账。

每次更新优先记录四件事:

  • 当时我以为什么是对的。
  • 后来系统或数据如何证明我想简单了。
  • 我做了什么决定,为什么没有选另一条路。
  • 这个决定留下了什么结果、教训或新的风险。

具体代码、命令、回测数字仍会保留,但放在章节中的证据段或附录里。正文要尽量像一篇可以给别人看的长文,而不是一份工作汇报。

第一幕:第一条权益曲线太好看了

这一幕写的是最早那种危险的兴奋感:两篇 X 文章、一套看起来很合理的交易框架、几条很快能跑起来的命令,以及第一条漂亮到让人愿意相信它的权益曲线。

那时我还没有真正理解,交易系统最会骗人的地方不是它跑不起来,而是它太快给你一个像答案的东西。第一幕的主线,就是从“策略好像有了”走到“数字开始互相打架”。

序章:最开始只是两篇文章

这个项目真正的开始,不是一个仓库,也不是一个数据库——而是我一次过于自信的“来都来了”。

如果严格按时间说,在这之前我已经碰过一条旁支:只读券商账户、持仓和市场简报。那条线当时还不是量化策略系统,却提前教了我一个后来反复出现的原则:先学会安全地看见现实,再谈让系统替我行动。所以这份回忆录把它放在主线边缘,而不是当成第一章;真正把交易想法推成策略系统的,是后面那两篇 X 文章。

它开始于 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 工具,再从这些工具里慢慢长出状态、规则、目录和边界。

如果用后来的词回看,这些命令就是最早的 CLI。但当时我并不会这样想。它们更像一排临时开关:哪个脚本能跑,我就用哪个脚本看一眼结果。问题是,交易系统不会因为入口简陋就少一份责任。只要一个命令会筛股票、跑回测、写纸面状态,它就已经在解释策略现实。

那批脚本之间的关系也比“几个小脚本”更紧:

  • 数据提供层先尝试走 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 模拟买入样本:

样本 方向 当时看到的事实
模拟买入样本 A Buy 本地纸面系统已经记录动作,券商侧需要另行只读核验
模拟买入样本 B Buy 本地状态和券商现实后来没有天然对齐

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

本地纸面账户里可能显示我有 FIXMRVL,也可能生成卖出计划。但 LongBridge 模拟账户只读检查显示实际没有对应持仓,于是卖单不能提交。

这件事非常具体,也非常关键。它没有什么高深数学,但能把人从幻觉里直接拽出来。

如果本地状态说“应该卖”,券商账户却没有仓位,那交易系统必须相信券商账户。不能为了让策略状态连续,就假装真实世界也连续。

而且那个阶段的“本地状态”也已经不是一句抽象的话。纸面模块真正在一套独立目录里维护自己的状态文件、交易流水、订单表、权益曲线和同步说明。它已经开始像一个小型纸面账户系统一样生活:会记住现金、持仓、pending orders、trades、runs 和 lastProcessedDate。这也是为什么后面的冲突会这么尖锐,因为你面对的不是一串临时变量,而是一套已经会积累记忆的本地现实。

后来系统里很多安全门都来自这个教训:

  • 提交前必须读取券商余额、持仓和今日订单。
  • 不提交本地凭空生成、但券商侧无法验证的卖单。
  • 不发明订单。
  • 只提交策略同步文件里明确列出的订单。
  • 数量不清楚就不提交。

同一阶段还暴露了另一个更深的问题:Node 版 paper:run 和 Python 版 monitor 看起来像同一策略,但不是同一个执行引擎。

Python monitor 会先跑一整段历史回放,再打印最后一天 snapshot。输出里有历史收益、回撤、交易数,也有当前信号。这对于人来说很容易误解:我到底看到的是历史理论组合,还是今天应该下的单?

这也是“界面问题”的最早种子。那时还谈不上后来那种 operator WebUI 或正式 TUI,但已经有了三种入口的雏形:Node 命令负责执行和写状态,Python monitor 负责给人看,券商只读检查负责提醒我真实账户不是本地文件。它们各自都合理,但只要没有共同 owner,就会各自讲出一套略有差别的故事。

后来我把这个问题总结成一句话:

策略配置一样,不代表回测、监控、纸面交易是同一个系统。

这句话后来直接影响了 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、插件、自动化、报告、数据混在一起。
  • 最早的 CLI 命令、Python monitor、纸面状态和券商只读检查已经开始各自解释系统。
  • 后来会长出来的 Web / TUI / operator 面板,其实只是把这个问题放大,而不是凭空制造了这个问题。
  • 回测引擎既像策略引擎,又像组合执行器。
  • 纸面交易器、paper service、runtime、provider 之间的边界也不清。
  • monitor 输出里混入历史回放摘要,用户很难判断它是不是当前信号。

这里需要补一句时间线:5 月 21 日左右,我真正面对的还不是一个成熟 WebUI 或 TUI 产品,而是多入口的前兆。最早的入口是命令行脚本和 monitor;Web dashboard、read-only TUI、live paper TUI 和 operator readiness gates 是 5 月 23 日建立 git 基线以后,才被系统化补上的。第六章说的“架构没打好”,不是说那时已经有完整的 WebUI/TUI,而是说问题的形状已经出现了:只要同一件交易事实被不同入口分别解释,后面无论入口叫 CLI、Web 还是 TUI,都会漂移。

这时候如果继续堆功能,系统会越来越像一团能跑但不能信的东西:你让它跑,它确实跑;你问它去哪,它开始装傻。

所以后面开始补架构文档:

  • 架构复盘
  • 依赖关系梳理
  • 重构路线图
  • 交易安全不变量

当时最重要的工程原则是:

不要盲目重写。先画边界,再做最小原子改动。

这句话让项目从“策略实验”开始转向“系统工程”。我开始接受一个不太浪漫的事实:架构不是装饰,是刹车系统。

它也让我意识到,架构不是为了好看。架构是为了在结果不一致、状态冲突、接口失败、用户想下单时,让系统知道谁说了算。

第七章:5 月 23 日,项目从原型变成工程

5 月 23 日,这套新仓库建立了 git 基线。

初始提交:

20b248e chore: establish project version control baseline

那一天不是因为新增了某个神奇功能而重要,而是因为系统终于有了可追踪的历史。

不过这里也要把时间线说得更精确一点。5 月 23 日并不是所有方向一起开闸。那天有一条很明确的研究计划线,反而刻意排除了 Web UI、券商执行、TUI、CLI 抽象这些邻近问题,只问一件事:V6 重构以后,策略研究应该怎样证明信号质量、鲁棒性和可信度,而不是继续追最大 CAGR。

这条线后来很重要,因为它把“研究”和“工程表面”先拆开了。现有数据只能覆盖一段较近的历史,所以当时形成了“两遍跑”的想法:先用当前数据验证工具和近期结论,等更长历史数据补齐后,再重新比较同一套研究假设是否仍成立。也就是说,5 月 23 日不是单纯从原型跳到工程,而是同时出现了两个互相制衡的方向:工程要有边界,研究要有数据门。

从那以后,项目开始系统性补课:

  • 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 里都短历史。

其中一个样本最接近,距离系统要求只差极少量历史数据,但仍然不够。差一点就像差一分:你知道自己“几乎”过线,但规则就是规则。

当时有一个诱惑:只差一点,要不要放宽?

最后没有放宽。

系统新增了 strategy:audit:paper-coverage,把阻塞原因清清楚楚暴露出来,但保留 _enforce_strategy_eligibility_coverage() 的 fail-closed 语义。

这个决定并不酷,也不让系统显得更“智能”。它只是让系统在证据不足时停下来。

但我现在觉得,这种停下来比很多自动化都重要。

第十一章:10 年数据和指标热身门槛

后来讨论 Massive 升级和 10 年数据补齐时,又出现一个问题:

10 年够不够?

答案不是简单的够或不够。10 年的意义在于覆盖更多市场环境:2018 Q4、2020 crash、2022 tightening、2023-2026 strength。但它仍然不覆盖 2008。

这个热身门槛也不是随便设出来的。它来自策略自己的指标需求:

  • 中长期动量窗口
  • 中短期趋势窗口
  • 长周期价格位置
  • 长周期均线背景
  • 指标 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

后来我又追问过一个看似小、其实很关键的问题:这个“置信度”到底是什么?

答案并不神秘,也不应该被神化。它不是统计学意义上的置信区间,不是在说“策略有 44% 概率可靠”或者“26% 概率能实盘赚钱”。它只是一个手工设计的审计扣分表,把 PIT universe、数据版本、外部复算差异、same-bar 比例、赢家集中度、行业覆盖、回撤等问题折成一个红黄绿式的风险索引。

这次澄清很重要。因为如果连“置信度”这个词都不被审问,它也会变成另一种漂亮数字。系统原本想提醒我“证据质量低”,但如果我把它误读成精确概率,它又会开始哄我开心。

收益可以由当前本地代码和 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。

一个成熟系统最重要的能力,可能不是执行,而是拒绝执行。

第三幕:券商事实和本地状态开战

第三幕是系统第一次真正碰到“外部现实”。本地状态再完整,也挡不住券商连接会断、账户里可能已有仓位、订单回执需要证明、等待必须有上限、容量不是展示字段而是执行边界。

这一幕的主题不是“接上券商 API”,而是承认一件更难听的话:模拟系统也不能只活在自己的童话里。只要它未来可能替我行动,它就必须先学会尊重券商事实。

第十三章:纸面交易第一次断桥

在写完一堆“不准下单”“不准融资”“必须有止损”的安全门之后,我一度以为纸面交易最大的风险已经被框住了。

我的想法当时很天真,像第一次装防盗门的人:门锁上了,我就觉得天下太平。只要默认不允许真实提交,把所有变更都关在模拟账户里,剩下的就是“把接口接通”,听上去就像插上电源那样简单。

后来我才发现,真正会咬人的往往不是“权限”,而是“连接”。因为我把一个更隐蔽的东西当成了理所当然:连接本身的可靠性

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 有关的提交,让我更确信这条路是对的。因为它们传达的不是“我们又加了几个模块”,而是另一句更值得记住的话:

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

这也是 signalContract 后来变得重要的原因。它不是一个好看的 JSON 字段,而是回测、扫描、纸面计划共同承认的信号叙述边界:谁在同一天、同一批特征、同一套过滤条件下,生成了哪些候选,哪些只是诊断信息,哪些才有资格继续进入执行链。

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

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

证据(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 架构里最麻烦的不是某一边单独报错,而是一边已经无法给出事实,另一边还在继续制造需要解释的偏差。

第四幕:系统里到底谁有解释权

第四幕里,问题从“系统能不能看到现实”变成“系统里到底谁有资格解释现实”。信号、持仓、回测、观察层、TUI、WebUI、止损、报告都可能说自己只是展示,但只要它们开始推断交易事实,就会悄悄变成第二套系统。

这一幕真正讲的是解释权。alpha 不只藏在选股信号里,也藏在持有方式、证据链、界面边界和每一个不能让前端脑补的状态里。

第十九章:Alpha 藏在怎么持有里

写到这里的时候,我其实还有一个顽固的直觉没放下。

我一直觉得,真正值得兴奋的改进,大概率还应该来自“发现了更好的信号”。换句话说,如果某次结果突然更强,我会本能地先怀疑是不是筛股条件、节奏判断、趋势定义又往前推进了一步。至于仓位上限、初始部署、加仓节奏、持仓退出和大赢家处理这些东西,我心里虽然知道重要,但总还是容易把它们归到“执行层优化”这一栏,像是在整理收益,而不是在决定收益。

6 月 7 日这批结果,逼着我把这个想法改掉了。

那天最有说服力的地方,不是又跑出了一组漂亮数字,而是同一条证据反复强调了一件事:final40 的信号核心根本没变。
研究报告、formal promotion、robustness validation、system default 更新,都在围绕同一个前提展开。信号没有偷偷换一套说法,变化集中在组合行为、风险、仓位、现金部署、加仓、止损和持有时间上。

这就让结果的含义完全不一样了。

如果信号没变,而 10 年和 5 年的表现、walk-forward、execution stress、formal parity 还在往前走,那我就不能再把它理解成“可能又找到了更聪明的选股方式”。更接近事实的说法反而是:我以前低估了“怎么持有、怎么分配、怎么限制自己”本身就是策略的一部分。

这件事对我冲击很大,因为它会反过来改写很多做研究时的习惯。

以前看到高收益,我第一反应常常是去追问信号;现在我会先问,这份收益到底是不是被一套更一致的持仓语义释放出来的。
以前那些单仓上限、新开仓容量和初始部署约束看起来像参数表里的小格子;现在它们更像纪律条款,改一个就是在改系统的性格。
以前我还会下意识把执行层看成离 alpha 稍远的地方;现在我更愿意承认,有些 alpha 根本不是被“找到”的,而是被“不乱拿、不乱加、不乱放大”保留下来的。

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

dbe4b29c4551e78fd9d6a 这一串提交,已经不只是“候选更优”,而是在把组合行为语义正式提升成系统默认路径。紧跟着的 Python 3.13 迁移和 baseline parity 回归,又像是在追问另一个更严厉的问题:如果把运行时换掉,这套语义还站得住吗?

我后来反而很喜欢这种追问。因为它让“好结果”不再只是实验室里的一次截图,而开始像一个可以跨 runtime、跨报告、跨入口重复确认的系统选择。

如果说前面几章写的是“不要让系统替现实脑补”,那这一章更像另一种承认:

真正改变系统收益曲线的,不一定是更会猜市场,而可能是终于学会了如何约束自己持有市场。

证据(2026-06-07)

  • 一份 2026-06-07 的策略基线文档明确写出:final40 信号核心固定不变,变化只来自组合行为、风险、仓位、现金部署、加仓、止损和持有时间控制;高收益候选仍被刻意标成研究基线,不冒充生产默认。
  • 同日的组合行为验证报告把同一个结论做了更系统的验证:一批实验都锁定 final40 信号核心,只比较组合行为与风险执行语义;其中某个单仓上限候选在稳健评分和长期收益之间给出了更强的一组平衡。
  • 另一份稳健性验证报告继续把这套配置拿去做 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 这些路径如果继续互相借字段、互相帮忙解释,就会把“显示层不夺权”的老问题,悄悄拖回修复链路本身。换句话说,真正危险的不是“界面说错一句话”,而是“恢复逻辑在借错一套事实做决定”。

这条线其实在前一天已经有过一个更具体的预警:本地生命周期曾经把某个计划买入记录成已提交,但券商只读事实后来显示它被券商侧取消且没有成交。如果只看本地流水,系统会以为动作已经进入下一阶段;如果只看券商事实,真实世界根本没有持仓变化。这个差异再次提醒我,生命周期记录是证据线索,不是交易终局。交易终局必须回到券商 truth。

于是接下来的提交开始出现一种以前没那么明确的姿态:系统不再只报告 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 是补洞;如果你拒绝区分它们,系统表面上会越来越谨慎,实际上却会越来越擅长把问题留到明天。

到这里,其实已经可以提前交代后面那串策略名的关系了。V1 是把文章直觉压成第一套日线执行语义;V2 是纸面账户第一次撞上券商现实;V6 是系统形态完整以后仍然不敢开全自动;final40 是信号核心冻结后,组合行为开始成为主要改进来源;D90 则不是“又一个更神秘的名字”,而是研究 wrapper 里的暗知识被正式引擎追债以后,被迫写成唯一合同的结果。

证据(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,看看今天这条链本身会怎么走。以前我总把这两件事混在一起,后来才明白:诊断真实事故和排练未来行为,需要的根本不是同一套证据。

第五幕:默认策略还没有资格按下按钮

第五幕是近期主线的收束:策略版本从 final40、D90 到 d90g,不再只是“哪个数字更好看”,而是变成一场晋升审判。一个策略要成为默认,不只是改配置名;它必须证明研究暗知识已经写进正式合同,旧解释器已经退场,执行链路不会在关键时刻听错人。

这一幕也解释了为什么我后来越来越谨慎。默认策略站上台以后,真正困难的不是宣布它默认,而是证明它有资格行动。

第二十五章:策略版本真正怎么推进

如果只按前面的章节读,容易产生一个误会:这套系统好像是从“第一版回测”一路自然长到了“formal contract”。但真实过程更乱,也更值得记录。

它不是一条直线,而是一串不断被证伪、被迁移、被收口的版本。每一代版本都不只是“收益更高”或“参数更多”,而是在回答一个不同的问题:

  • V1 问的是:文章里的交易直觉能不能变成一个不明显未来函数的日线系统?
  • V2 问的是:如果策略更进攻,纸面账户和券商账户会不会开始说两套话?
  • V6 问的是:当数据、扫描、回测、预实盘和纸面执行都变完整以后,系统敢不敢承认自己还不能全自动?
  • final40 问的是:信号核心能不能冻结,收益改善是不是更多来自组合行为,而不是继续换一套选股故事?
  • D90 问的是:研究 wrapper 里看起来有效的暗知识,能不能被写成正式引擎唯一承认的合同?

这条线现在必须补清楚。因为它解释了为什么后面我越来越少说“又找到一个更强策略”,越来越多说“这次到底改变的是信号、组合行为,还是执行合同”。

V1:把文章直觉压成第一套可跑系统

V1 最重要的不是收益,而是把两篇文章里的语言翻译成了第一套执行语义。

文章说“强的时候进攻,弱的时候防守”,代码里就必须回答:什么叫强?什么叫 setup?什么时间生成信号?什么时间成交?如果今天收盘才知道信号,能不能假装今天开盘就买到了?

所以 V1 真正留下来的第一条纪律,是:

T 日收盘生成信号,T+1 开盘成交。

这条纪律比第一版收益更重要。因为如果连这个都守不住,后面所有收益都只是漂亮幻觉。

V1 的数字看起来已经足够诱人:

版本 / 测试 窗口 总收益 最大回撤 当时的含义
V1 静态 54 股 2024-01-01 至 2026-05-14 150.97% -25.93% 第一版能跑,但股票池明显带有事后热门股偏差
V1 NASDAQ-100 点时池 同窗口 60.92% -22.76% 去掉一部分幸存者/主题偏差后,收益仍在但光环变暗
V1 S&P 500 点时池 同窗口 92.75% -31.93% 更宽股票池仍有收益,但数据缺口和回撤压力更明显

这组结果让我第一次明白:策略不是看静态最优结果,而是看被换股票池、换数据口径、换偏差假设之后还剩什么。

如果说 V1 有一个教训,就是别急着把第一张好看的权益曲线当成 alpha。它更像一张入场券,证明这个想法值得继续审问。

V2:收益更激进,但纸面交易开始制造第二套现实

V2 的名字和形态都更像“策略版本”:high-beta pyramid,高 beta,金字塔,加仓,纸面交易。它比 V1 更激进,回测约 528.81%,看起来像是系统终于开始抓住趋势股最肥的一段。

但 V2 真正留在我脑子里的,不是 528.81%。而是它第一次把“本地策略状态”和“券商账户事实”撞在一起。

本地纸面账户说自己有仓位,卖出计划也能生成;券商模拟账户只读检查却显示没有对应持仓。那一刻,问题已经不是“收益率是多少”,而是“如果两边不一致,系统到底听谁的”。

所以 V2 不是 V1 的简单增强版。它把问题从“怎么选股”推进到“怎么承认现实”:

版本 / 测试 已知收益 最大回撤 更重要的发现
V2 high-beta pyramid 528.81% 当前回忆录未保留可靠数值 本地纸面状态不能替代券商账户事实

这里我故意不补一个没有核实过的最大回撤。因为这本身也是回忆录应该保留的纪律:没有证据的数字不要为了表格好看硬填。

V2 还暴露了另一个问题:Node 版纸面执行和 Python monitor 看起来在跑同一策略,但其实不是同一个执行引擎。配置相似不等于语义一致。这个坑后来一路延伸到 V6、final40 和 D90。

V6:系统形态完整了,但不能因为完整就自动相信

V6 是第一次让我觉得“这不是玩具脚本了”的版本。DuckDB、宽筛、日线确认、scan、live、backtest、prelive 都出现了。它的信号输出也更像交易计划:入场、止损、股数、风险金额、加仓和失效条件都开始被明确写出来。

后来我回看才意识到,V3、V4、V5 并不是被我漏写的三代正式策略;它们没有成为主线版本。V6 更像一次跨会话跃迁:从早期脚本和纸面试验,直接跳到一套已经带数据仓库、扫描、回测、监控和 prelive 检查的系统形态。

V6 的早期数字也足够刺激:

版本 / 测试 总收益 最大回撤 当时的含义
V6 早期研究基线 748.68% / 748.76% -29.06% 信号源显著增强,但证据仍偏研究性质
加入更严格 gap / risk execution guards 后 612.43% -29.30% 控制规则降低收益,却没有明显降低回撤
重新网格测试后的改进默认 771.44% -29.06% 说明问题不只是“多加安全门”,而是安全门本身也要被审计

这段推进特别重要,因为它打碎了一个很容易自我安慰的想法:

风控规则只要更严格,就一定更安全。

事实不是这样。一个 gap/risk guard 如果跳过了大量好交易,却没有降低最大回撤,它就不是成熟风控,而是收益损耗器。V6 让我开始明白,交易系统里的“安全”不能只靠语气判断,必须也接受回测和归因审计。

同一时期的 prelive 失败也很关键。V6 明明更完整、更强,却仍然返回 FAIL_FOR_FULL_AUTO_LIVE。这逼我做了一个后来一直影响项目的决定:升级信号源,但不升级成全自动实盘。

也就是说,V6 的真正推进不是“收益从 V2 变高了”,而是系统第一次同时拥有两种能力:

  • 能生成更像样的交易计划。
  • 能明确告诉我,它还没有资格无人值守实盘。

final40:Alpha 开始从信号转向持有方式

从 V6 到 final40,表面上像是继续找更好的策略。后来我才意识到,这一段真正发生的是“信号核心冻结”。

final40 不是一个简单的新名字。它更像一个分水岭:从这里开始,我不再默认每一次收益改善都来自“更聪明的选股”。相反,证据反复提醒我,信号核心没有变,变化集中在组合行为、风险、仓位、现金部署、加仓、止损和持有时间上。

这对我的认知冲击很大。因为它等于承认:策略不只是买什么,也包括怎么持有、怎么加、什么时候不再加、怎样限制单一仓位、怎样让现金闲着。

final40 这阶段有两组数字应该分开看:

版本 / 测试 窗口 总收益 最大回撤 定位
final40 工程治理基线 compound 10 年 26493.86% -30.79% 行为保持/工程治理用的长期 baseline,不是投资承诺
final40 工程治理基线 fixed 10 年 677.80% -30.71% 去掉复利放大后的对照
final40 工程治理基线 compound 5 年 2700.04% -30.79% 中窗口语义变更前的审计基线
final40 工程治理基线 fixed 5 年 347.61% -19.84% 更接近固定本金口径的对照
final40 组合行为高收益研究候选 10 年 53906.88% -30.78% 信号核心不变,收益来自组合行为研究候选
final40 组合行为高收益研究候选 5 年 5878.36% -30.78% 仍是研究 baseline,不冒充生产默认

这张表如果只看收益,会很危险。它真正该告诉我的不是“系统已经会赚钱”,而是三个更冷静的事实:

  • 复利口径会极度放大强趋势阶段,必须同时看 fixed。
  • 最大回撤长期卡在约 30% 附近,说明收益提高并不等于风险消失。
  • 高收益候选的信号核心没有变,所以贡献来自组合行为,而不是新因子突然开窍。

从 final40 开始,项目的重心不再只是“找 alpha”,而是“别把已经找到的趋势收益在持有和执行里浪费掉,也别让它在自动化里变成不可控风险”。

D90:把研究暗知识写成正式合同

D90 最容易被误读成“final40 后面又冒出来一个更复杂版本”。但我现在更愿意把它写成另一件事:研究 wrapper 和正式引擎之间的语义债,被迫摊牌了。

在 D90 之前,很多行为其实活在研究代码、临时报告和人的记忆里。某个 wrapper 跑得更好,到底是因为信号更好,还是因为它偷偷用了不同的候选补位、现金预留、ADD 预留、生命周期指标刷新方式?如果这些东西说不清,所谓“策略版本推进”就会变成一堆口口相传的民间版本。

D90 的意义就在这里:把这些暗知识压成正式合同,并且明确旧 wrapper、参数 sweep、历史候选只能作为证据,不能继续当可运行真相。

D90 这阶段的数字也必须分层看。

第一层,是 strict-rank 发现:它证明正式 D90 当时确实有低 rank 补位问题,修正后收益和 PF 改善,但仍不如 final40。

版本 / 测试 窗口 口径 总收益 最大回撤 结论
D90 formal 当时版本 10 年 compound 7934.43% -31.20% 可运行正式版本,但存在低 rank fallback 问题
D90 strict-rank 候选 10 年 compound 8994.55% -31.20% 收益改善,回撤未恶化,但仍只是候选
final40 对照 10 年 compound 23751.41% -30.79% 同轮验证里仍明显领先 D90 strict
D90 formal 当时版本 5 年 compound 563.33% -29.47% 中窗口收益明显低于 final40
D90 strict-rank 候选 5 年 compound 648.30% -29.47% 低 rank 补位修正有效,但不是最终答案
final40 对照 5 年 compound 1247.85% -30.80% 继续作为更强对照

第二层,是 formal contract 收口。D90 后来不再追着每个 wrapper 变体跑,而是把最终语义写成唯一 canonical config。这个版本在完整验证快照里出现过更高数字:

版本 / 测试 窗口 口径 总收益 最大回撤 定位
D90 final contract validation 10 年 compound 45628.36% -30.47% 正式合同推广前的完整验证快照
D90 final contract validation 10 年 fixed 590.57% -27.06% 固定本金对照
D90 final contract validation 5 年 compound 6989.25% -26.57% 中窗口验证快照
D90 final contract validation 5 年 fixed 495.21% -26.57% 固定本金对照

这组数字看起来很强,但它最值得记住的仍然不是“D90 又变强了”。真正值得记住的是:研究 wrapper 终于不再是暗处的权威。

第三层,才是 D90G 的出现。

D90G 不是 D90 final contract 的自然升级,也不是“D90 后面又多一个更强后缀”。它出现的原因更尴尬:D90 刚刚被收口成正式合同时,市场过滤的动作范围又暴露出一层语义不一致。研究 wrapper 可以用很短的写法表达“坏环境里把候选清空”;正式引擎却必须逐个动作回答:新买入要不要挡,已有仓位还能不能加码,生命周期指标还要不要刷新,纸面计划和自动链路是不是也按同一套动作范围执行。

所以 D90G 的名字真正指向的不是“多加一个过滤器”,而是一次命名边界重划:它只能表示 D90 组合行为基础上,市场过滤同时约束新买入和加仓的候选语义。它不能再被拿来泛指 entry-only 版本,不能代表研究网格里的临时变体,也不能让某个 wrapper 的捷径继续替正式引擎定义策略。

更重要的是,D90G 一出现就没有被允许直接晋升为默认。它必须先走一条显式晋升路线:冻结候选身份,冻结基线证据,用正式引擎重跑对照,证明 backtest、scan、paper plan 和 auto plan 消费同一份策略合同,再验证 lifecycle refresh、paper / auto smoke、repair / reconcile,最后才允许默认切换和切换后观察。换句话说,D90G 的第一天就被放进“候选而非默认”的笼子里。

这个动作对我很重要。因为它把前面所有教训又重复了一遍:高收益只能说明“值得审”,不能说明“可以上”;名字清楚只能说明“有索引”,不能说明“语义已经一致”;研究 wrapper 赢过,不等于正式引擎、纸面链路和自动修复链路都已经会同样地赢。

也就是说,D90 的推进不是“我又多了一个策略名”,而是系统终于学会把这几个问题一次说清:

  • 哪个版本是唯一可运行合同?
  • 哪些版本只是历史证据?
  • 哪些语义由正式引擎拥有?
  • backtest、paper、WebUI、monitor 只能消费哪个 owner?
  • 如果研究结果不能进入正式合同,它就不能继续冒充策略本身。

这一串版本真正改变了什么

回头看,V1 到 D90 不是收益率单调上升的故事。它更像一条认知迁移线:

阶段 当时我以为在推进什么 后来真正推进了什么
V1 把文章写成策略 建立最小可验证日线语义,承认股票池偏差
V2 找到更激进的高 beta 版本 发现本地纸面状态和券商事实会冲突
V6 做出更完整、更高收益的系统 学会区分研究收益、执行安全和 prelive 资格
final40 再找一个更强信号 冻结信号核心,把组合行为当作策略的一部分
D90 追上研究 wrapper 的好结果 把研究暗知识写成唯一正式合同,退役旧变体
D90G 把市场过滤候选推成更强默认 先定义动作范围,再走晋升门,防止研究收益绕过正式引擎

这也是为什么我现在看这些版本,不太愿意只按收益排序。

V1 的收益不如后面,但它让我第一次知道回测要被审问。V2 的收益更高,但它让我知道本地状态会撒谎。V6 更完整,但它让我知道完整系统也可能没有实盘资格。final40 数字巨大,但它让我知道高收益不一定来自新信号。D90 最复杂,但它让我知道复杂如果不能收口成合同,就只是下一轮混乱的种子。D90G 则把这个教训又往前推了一步:合同收口之后,还要继续追问每个动作的范围;候选再强,也必须先证明自己能穿过正式晋升链路。

所以这条版本线真正该被记住的,不是“从 150.97% 一路跑到几万百分比”。更准确的说法是:

我一开始在追更高收益,后来越来越像是在追一份能被所有入口承认、能被回测复核、能被纸面执行消费、也能在风险面前停下来的策略合同。

第二十六章:研究暗知识被写进合同

6 月 20 日到 21 日这两天,表面上看像是我又掉回了自己最擅长的坑里:改配置名、补 policy、加 validation、调 WebUI、再顺手做几份 before/after 报告。要是只看 commit 标题,这一段很容易被误读成一种熟悉的、略带疲惫感的工程景象:

“昨天说系统需要治理,今天就开始写更多 contract。”

“昨天说别再小修小补,今天怎么又多了十几个策略变体名。”

“昨天还在谈实盘优先级,今天怎么又跑回回测和前端工作台了。”

所以在进入这些名词之前,必须先把 D90 放回正确位置:它不是一个凭空冒出来的新策略名,而是一张欠条。研究 wrapper 以前用很短的实现表达了很多默认假设,正式引擎后来必须逐条追问这些假设到底归谁拥有、谁能消费、谁只能作为历史证据。

我一开始也差点这么理解。因为这几天最容易让人烦躁的地方,不是没有进展,而是进展看起来太像“写更多规则”。新买入选择、待买入预留、候选预过滤、待加仓预留、持仓生命周期指标、初始部署范围,这些名字一个接一个出现,很像系统又长出了一层新的术语墙。一个不小心,就会让人怀疑我是不是又在做那种项目后期最常见的幻觉:把旧 wrapper 的直觉,翻译成更多枚举值和更多 JSON 字段,然后假装这就叫收敛。

更刺耳的是,有一次 focused 测试已经大面积通过,单独构造的混合场景却仍然发现了语义漏洞:先发生的 ADD 会消耗现金,但后面的新 BUY 仍可能按旧的预留预算判断自己可用。这不是“测试不够多”的普通问题,而是提醒我:只要 backtest、paper plan 和 submit gate 不是在同一套批次现金语义下思考,绿色测试也可能证明不了真正的执行安全。

后来我才意识到,这次真正变化的不是“参数更多了”,而是研究里的暗知识,第一次被逼着写成正式合同。 以前很多行为其实不是没有答案,而是答案散落在研究 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 变体正式退役,并把候选补位、现金预留、加仓预留、生命周期指标和初始部署约束一起写成正式 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。
  • 4747033a 又把 entry reservation ledger 从 backtest engine 和 paper plan engine 里抽成共享 owner,并加了测试禁止这两条链路各自重新实现 intended reservation / prefilter / ADD reservation 语义。这一步没有另开新故事,却补上了第二十六章最关键的闭环:合同不只写在配置里,还要删掉消费者里的重复账本。
  • 一组 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。

第二十七章:三条旁支补回主线边缘

这次重新翻会话摘要时,我发现前面的回忆录有一个隐蔽问题:它太容易把故事讲成“策略系统一路长大”。这当然是主线,但不是全部。

这一章是有意插叙,不是时间线继续往后推进。它放在版本谱系之后,是为了把几条没有资格单独成章、但会解释系统性格的支线补回来:只读券商能力、运行时可见性、旧入口退役、自动化噪音清理。它们不像 D90 那样直接改变策略合同,却反复提醒我同一件事:系统越大,越要防止旁支悄悄长成第二套现实。

有些东西看起来像旁支,甚至不在“回测收益”或“自动交易链路”里,却改变了我后来判断系统的方式。它们不应该抢主线的位置,但如果完全不写,读者会误以为这些纪律是后来突然冒出来的。

第一条暗线,是只读券商读路径。

在两篇 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 退役不是移除一个命令就结束;只要文档、测试、注册表或残留实现仍然引用旧入口,它就仍然是迁移未完成,而不是历史已经清空。

第二十八章:同名策略也会有两套语义

昨晚到今天,真正值得记住的不是又多了一个策略名字,而是我发现:名字统一,不等于动作统一。

这句话之所以放在第二十五、二十六章之后,是因为它正好打破了一个刚刚形成的新自信:我以为 formal contract 已经把 D90 收口了,至少同一个名字下面的研究和正式引擎应该不会再各说各话。结果很快就发现,合同刚写清楚一层,动作范围又把另一层含糊暴露了出来。

前面刚把 D90 从研究线索压成正式合同时,我很自然地以为,只要 config 名、alias、portfolio behavior 都写清楚,研究结果和正式引擎就会开始说同一种话。这个假设很危险。因为它忽略了一件事:研究 wrapper 可以用很短的代码表达一个想法,但正式引擎必须在每一个交易动作上表达同一个想法。

第一个裂缝出现在 market filter。

我原本以为,“市场过滤打开”只是在说:大盘环境不好时,不要开新仓。后来对比研究 wrapper 和正式引擎的历史动作,才发现两边并不完全一致。某个历史样本日,研究 wrapper 在 market filter 触发后直接把候选清空,于是新买入和加仓都被挡住;正式引擎的语义却是只挡新买入,已经存在的仓位仍然可以继续走 ADD / lifecycle 逻辑。

这不是一个小差异。它说明 marketFilter = true 不是一个足够完整的合同。它至少还要回答:

  • 挡的是 BUY,还是 BUY + ADD
  • 已有仓位的 lifecycle 是否继续运行?
  • 回测、纸面计划、监控展示是不是都消费同一份动作范围?
  • 研究 wrapper 的捷径有没有被误当成正式策略语义?

这也是为什么后来必须把 market filter 拆成 action-scoped contract。entry-onlyBUY + ADD 看起来只是两个小词,但在系统里它们是两套策略。前者只是在坏环境里停止开新仓;后者还会限制已有仓位继续加码。它们不应该共用一个含糊名字,更不能让一个 wrapper 的短写法替正式引擎做决定。

d90g 不是“又一个更强默认”,而是一道晋升门

这件事之后,d90g 的意义也变了。

它不再只是“D90 加一个市场过滤”的随口说法,而被定义成更窄的候选语义:在 D90 组合行为基础上,市场过滤同时约束 BUYADD。这个名字一旦写下,就不能再拿来表示 entry-only、研究网格临时变体,或者某个 wrapper 里顺手实现的版本。

从数字看,它确实还有吸引力。在 ordinary execution pressure 下,D90 过滤候选的 10 年复利收益约 26110.35%,最大回撤约 -40.08%,profit factor 约 3.22;5 年复利收益约 4587.29%,最大回撤约 -30.48%,profit factor 约 3.26。同一压力模型下,final40 的 10 年复利收益约 2591.15%,最大回撤约 -48.19%,profit factor 约 1.37;5 年复利收益约 609.91%,最大回撤约 -37.96%,profit factor 约 1.34

如果只看这张表,很容易又回到最早那种冲动:收益更高,就把它推成默认。

但这次系统没有这么做。因为同一组证据里也有另一面:D90 过滤候选在 10 年固定仓位口径下 profit factor 约 2.71,没有自然跨过所有晋升门槛;10 年复利回撤也已经接近 -40%。这说明它值得继续推进,但还不能因为漂亮的收益数字直接获得默认资格。

所以 promotion gate 被写得很长:候选冻结、baseline 冻结、正式回测、语义一致性、lifecycle refresh、paper / auto smoke、repair / reconcile、默认切换、切换后观察。这个门槛看起来繁琐,但每一关其实都在防同一种滑坡。

候选冻结,是为了防止 d90g 这个名字又漂移成多个版本。baseline 冻结,是为了让后续比较能绑定到同一份代码、数据和默认策略。正式回测,是为了确认它不是只在研究 wrapper 里成立。语义一致性,是为了确认 backtest、scan、paper plan 和 auto plan 没有各自解释候选、容量、市场过滤或 lifecycle。paper / auto smoke 和 repair / reconcile,则是为了确认它不只会在 CSV 里赚钱,也不会在真实纸面链路里绕过保护止损、现金约束、修复语义和券商事实。

默认切换反而被放到了很后面,而且被要求非常窄:只改默认策略指向,不顺手改策略语义、回测引擎、纸面计划、自动状态机或显示层。这个限制看起来保守,但它正是在防止我以前最容易犯的错误:把“晋升一个候选”变成“顺手改一堆系统”,最后没人知道到底是哪一件事带来了结果。

这一天给我的教训很具体:

策略名只是索引,动作语义才是合同;回测收益只是候选理由,不是晋升资格。

以前我会把 d90d90gfinal40 这些名字看成版本。现在我更愿意把它们看成承诺。一个名字如果不能说明它挡哪些动作、消费哪些 owner、经过哪些压力模型、是否通过纸面 smoke,它就还只是研究笔记,不是系统默认。

证据(2026-06-21)

  • 研究 wrapper 与正式引擎的 market-filter 对比确认:同名“市场过滤”在动作范围上可以不同,研究侧曾经通过清空候选同时阻断新买入和加仓,正式侧则需要明确区分 entry-only 与 BUY + ADD
  • 2026-06-21 的提交链把 market filter 从含糊开关推进到 action-scoped 语义,并把 ordinary execution pressure 写成更接近真实执行的默认回测压力口径。
  • 同日的晋升门文档把 d90g 定义成候选,而不是默认;它必须先通过 formal backtest、semantic parity、paper / auto smoke、repair / reconcile 和 post-switch observation,才能进入默认切换。
  • ordinary execution pressure 复测显示:D90 过滤候选在收益和 profit factor 上仍然明显优于 final40,但 10 年复利回撤约 -40.08%,固定仓位 10 年 profit factor 约 2.71,因此它是“值得推进的候选”,不是“已经完成晋升的默认”。

第二十九章:默认策略切过去以后,真正该删的是旧解释器

第二十八章写完时,d90g 还只是候选。那时我给自己的提醒是:不能因为一组漂亮回测,就把它直接推成默认。

后来它真的被推成默认了。

这句话听起来像一个阶段胜利,但我现在反而不敢把它写得太热闹。因为这一次最值得记住的,不是“默认策略终于换了”,而是默认切换之后,系统马上暴露出另一个更硬的要求:如果默认已经换到正式合同上,旧的解释器就不能继续在旁边活着。

以前我会把默认切换看成终点。配置指向改了,测试过了,系统从此就按新策略跑。现在我知道这太天真。默认指向只是入口,入口后面还有一堆会偷偷解释策略的东西:旧 loader、旧 facade、旧 policy layer、旧 naming path、旧 research 变体、旧 scan 语义、旧 runtime projection。它们不一定马上出错,但只要还被生产路径、测试或人的习惯引用,就会继续保留“其实我也懂策略”的资格。

这就是 6 月 21 日深夜到 6 月 22 日凌晨那串提交真正改变的地方。系统没有停在“把 d90g 设成默认”,而是继续把策略配置、组合行为、entry selection、entry sizing、entry evaluation、runtime strategy config 都往同一个 contract owner 上赶。与此同时,一批旧路径被删除或去权威化:重复的 strategy config owner 没了,flat strategy loader entrypoint 退场了,strategy engine facade 被剪掉了,旧 scan / naming / policy option 也不再当成正式解释来源。

这件事让我对“晋升默认”有了一个更难听、但更准确的定义:

默认策略不是把一个名字写进系统配置,而是让系统里其他解释者失去解释它的权力。

这比改默认指向麻烦得多,也更容易让人不耐烦。因为删除旧解释器看起来不像“新功能”,甚至不像“策略变强”。它更像清账:把以前为了研究、兼容、快速验证、临时命名留下来的路,一条条关掉。关掉以后,系统表面上少了一些灵活性,实际却少了很多可以悄悄漂移的现实。

我也开始更能理解,为什么前面那些看似重复的 owner / contract 纪律会一路追到策略层。最开始我以为它们主要是为了防止 Web、CLI、monitor 这些观察层乱解释交易事实。后来才发现,策略本身也会出现同样的问题:研究层说一种版本,正式回测说一种版本,纸面计划说一种版本,自动运行又吃到另一种默认。只要这种分裂还存在,哪怕每一层都“看起来合理”,合在一起也不是一个可托付的系统。

所以这一章不是给 d90g 庆功的。

它更像给自己留的一张罚单:以后任何策略被说成“已经晋升”,都必须同时回答两个问题。第一,它是不是已经通过正式证据链;第二,旧解释器是不是已经被删除、迁移或明确降级为历史证据。如果第二个问题答不上来,默认策略就还没有真正默认,只是系统暂时假装只听它一个人的话。

证据(2026-06-21 ~ 2026-06-22)

  • 2026-06-21 晚间,d90g 从候选进入默认指向;这一步本身被控制成窄切换,而不是顺手改策略语义。
  • 随后的提交链继续收敛 strategy contract、portfolio behavior、runtime strategy config、entry selection / sizing / evaluation 等解释权,核心方向是让 backtest、paper、runtime 和测试消费同一套正式合同。
  • 2026-06-22 凌晨的多次删除和重路由,退役了重复配置 owner、flat loader entrypoint、strategy engine facade、旧 scan / naming / policy option 等旧路径,避免它们在默认切换后继续成为第二套策略解释来源。
  • 同期的阶段化 goal 文档把后续工作明确为:策略配置、标准策略合同、共享计划 owner、正式执行链路、重置与观察要按阶段推进,不能只靠聊天记忆或一次默认切换声称完成。

第三十章:默认策略还没有资格按下按钮

默认策略刚切过去的时候,最容易产生一种错觉:既然 d90g 已经站上台,旧解释器也开始退场,下一步就应该让系统真正跑一遍。

我当时确实有这个冲动。把模拟账户里已有的仓位和订单处理干净,让系统从一个干净起点重新生成计划、提交订单、挂好保护止损,再把券商回读和本地计划对上。这个动作听起来很有诱惑力,因为它能给人一种很直接的确认:

你看,它不只是默认策略,它真的能行动。

但正是这个“很直接”,让我后来意识到它危险。

如果只是看一份回测,风险还关在文件里;如果只是改默认配置,风险还关在代码里;可一旦开始取消订单、平仓、重置本地状态、重启 runner、重新提交计划,系统就已经从“解释世界”进入“改变世界”。哪怕对象只是模拟账户,它也不再是普通验证。它会留下真实的券商订单状态,会改变本地和远端的对账起点,也会把每一个含糊的 owner boundary 都放大成执行风险。

所以那天真正发生的,不是我完成了一次漂亮的端到端验收,而是我被迫承认:默认策略还没有资格按下按钮。

这个资格不是收益率给的,也不是“已经设成默认”给的。它要由一串更枯燥的证据来给:账户模式有没有明确,券商选择器有没有锁死,订单范围有没有列清,重置顺序有没有 dry-run,保护止损能不能回读,非交易时段动作有没有报价和流动性依据,runner 重启后消费的是不是同一份正式计划。

只要这些问题里有一个答不清,“证明系统能跑”就会变成另一种更隐蔽的冒险:系统为了证明自己,先制造了一次新的不确定性。

这也是为什么同一天看起来分散的只读审计,其实都在服务同一个判断。共享 planner 不是为了少写几行代码,而是为了确认 backtest、paper plan 和 auto plan 说的是同一种策略意图;旧 rules、raw config、replay 摘要、默认请求和测试夹具不是因为难看才要清理,而是因为它们可能在关键时刻继续解释策略;WebUI 和 readonly monitor 也不是旁枝,它们如果自己拼交易事实,就会让显示层在行动前偷偷获得解释权。

回测世界和纸面世界尤其不能被揉成一团。回测关心的是 T 日信号、T+1 开盘执行;纸面计划面对的是券商现金、已有持仓、未完成订单、保护止损和重复买入约束。它们可以共享策略意图,但必须用各自真实拥有的事实说话。所谓 owner contract,在这一刻不再是架构洁癖,而是系统按下按钮前的刹车片。

VPS 和 CI/CD 的讨论也因此接上了主线。未来把系统搬到服务器,不应该只是“机器能跑起来”。代码发布、数据下载、数据对齐、远端同步、数据湖校验、readiness gate 都必须拆开;否则服务器只会把本地的含糊放大成无人值守的含糊。系统越接近真实运行,越不能靠“我记得应该没问题”来解释自己。

所以第三十章不是一次胜利宣言,而是一道门槛。默认策略已经上台,但后台还没有完全清场;系统已经能描述行动,但还没有充分证明每一层行动依据都只来自正式合同和券商事实。

等有一天真的按下提交键,我希望自己能清楚地知道:那一下不是旧 wrapper、旧摘要字段、旧默认请求、旧测试夹具或某个显示层推断替我按的,而是来自一条被证明过的计划链路。

到那时,默认策略才算真正有资格行动。

侧记:2026-06-22 那些真正改变判断的会话

  • 多个只读审计把同一个问题从不同方向讲清楚:共享 planner 可以成为 owner,但不能抹平 backtest 的 T+1 语义和 paper plan 的券商上下文。真正要共享的是策略意图和 sizing 投影,不是把现金、持仓、订单和执行价格混成一个万能输入。
  • 我明确提出过一个很直接的验收冲动:“先强平掉,然后确认系统能正常按照 d90g 生成计划并且正确提交”。审计结论不是简单同意,而是把这个想法升级成执行安全问题:模拟账户也必须先证明选择器、账户引用、dry-run 重置计划、平仓顺序、保护止损和券商回读证据,否则不能把行动当验证。
  • rules、raw config、replay 摘要、默认请求和测试夹具被反复点名,不是因为它们难看,而是因为它们可能在关键时刻继续解释策略。默认策略要真正默认,必须让这些旧路径删除、迁移或明确降级为非权威证据。
  • Operator WebUI、readonly monitor 和浏览器 smoke 的会话证明,显示层的成熟不是“做得更像控制台”,而是稳定消费只读事实、暴露空状态和降级状态,并避免任何 UI 侧交易推断或变异动作。
  • VPS / CI/CD 会话把同一条原则推到运行环境:服务器不是开发机的延长线,代码发布、数据刷新、数据对齐、远端同步和 readiness gate 必须分开;硬件选择也要服务于数据事实,而不是只问能不能勉强跑起来。

证据(2026-06-22 ~ 2026-06-23)

  • 已读取 2026-06-22 本地 session 目录下 19 个 JSONL 会话,覆盖策略 owner 审计、测试债务审计、执行安全预审、回忆录发布、Operator WebUI / readonly monitor 验证、VPS / CI/CD 设计等主题。
  • 关键会话包括:entry planner / paper plan / backtest sizing 语义审计,raw rules / StrategyContract 收敛审计,模拟账户重置与提交安全预审,Operator WebUI API readiness 与外部浏览器 smoke,readonly monitor 外部浏览器 smoke,以及 VPS 数据同步与部署设计。
  • 近期提交与会话结论共同指向同一条线:默认策略切换之后,不能只看配置是否指向新策略,还要证明旧解释路径、显示层推断和运行环境捷径不会重新成为事实来源。

第三十一章:止损成交以后,系统还要学会算剩下的风险

第三十章写到“默认策略还没有资格按下按钮”。到了第二天,这句话没有变轻,反而变得更具体了。

我原来以为,保护性止损这件事最难的是“有没有”。买入以后有没有挂止损,券商那边有没有对应订单,本地状态有没有记录,这些问题当然重要。但真正把系统推到墙边的,是另一个更细的追问:

止损已经部分成交以后,系统到底应该认为剩下的风险是什么?

这个问题听起来像会计题,实际上是无人值守交易的生死题。因为一个止损订单不是护身符。它的保护力取决于很多条件:订单类型是不是能在触发后真正变成市场可执行动作,触发价是不是已经被跳空穿过,订单剩余数量是不是刚好覆盖剩余持仓,券商是否已经成交了一部分,本地生命周期是不是还在拿原始数量当现实。

LongBridge 那边先暴露出一类问题:LIT 订单并不天然等价于 hard stop。以前系统很容易把“存在一个 SELL LIT”看成“保护性止损已经覆盖”。但如果价格跳过触发区间、订单没有成交、仓位还开着,这种判断就危险了。系统不能因为看见一张看起来像止损的单,就宣布风险已经被盖住;它必须区分 trigger-market 类型的硬止损、trigger-limit 类型的限价触发,以及 gap-through 之后仍未成交的风险暴露。

IBKR 那边给了另一个更有戏剧性的提醒:OUST 明明整体是赚钱的,系统却一度把它看成保护止损数量异常。后来只读快照把故事讲清楚了:这笔仓位的一部分已经被 STOP 卖掉,卖掉的那部分是亏的;剩下的股票反弹以后,整体净额反而是赚的。问题不在“止损有没有发生”,而在系统用原始 STOP 数量去判断覆盖,忽略了订单已经部分成交,剩余订单数量才是当前还在保护剩余仓位的现实。

这就是量化系统里很容易让人不舒服的地方:人的直觉会问“我现在是不是赚钱”,系统安全门却必须问“剩余风险是不是被正确覆盖”。两者都是真的,但它们不是同一个问题。赚钱不能自动证明止损覆盖正确;止损成交也不能自动证明风险已经归零。真正的 owner contract 必须把两者分开:盈亏归盈亏,保护覆盖归保护覆盖,券商 truth 归券商 truth。

通知系统也在同一天补了一块拼图。Bark 以前会告诉我“阻断了”“需要人工处理”,但这对操作者来说还不够。一个无人值守系统如果只会喊停,却说不清楚为什么停、停的是哪一组计划、哪些标的是因为现金、哪些是因为重复风险、哪些只是等待对账,那么人最后还是要回到日志里重新考古。于是计划通知开始携带完整计划和“为什么”。这看起来像体验优化,其实也是 owner 边界:通知不能自己解释交易事实,但它必须把 owner 已经给出的事实完整、克制地交给人。

这一天给我的教训,比“又修了一个止损 bug”更重。保护性止损不是一个布尔字段,不是一个订单 ID,也不是一行显示文本。它是一段会随成交、跳空、撤单、剩余持仓和券商回报不断变化的关系。系统要是真的想无人值守,就不能只在买入那一刻证明“我挂过止损”,还要在止损被部分成交、价格跳过触发区、券商订单状态变化以后,继续证明“我知道现在还剩下多少风险”。

所以默认策略的下一道门槛变得更清楚了:不是能不能生成计划,也不是能不能发出通知,而是当券商现实开始变脏、变碎、变动态时,系统还能不能不撒谎。

侧记:2026-06-23 那些真正改变判断的会话

  • 自动交易通知的追问从“为什么 Bark 说需要人工处理”推进到“完整计划和每条原因必须可见”。这不是为了让通知更热闹,而是为了让操作者不再从一条阻断消息倒推整条计划链。
  • LongBridge 的 hard stop 审计把 LIT 和真正的 hard stop 分开:存在 SELL LIT 不等于风险已覆盖,gap-through 且未成交、持仓仍开时不能 PASS。
  • OUST 的只读核对证明,交易盈亏和保护覆盖是两条不同事实线。当前整体盈利不能掩盖系统一度把原始 STOP 数量当作剩余保护数量的误报风险。
  • 多个只读审计给出同一个结论:protective-stop contract 已经有单一 owner 候选,但 plan、reconcile、repair、测试和可观测性还没有完全收口,不能因为普通路径看起来能跑就宣布 Stage 1 完成。
  • 买入滑点统计给出一个更朴素的执行假设:如果要在回测和计划里保守估计开盘买入更贵,当前应先用约 1% 作为主口径,并继续补样本。

证据(2026-06-23 ~ 2026-06-24)

  • 已读取 2026-06-23 本地 session 目录下 28 个 JSONL 会话,覆盖自动交易阶段通知、LongBridge hard stop 审计、IBKR 部分成交止损核对、保护止损 owner 收敛审计、买入滑点统计、Codex 协作模板复盘和回忆录结构调整。
  • 当天提交继续围绕同一条主线推进:止损价格错配进入自动修复、通知改成本地化且降低 owner-gap 噪音、已提交 dry-run 计划订单被识别、保护止损订单合同继续收敛。
  • 最新只读运行证据显示,系统已经能在报告里显式暴露 gap-through、partial fill、protective stop quantity mismatch 等对账计数;但会话审计也明确指出,运行闭环、测试覆盖和 owner 边界仍不能用“部分路径通过”代替最终验收。

第三十二章:交易员终端不是日志窗口

第三十一章之后,我以为下一步主要还是继续清理 owner:删旧 wrapper、迁移测试、让计划、对账、修复和显示都消费同一套合同。那当然仍然重要,但 6 月 24 日又把一个更朴素的问题推到前面:

如果自动交易卡住了,人第一眼到底看见什么?

当天的会话很密集,很多都在做只读审计。它们反复证明同一件事:系统已经不缺“能显示一点东西”的界面,真正缺的是不越权、不撒谎、还能让交易员快速判断的第一屏。paper_submit、operator status、monitor、parity、replay、target plan、strategy expected actions,这些名字看起来像工程内部的拆分,但背后都是同一个问题:谁有资格说“今天系统应该做什么”,谁只能把这个事实拿给人看。

这件事在自动交易“卡住”的排查里变得非常具体。

我最开始的问题很直接:为什么 auto 自动交易会卡住?直觉上很容易怀疑是数据没下载、面板没启动、进程死了,或者某个运行命令没有反应。但只读检查把这些假设一个个排掉以后,真正的问题反而更尴尬:refresh 子流程其实在跑,父流程也在等,只是进度回调没有被传进去,子进程输出又被静默捕获了。于是人的终端上看起来像一片空白,系统内部却只是进入了一个没有说话的等待区。

这不是一个普通的“日志少了”问题。无人值守交易最怕的,不是系统明确告诉你“我阻断了”,而是它在关键时刻沉默。沉默会逼操作者去猜:是卡死、是等待数据、是网络慢、是券商不可用,还是仍在安全门里排队。只要人开始靠猜,终端就已经失职了。

所以后来讨论“优秀的交易员终端应该长什么样”时,重点不再是把更多字段堆上去。一个给交易员看的终端,第一屏必须回答几个问题:现在安全吗?系统处在哪个阶段?下一步是谁做?为什么还没做?有没有人工动作?能不能继续自动?后面的计划、券商、信号、止损和事件流才是证据层。

这也解释了为什么同一天那么多架构审计并不只是“继续减文件行数”。当显示层还会从旧 cycle、heartbeat、run log、raw broker rows、workflow summary 里重新拼状态,它就不是纯显示层。它在用另一套语言解释交易事实。即使它不下单,也会影响人对系统的判断;人一旦根据它做决定,它就间接参与了交易。

我以前更容易把终端美观当成体验问题。后来才意识到,对自动交易来说,美观首先是扫描效率和风险排序:红色阻断不能藏在长日志里,等待原因不能散落在 JSON 里,风险状态不能被一堆“看起来正常”的表格淹没。真正好的终端不是日志窗口,也不是开发者调试台,而是一个克制的交易员决策台。

这一天留下的教训很清楚:系统越接近无人值守,越不能把“底层事实存在”当作“操作者已经正确看见”。事实 owner 要收敛,显示 owner 也要收敛;前者决定系统能不能安全行动,后者决定人能不能在系统沉默、阻断或等待时不被误导。

侧记:2026-06-24 那些真正改变判断的会话

  • 多组只读审计把 Stage 0 到后续里程碑的债务摊开:大文件、旧 wrapper、旧 projection、旧测试和旧状态字段的问题,不是代码风格,而是它们仍可能在关键时刻重新解释交易事实。
  • “是否一开始就该用成熟开源引擎”的讨论没有被简单回答成是或否,而是变成一条更清楚的边界:可以用成熟引擎做回放和对照,但不能把现有系统硬塞进去,再制造一个长期耦合层。
  • 自动交易卡住的排查证明,刷新任务并非没有执行,而是进度链路被静默吞掉。这个问题把“可观测性”从附属体验升级成无人值守安全条件。
  • 交易员终端设计会话把第一屏顺序重新排定:先给安全、阶段、阻断原因、下一步和人工动作,再给计划、券商、信号、止损和事件证据。
  • 同一天围绕 strategy expected actions、lifecycle stop、parity、target plan 和 replay 的审计说明,合同收敛不能只覆盖 BUY/ADD;退出、取消、公司行动和剩余风险也必须被纳入统一解释。

证据(2026-06-24 ~ 2026-06-25)

  • 已读取 2026-06-24 本地 session 目录下 64 个 JSONL 会话,覆盖前一日回忆录发布、Stage 0 / Stage 1 / Stage 2 / Stage 3 多轮只读审计、开源引擎路线讨论、术语解释、自动交易卡住排查、交易员终端结构设计和若干非本项目支线。
  • 当天策略仓库最新提交集中在 operator broker facts、只读 broker event stream、background data sync status、operator terminal monitoring、WebUI backtest command 边界和 broker fact display;这说明主线仍在把执行事实、显示事实和用户可见工作台拆开。
  • 会话中最有叙事价值的转折不是“又发现很多债”,而是系统开始把终端沉默、进度不可见和显示层重解释也纳入安全边界:无人值守系统不仅要知道事实,还要让操作者在正确顺序里看见事实。

第三十三章:验收不是一条绿勾

第三十二章之后,我以为接下来会更像收尾:终端第一屏已经被重新排序,自动交易状态也能说出更多人能读懂的话。照这个节奏看,剩下的工作似乎就是把几个界面补齐,把几个测试跑绿,然后找一个足够稳妥的时间点进入下一轮实盘前验收。

6 月 25 日的会话把这个念头又往回拽了一步。

当天第一个真正刺人的问题不是界面,而是仓位语义:如果策略说“单笔买 25% 总仓位”,执行时到底买多少股?是按价格先估股数,还是按资金下单再按开盘价剪裁?跳高保护会不会让系统买超过原本想要的仓位?

查完整条链路以后,答案反而不是最吓人的那种。跳高保护不会把股数放大到超过策略预算;系统的股数仍然受仓位、风险预算和现金共同约束。但它暴露了另一个更朴素的风险:如果现金约束用开盘限价或预留价来剪裁,而真实开盘没有触及那个较高价格,系统可能买少,留下一个低于 25% 的实际仓位。也就是说,这不是“系统偷偷加杠杆”的问题,而是“系统以为自己很保守,最后可能保守到没买够”的问题。

这个细节很小,却很有代表性。交易系统里的风险不总是以灾难姿态出现;有时候它只是一个看起来合理的保护参数,让实际仓位偏离了研究语义。更麻烦的是,如果终端和报告只告诉我“买入计划正常”,而不解释股数是被哪个约束压下来的,人就很容易把仓位不足误读成策略信号弱,或者把执行保守误读成系统稳健。

后面几组只读审计把这个问题放进了更大的验收框架里:BROKER_WAIT 到底是真状态还是投影产物?risk-reduction 修复会不会覆盖买入计划证据?IBKR 的 bracket parent/child 和 LongBridge 的当前活跃订单是不是同一种 broker truth?operator 和 notification 是在消费 owner snapshot,还是又从原始行、compact row、summary 字段里拼出“下一步”?

这些问题听起来像代码审计清单,但它们其实在追问同一件事:系统的“验收通过”到底是什么意思。

如果只有黄金测试通过,但 WDC 这种已有持仓的 lifecycle 在渲染输出里丢了 symbol,那不是体验瑕疵,而是验收对象错了。plan owner 明明知道它为什么跳过,operator snapshot 却只剩一个聚合原因;人看到的不是事实,只是事实被磨平以后的影子。

如果自动交易 E2E 审计说 data、signal、paper plan 没有新的 P0 代码阻断,但 broker truth、reconcile、repair 和 operator projection 没有本轮实证,那也不能叫完成。它最多说明“这段代码看起来没有新的硬伤”。真正的验收还要证明:券商事实是当前的、对账是可信的、修复是 owner 驱动的、显示层没有再解释交易事实、通知不会把内部代码词直接扔给人。

我以前很容易把“测试绿了”当成一种松口气的信号。到这一天才更清楚地意识到:无人值守交易的验收不是一条绿勾,而是一张证据网。它既要证明系统没有乱动,也要证明系统知道为什么没动;既要证明底层事实存在,也要证明操作者看到的事实没有被另一个投影层改写。

这一天还顺手回答了另一个外部框架问题:vn.py 的 IBKR 底层连接更成熟、更贴近官方 TWS 生态,但这不意味着可以把它整体替换进当前系统。成熟连接解决的是“怎么连上券商”,不是“谁拥有 broker truth、谁可以提交 mutation、谁负责修复、谁只负责显示”。如果引入外部框架,正确的位置也只能是底层传输适配器,而不能让它绕过已经收敛出来的 owner contract。

所以第三十三章真正记录的不是某个 bug,也不是某个新界面。它记录的是一个更笨、更难跳过的结论:越接近实盘前验收,越不能让单点成功替代整链证据。能跑、能看、能通知、能回测、能读券商,都只是局部能力;真正可托付的系统,必须把这些能力放在同一张事实所有权地图上。

侧记:2026-06-25 那些真正改变判断的会话

  • 仓位语义追问证明,开盘限价和跳高保护不会让系统超买,但可能在现金约束绑定时导致实际仓位低于按真实开盘价可买到的 25%。这把“保护参数也会改变执行语义”写进了实盘前检查清单。
  • 对 vn.py / IBKR 的研究没有导向“整体替换”,而是把外部成熟框架限定在底层传输适配器候选;broker truth、submit readiness、repair 和 operator projection 仍必须由当前系统的 owner contract 管住。
  • 多轮只读审计把 StrategyExpectedActions、broker order contract、target plan、repair planner、operator console 和 notification 放在同一张图里看,结论是主链正在收敛,但 zero-buy、旧 repair API、raw/compact 字段投影和 notification policy 仍有 P1 债。
  • 自动交易 E2E 审计把“没有新的 data -> signal -> paper plan P0 阻断”和“还不能验收整条链”分开:broker truth、reconcile、repair、operator projection 必须有本轮实证,不能靠代码阅读或局部测试替代。
  • operator snapshot 的黄金用例暴露了一个典型问题:plan owner 保留了已有持仓的 lifecycle,但 snapshot/rendered 输出把逐 symbol 事实压成聚合原因;这说明显示层不是“多显示一点”就够了,而要保住 owner 给出的粒度。

证据(2026-06-25 ~ 2026-06-26)

  • 已读取 2026-06-25 本地 session 目录下 13 个 JSONL 会话,覆盖仓位/跳高保护语义、vn.py / IBKR 对照、多轮 owner-boundary 只读审计、broker/order/plan/reconcile 链路审计、operator/notification 审计、自动交易 E2E 验收阻断审计和 operator snapshot 黄金用例分析。
  • 当天策略仓库从 broker event stream、operator display projection、notification、order repair、intraday entry limit、IBKR bracket facts、WebUI backtest 和 auto lifecycle read model 多线推进;但会话结论反复强调,代码推进不能替代 broker truth 和 operator projection 的完整验收。
  • 当天产生 30 个 d90g 回测报告,说明策略参数仍在被压力测试;但叙事上更重要的不是这些报告数量,而是同一天仓位语义被追问到执行股数和现金预留层面,防止“25% 仓位”只停留在口头配置。

第三十四章:控制台开始像控制台,现实却不肯配合演出

第三十三章把“验收不是一条绿勾”写完以后,我以为下一步会更工程化:既然证据网已经被看见,那就把 WebUI 补成一个真正能用的操作台,把 broker facts、auto state machine、backtest、data、tasks 和 config 都接到同一套只读 owner 合同上。

6 月 26 日的会话看起来也确实像在朝这个方向走。七个页面被重新收拢:指挥中心、回测、券商、交易、数据、任务事件、配置中心。界面不再像一排说明卡片,而更像一个交易员终端:深色、密集、表格化、有状态带、有分栏,也开始通过同源 BFF 去读真实 Core,而不是拿 fixture 把界面填满。

这本来是很容易让人兴奋的一步。一个系统如果终于有了像样的操作台,人很容易下意识觉得它“成形了”。但这一天最有价值的地方,恰恰是它没有让这个错觉顺利过去。

第一层不配合来自 IBKR facts。自动运行进程里确实有 IBKR 会话,券商连接也确实显示正常,订单、持仓、PnL 这些事实并不是完全不存在。可是 WebUI 的只读 broker stream 不能直接把“某处有事实”当作“owner 已经发布事实”。有些字段仍然显示 owner gap,不是因为页面懒得画,而是因为正确的只读载体还没有把这些行事实稳定发布出来。

这听起来像又一个后端字段问题,但它其实是第三十三章的延续:事实存在,事实可见,事实以正确身份可见,是三件事。交易系统最危险的诱惑之一,就是在第三步没完成时,用前两步糊过去。比如看到 cached order count,就把它画成最近订单事件;看到 broker snapshot 里有执行记录,就绕过当前流合同直接塞给 UI;看到浏览器 smoke 过了,就忘了它可能只是 mock 或 owner-gap 文案通过。

所以那天围绕 broker.stream.read_model.v2 的多轮审计,真正的结论不是“再补一列”。结论是:计数器只能是诊断,不能被提升为订单事实;fixture 只能是测试,不能被当成真实 broker truth;浏览器只能证明它自己实际访问到的同源路径,不能替 Core owner 合同背书。WebUI 可以变得更像控制台,但它不能因此拥有解释现实的权力。

第二层不配合来自运行时本身。当天一度需要确认到底是谁在占用 IBKR session。排查以后发现,持续持有会话的不是浏览器,也不是 WebUI 验收脚本,而是无人值守 paper runner。这个结论表面上解决了“谁在连券商”,实际上又打开了更严肃的问题:正在跑的进程到底是不是当前代码?它的 runId、pid、启动 commit、dirty 状态和当前 HEAD 是否一致?

后来再查自动运行状态时,现实更直接:auto 已经停了,旧 runner 不再是活进程;旧 heartbeat 停在前一个时间点;旧启动 commit 和最新 HEAD 不一致,而且旧运行时启动时不是干净状态。换句话说,不能因为昨天某个进程曾经跑过,就把它当成今天代码的 runtime evidence。运行时证据也有保质期。

第三层不配合来自数据和通知。当天确实尝试过数据下载,但 grouped daily 路径因为网络层问题超时;后续 data gate 仍然挡在目标日期之前。Bark 通知也不是“完全没有配置”,而是有发送请求、有 readiness,但投递链路被策略或 channel 状态抑制,后面还进入 backoff。这里的教训很刺眼:通知 ready 不等于通知送达,下载开始不等于数据新鲜,auto status 里有阶段不等于可以继续 broker mutation。

这些事实加在一起,把 WebUI 目标从“做一个漂亮面板”推成了更难的东西:做一个不抢权、不脑补、不用旧运行时粉饰当前状态的操作台。它要能告诉人页面正在看哪一个 owner,哪一个字段缺失是 owner gap,哪一个状态来自旧 runner,哪一个阻断来自数据 gate,哪一个通知只是请求过、但没有真正送到。

这一天还有一个旁支也很有意思:我去排查 TWS 启动失败,又讨论能不能从 ToS desktop 自动取 1 小时 OHLCV。那条线不直接属于当前自动交易主链,却像一个很好的旁白:成熟客户端里“看得见数据”,不等于磁盘上有一个干净可读的事实源;能启动券商软件,也不等于系统已经拥有可自动化、可验证、可维护的执行证据。外部工具越成熟,越容易诱人跳过 owner 边界。

所以第三十四章真正想记住的不是“WebUI 终于改漂亮了”。它想记住的是另一个更朴素的阈值:当控制台开始像控制台时,系统反而更要诚实地暴露自己哪里还不像一个可托付的操作系统。界面可以越来越像交易员工作台,但只要 owner facts、runtime provenance、data gate 和 notification delivery 还没有在同一条证据链上闭合,它就仍然只是一个正在接近现实的窗口,而不是现实本身。

侧记:2026-06-26 那些真正改变判断的会话

  • Operator WebUI 的目标从“把页面补齐”升级成“七页集成控制台”:指挥、回测、券商、交易、数据、任务和配置要消费同一套只读合同,不能各自用 fixture 或局部推断让页面看起来成功。
  • IBKR broker facts 的 owner-gap 审计证明,订单事件计数、缓存状态和 broker snapshot 里的事实都不能被 UI/BFF 擅自提升为当前订单行。缺 owner 字段时,正确显示是 owner gap,而不是合成一个看似完整的表格。
  • 自动运行排查把“进程还在”拆成更严格的运行时证据:pid 是否活着、runId 是否当前、启动 commit 是否匹配、startup dirty 是否为 false。旧 runner 的 heartbeat 不能替当前代码作证。
  • 数据 gate 和 Bark 通知排查证明,失败不只发生在策略层。下载超时、数据日期滞后、通知 channel 抑制、投递 backoff 都会让“系统正在运行”与“操作者已经被正确告知”分裂。
  • Backtest、Data、Tasks、Config 的浏览器验收开始围绕 same-origin、real Core、owner read model 和非 broker workflow 收敛;这让 WebUI 更像产品,但也把验收门槛从“页面可见”提高到“页面没有越权解释现实”。

证据(2026-06-26 ~ 2026-06-27)

  • 已读取 2026-06-26 本地 session 目录下 29 个 JSONL 会话,覆盖 Operator WebUI 集成控制台、P0A 自动状态机、P0B IBKR broker facts、P0C backtest、P0F 任务、配置与数据 workflow、auto runner 运行态排查、数据 gate 与通知投递排查,以及 TWS / ToS 本地客户端自动化支线。
  • 当天策略仓库新增的一组提交集中在 Operator WebUI 目标计划、只读自动状态机、IBKR broker stream、same-origin BFF、真实 Core 浏览器验收、broker facts scope、runtime reporting 和研究信号诊断。代码量很大,但叙事重点不是“又做了很多页面”,而是 owner facts 和 runtime provenance 开始进入同一个验收口径。
  • 最新只读证据显示,WebUI 已经明显不再是纯 demo,但仍未达到 P0 通过:它能显示七页控制台和真实 Core 路径,却仍需要 IBKR facts owner、auto runtime provenance、data freshness 和 notification delivery 一起闭合,才能说“交易员正在看见同一个现实”。

第三十五章:不是字段太少,是事实被重复解释了

第三十四章写到控制台不能抢现实的解释权。6 月 27 日,我原本以为下一步会比较朴素:把剩下的显示字段、提交路径和修复路径继续往 owner contract 上收一收,清掉几个旧 fallback,然后系统应该会明显安静下来。

结果那天真正暴露出来的不是“某个字段还没迁完”,而是更深一层的失败模式:同一个交易事实会在不同地方被重新解释。

这句话听起来像工程洁癖,但在交易系统里一点也不抽象。一个 expected buy,如果 plan owner 算了一次,parity 又从 signal 或候选列表猜一次,operator 再从 target basket 或 broker rows 拼一次,notification 最后又按 summary 文案解释一次,那就不是“四个地方都很努力”。那是四个地方都可能在不同时间、不同输入、不同脏状态下,对同一件事给出不同现实。

这一天的提交序列很像一场清账。target basket 不能再当 target plan 的事实源;planned entries 不能再冒充目标 owner;broker facts 不能再反推 expected buy;operator progress 不能从 raw reconciliation 里临时派生 mismatch rows;submit runtime 不能靠 options 字典和 repair kind 字符串决定提交窗口;cancel-only、OPG 补救、entry order type、protective stop order type 都必须被压回明确的 action / order / repair 合同。

如果把这些改动拆成单个 commit,看起来会很碎:重命名一个 parity 字段,迁走一个 mismatch projection,删除一个 raw target-plan 暴露,收口一个 submit-window scope。但把它们放回同一张图里,线索就很清楚:系统终于开始承认,最大的复杂度不是“函数太多”,而是每个函数都觉得自己可以顺手解释一下交易事实。

这也是那天新增“失败模式”文档的意义。它没有提出新的酷架构,只是把一个反复出现的问题钉在墙上:重复推断。过去我总以为失败来自某个模块还不够聪明,后来才发现,很多失败恰恰来自模块太聪明了。显示层想帮我补齐状态,修复层想帮我猜动作,提交层想帮我放宽窗口,测试 fixture 想帮我保住旧形状。每一个“帮忙”单独看都合理,合起来就会长出第二套系统。

当天还试了一条工具线:Serena 和 CodeGraph 这类代码智能工具,确实能帮助定位调用关系、依赖链和语义边界,尤其是在这种到处都是旧入口、旧字段和旧推断的代码库里。但它们最后也被放进同一条纪律里:工具可以减少找代码的成本,不能替 owner contract 做判断;索引可以加快路线侦察,不能替源码、测试和运行证据背书。换句话说,工具要服务证据,而不是制造新的自信。

回头看,6 月 27 日最值得记住的不是“又合了几十个收敛提交”。真正值得记住的是,我开始把系统失败从 bug 列表上升成模式列表:哪些症状会反复出现,为什么它们总是看起来像小修,正确的修法必须长什么样,哪些旧路径必须在同一个里程碑里被删除或去权威化。

这一天以后,“不要重复 owner”不再只是口头提醒,而变成了一条更硬的验收规则:只要旧路径还能报告成功,只要显示层还能从 raw 字段里拼事实,只要 submit runtime 还能从 side-channel options 里猜 repair 语义,就不能说 owner 收敛完成。系统要学会说“不”,首先要让每个事实只在一个地方开口。

侧记:2026-06-27 那些真正改变判断的会话

  • 代码智能工具讨论把“能不能更快读代码”落到实际边界:Serena / CodeGraph 有实际价值,尤其适合快速定位调用链和依赖关系;但它们只能作为只读侦察工具,不能替代源码、测试和 owner-boundary 审计。
  • 多组只读审计证明,主链并不是全线还在从 signal、heartbeat 或 display 乱推 expected buys;真正危险的是残留的二次解释点:operator projection 的 fallback 计数、targetPlanReconciliation raw field、CLI progress 重建 brokerPlan、submit runtime 解释 repair scope。
  • broker truth 到 target_plan、repair、submit runtime 的审计把 OPG 补救、BUY/ADD order type、protective stop order type、cancel-only repair 分开看:有些 owner 已经清楚,有些默认值和 entry-kind 推断仍会让合同漂移。
  • 当天的修复提交随后把这些发现逐步压回 owner:target parity、target-plan mismatch projection、repair action、submit selection scope、fresh submit repair、action kind、broker rows 和 operator display 都被要求消费明确合同。
  • 面向电脑新手的 Codex 使用建议也提醒了同一个原则的生活版:先解释、再操作;先模拟、再真实;先小任务、再大系统。复杂系统不是靠胆子大推进,而是靠每一步都知道自己正在相信什么。

证据(2026-06-27 ~ 2026-06-28)

  • 已读取 2026-06-27 本地 session 目录下 11 个 JSONL 会话,覆盖前一日回忆录发布收尾、Serena / CodeGraph 代码智能工具评估与隔离落地、auto/operator/status 只读审计、paper/plan 只读审计、broker truth -> target_plan -> repair -> submit runtime 审计、target parity / projection 旧推断审计,以及一条面向新手的 Codex 使用说明。
  • 当天主仓库在 2026-06-27 本地时间内新增 57 个提交,从 Stabilize auto execution contracts and operator output 推进到 Carry preflight repair actions as owner contracts,主线集中在 target parity、repair action、submit selection、operator projection 和 raw target-plan field 去权威化。
  • 新增的 auto-trading failure patterns 文档把“重复推断”写成固定失败模式,并给出 owner chain:strategy contract、plan expected actions、broker order contract、broker truth order、reconcile decision、repair action、submit runtime、operator projection。
  • 本次只读检查时,主仓库 HEAD 已推进到 Fix notification readiness and broker retry cadence,但工作区仍有未提交研究文件改动和新 research 脚本;这些属于当前脏状态证据,不应被回忆录发布流程清理或回滚。

第三十六章:不是没有数据,是第一屏还没有说真话

第三十五章之后,我一度以为问题已经很清楚了:既然不能让显示层重复解释交易事实,那就把信号、计划、订单、提交资格、券商真相都压回服务层的只读合同,前端只负责渲染。听起来像是把一堆散落的电线重新接进配电箱,接完以后,控制台自然会亮。

6 月 28 日的工作确实沿着这条路走了很远。指挥中心不再应该从研究信号接口猜“当前信号”,而要从自动交易计划 owner 读当前投影;订单生命周期不再应该从一堆嵌套字段里找行,而要有自己的只读投影;提交资格和券商事实也不能由浏览器 GET 顺手触发网络读取,而必须来自缓存快照或已有 read model,并明确标注范围、完整性和非权威缺口。

这些听起来都是后端合同问题,直到晚上做了一轮真正的界面体验审计,我才发现另一个更尴尬的问题:事实已经有了,操作者未必看得见。

页面顶部还在显示“数据时间未知”“数据 –”“版本待确认”,但底层数据管线已经返回了交易日、最新完成日期和整体通过状态。指挥中心说自己是 partial,却没有告诉人“哪几个 owner 已经 OK,哪一个 owner 仍然 partial”。数据阶段明明能说出时间门、下载资格、验证、转换、远端同步、缺口填补、公司行动这些阶段,界面却只给出一句很薄的“数据已就绪”。最要命的是,等了十几秒以后,心跳延迟、更新时间和下一触发时间仍然不动。操作者看到的不是“系统正在等待”,而是“这个页面到底活着没有”。

那一刻我意识到,控制台的失败不只分两种:一种是 UI 自己发明事实,另一种是 UI 明明消费了 owner facts,却没有把最该被人看到的事实放到第一屏。前者会制造第二套交易系统;后者会把人重新推回日志考古。

这也是“交易员操作台”和普通 dashboard 的差别。普通 dashboard 可以把事实铺开,让人自己慢慢看;无人值守交易的操作台必须先回答几个非常朴素的问题:现在活着吗?现在停在哪一步?为什么等?下一步会自动做什么?需要我做什么?哪些事实已经可信,哪些只是降级证据?如果这些问题藏在一堆卡片、chip、缩略时间戳和被截断的长文本里,那它再克制、再现代、再像 Apple 风格,也只是一个漂亮的疑问句。

所以 6 月 28 日给我的教训不是“前端还要继续美化”。恰恰相反,那天最重要的判断是:先别碰颜色和大布局,先让事实清楚、实时、完整地呈现。控制台的第一屏不是展示层,它是操作者对系统现实的入口。它不能替 owner 思考,但它必须把 owner 已经知道的事情讲明白。

侧记:2026-06-28 那些改变第一屏判断的会话

  • 行情事实源讨论把 Google Finance 和 TradingView 放回了非权威位置:它们可以辅助人工查看或交叉参考,但不能成为无人值守系统里的事实行情 owner。
  • repair 链路的只读审计继续证明,修复动作、route、status 和 reason 仍有残留解释点;正确方向不是再补一个包装层,而是让 repair owner 输出更明确的合同,并用静态测试禁止多层重建。
  • Operator WebUI 的多轮实现把当前信号、自动生命周期、订单生命周期、提交资格、券商事实继续压成服务层只读投影;浏览器和 BFF 只消费这些投影,不能直接拼交易事实。
  • 晚间 UX 审计把问题从“布局是否好看”拉回“事实是否可判断”:顶部摘要不可信、时间没有实时感、partial 状态不可操作、数据阶段缺少验证细节、长文本截断影响判断。
  • d90g 研究支线被放进独立研究工作区,并明确只用研究数据库和缓存;这条线还没有产出足够新的策略结论,但它延续了同一条纪律:研究可以探索,不能污染正式事实源。

证据(2026-06-28 ~ 2026-06-29)

  • 已读取 2026-06-28 本地 session 目录下 21 个 JSONL 会话,覆盖行情事实源评估、repair owner 审计、IBKR Web API pacing 与 WebSocket 订阅问题定位、Operator WebUI W1/W2/W3 只读投影实装、submit readiness / broker truth projection 设计与实现、d90g 研究工作区启动,以及一次真实页面 UX 审计。
  • 当天主线没有触发真实下单、没有启用实盘交易,也没有修改策略语义;核心变化集中在“事实如何被服务层拥有、如何被控制台第一屏呈现”。
  • 本轮发布前检查时,策略仓库仍有大量未提交的 Operator/WebUI、IBKR live facts、auto runner、Web API 和测试改动;这些属于并行工程现场,不应由回忆录自动化清理或回滚。

第三十七章:研究开始学会先否定自己

到这里,我已经不太缺“能跑出一个漂亮数字”的脚本了。

真正缺的是另一种能力:一个研究想法冒出来以后,系统能不能先问它三个难听的问题——你是不是用了旧语义?你是不是只在一个窗口里好看?你是不是靠少数超级赢家把自己包装成了规律?

2026 年 6 月 29 日,d90g 的研究线变得非常忙。忙到如果只看会话数量,很容易把那一天写成一串流水账:一个 runner 结束,另一个 runner 启动;一个报告落盘,另一个 smoke 通过;半小时检查一次,像给研究工厂装了打卡机。

但这一天真正值得记住的,不是“跑了很多实验”。

它更像是研究流程第一次被迫承认:旧 wrapper 里那些诱人的线索,只能算嫌疑人,不能算证人。每一条线索都必须回到正式策略合同和正式回测引擎里重跑;必须用修正后的 10 年 / 5 年窗口;必须分 compound / fixed 两种资金模式;必须说明它到底是主线候选、风险提示,还是应该当场关掉。

这和早期研究的心理完全不同。

早期我看到一个高收益组合,会本能地想:“这个是不是 alpha?”后来我学会的第一层怀疑是:“是不是数据或语义错了?”到了这一天,怀疑又往前走了一步:就算语义是正式的、数据是研究隔离的、窗口也对了,一个结果仍然可能只是窗口里的巧合、右尾赢家的幻觉,或者一个被旧假设命名得太漂亮的噪音。

于是那天的研究流很像一次大规模排队受审。

有些线索很快被关掉:现金门槛、early failure、entry throttle、一些 second-ADD 变体,看上去有故事,进了正式引擎以后却没有足够资格继续扩网格。它们没有变成新策略,反而变成了更有用的负证据:以后别再拿同一类直觉重复烧时间。

有些线索没有被关掉,但也没有被立刻晋升。比如 missed-fill 和 top-winner perturbation 继续提醒我:d90g 的漂亮收益仍然严重依赖少数关键赢家,错过某些入场会让 compound 结果大幅缩水。这个结论不新鲜,但它被正式扰动实验重新确认以后,味道就不一样了。它不再是“我担心会这样”,而是“系统已经用正式 replay 告诉我,确实会这样”。

还有少数线索第一次露出“可能值得继续追”的样子。最典型的是一个更温和的 market-state stop-tighten 变体:在 10 年和 5 年、compound 和 fixed 四个象限里都没有伤害基线,甚至略有改善。composite_tighten_15 把 10 年 compound 从 26440.76% 推到 29506.41%,5 年 compound 从 4542.91% 推到 4762.80%;fixed 口径也只是小幅改善,而不是另一个暴力神话。听起来像好消息,但这一次我没有把它写成“发现新 alpha”。更准确的说法是:它终于拿到了一张继续审问的号码牌。

这个变化很小,也很大。

小在它没有让系统立刻更会赚钱。那天没有实盘开关被打开,没有默认策略被替换,也没有一个参数因为表格好看就被塞进生产配置。

大在研究流程终于有了一个更像工程系统的姿态:不是每个想法都要被保护到“看起来还有希望”,而是每个想法都要被设计成可以被关掉。真正有价值的研究,不只是找到候选参数,还要减少以后反复纠缠的假设。

我后来意识到,量化研究里最危险的不是“没有灵感”。

最危险的是每个灵感都像有点道理,每个回测都能截出一段漂亮区间,每个失败都能再加一个条件解释过去。只要系统允许这种解释无限生长,研究就会变成参数神话:永远有下一层网格,永远有下一种过滤器,永远有一个“再试一下”的理由。

6 月 29 日这条线真正带来的教训,是把研究也纳入 owner 边界:正式合同负责定义策略,研究 runner 只能提出可证伪假设,报告负责记录它该继续、该降级,还是该关闭。研究可以探索,但不能再偷偷替正式系统改写现实。

侧记:2026-06-29 那些真正改变研究判断的会话

  • d90g 研究检查覆盖了一整天的半小时轮询。最初的关键动作不是调参,而是先补研究 cache、隔离研究数据库,并确认正式 baseline 能复现冻结基线;这把旧研究 wrapper 和正式引擎之间的差异摆到台面上。
  • corrected execution-pressure、gap/risk、delayed-start、top-winner、missed-fill 等扰动实验依次落账。它们共同改变的不是某个参数,而是研究证据的门槛:风险和右尾依赖必须进入正式 replay,不能只靠旧报告口头提醒。
  • 多个看似合理的候选被正式关掉或降级:cash gate、early failure、entry throttle、second-ADD 变体没有拿到继续扩张的资格。其中 marketStateEntryThrottle smoke 在 5 年 compound / fixed 下与 baseline 完全一致,是严格 no-op 方向。这些负结果减少了未来的研究噪音。
  • market-state stop tightening 的温和版本首次在四个象限里都没有伤害基线:composite_tighten_15 在 corrected 10y/5y x compound/fixed 全部非负,而更激进的 composite_tighten_12 明确劣化。它仍只被记录为“可继续研究”,不是生产结论。这个克制,比结果本身更重要。
  • 同日的 Schwab OAuth 查询、broker mutation 第一阶段审计、线程工具 smoke 和量化教材审读都提供了旁证:系统正在把“能不能省事”压回官方契约、安全边界和工具可用性,而不是绕过约束。

证据(2026-06-29 ~ 2026-06-30)

  • 已读取 2026-06-29 本地 session 目录下 44 个 JSONL 会话,覆盖 2 个回忆录发布会话、全天 d90g research automation、Schwab OAuth 研究、broker mutation command-bus 审计、线程工具 smoke、量化教材审读和相关最终结论。
  • d90g research worktree 处于研究专用脏树,包含一批 formal runner、测试和状态文档改动;本轮回忆录只读取这些证据,没有触碰策略代码、交易配置或任何下单路径。
  • 6 月 29 日主仓库有 6 个提交,主要围绕 Operator live facts、SSE 事件流、auto resume owner 和 broker mutation executor 收口;这些提交继续支持“事实和 mutation 都要回到 owner”的主线。
  • 本章没有把任何 d90g 研究结果写成生产策略建议;所有数字只作为研究流程和证据门槛变化的叙事材料。

第三十八章:工作台越像工作台,老板越不能换人

第 37 章之后,我以为接下来会轻松一点。

研究线已经开始学会先否定自己,操作台也已经有了越来越多真实事实。按理说,剩下的工作应该是把页面做得更清楚、把等待做得更顺滑、把恢复流程做得更自动。听起来像是产品完善阶段,不再像前面那些会推翻系统模型的大问题。

但 2026 年 6 月 30 日又提醒了我一次:交易系统里最危险的“完善”,往往不是新增功能,而是一个原本只该等待、只该展示、只该桥接的地方,悄悄开始替真正的 owner 做决定。

当天有两条线同时往前走。

第一条线是开盘恢复。

早期我会把它理解成一个很朴素的问题:开盘后订单没有完全成交,系统再查一查、等一等、补一补就好了。只要监控循环足够勤快,5 秒、10 秒刷新一次,似乎就能把风险捞回来。

后来只读审计把这个想法拆开了。

高频检查本身不难。真正难的是:谁有资格说“这是开盘未成交 entry”?谁有资格计算 remaining quantity?谁有资格确认开盘价、当前价、策略 cap 和风险价格之间的关系?谁有资格把一条 repair action 交给 submit runtime?

如果这些判断散在等待循环、提交脚本、状态摘要和页面里,系统看起来是在自动恢复,实际上是在把同一个交易事实交给多个地方重新解释。那不叫恢复,那叫几个部门同时拿着印章。

所以那天的审计结论反而很有价值:一部分路径已经走对了,开盘窗口高频、open-price evidence、repair contract、blocked rows 这些东西开始有了 owner 形状;但另一部分还没有收口,尤其是等待循环仍然有可能把 supervisor contract 降级成普通 monitor wait。这个 FAIL 不是坏消息。它像是系统在正式说:不能再让“等待”顺手当老板。

第二条线是 Operator WebUI。

那天晚上,三个并行 worker 同时推进了三个页面:Broker Cockpit、Operations、Tasks。它们的方向都很对:Broker 页面不再只是告诉我“有券商数据”,而是把 live facts 的来源、字段状态、订单/持仓/成交空状态和 P&L 说清楚;Operations 页面不再只是 compact lane,而是开始像一个运营恢复和权限工作台;Tasks 页面也从占位壳变成可以接真实只读路线和 read-model 的组件。

这听起来像纯前端工作。

但在这个系统里,前端从来不是“纯前端”。一个页面只要展示 broker truth、submit readiness、repair action、runtime status,它就会影响操作者的判断。它越像一个真正的工作台,就越不能自己拼事实、自己补语义、自己猜下一步。

所以三个 worker 的共同约束反而比页面本身更重要:只消费已有 owner / read-model 合同;不改 runtime、broker、session、provider、交易链路;如果类型或共享模型缺字段,就停下来报告,而不是顺手扩一层。也就是说,页面可以变聪明,但聪明只能体现在组织事实、暴露缺口、让人少翻日志;不能体现在替底层 owner 做第二次判断。

这一天的教训,把前面几章串了起来。

第 35 章说,问题不是字段太少,而是事实被重复解释。第 36 章说,不是没有数据,而是第一屏还没有说真话。第 38 章又往前推了一步:当系统开始有恢复、工作台、任务事件和券商 cockpit 时,真正要防的不是“页面不够好看”,而是页面太好用了以后,人会开始相信它就是老板。

它不是。

老板仍然应该是那条无聊但可靠的 owner chain:计划 owner 说今天应该做什么,券商真相 owner 说现实发生了什么,repair owner 说哪些风险可以安全收回,submit runtime 只消费合同去执行,Operator WebUI 只把这些事实排成一个人能读懂的工作台。

这也是为什么那天没有因为三个页面都能跑起来,就把它写成“操作台完成了”。

更准确的说法是:操作台第一次开始有了完整工作台的样子,但系统也第一次更明确地要求它保持克制。能看见更多,不等于能决定更多;能组织事实,不等于能拥有事实;能把按钮和状态摆得很漂亮,也不等于可以绕过交易链路。

我后来越来越觉得,交易系统的 UI 和普通 SaaS 最大的不同,就在这里。

普通软件的页面如果多做一点“智能推断”,可能只是体验更顺。交易系统的页面如果多推断一点,就可能把一个缺失的 owner gap 包装成“看起来已经知道了”。而一旦操作者相信了这个包装,风险就不是界面风险,而是交易风险。

所以第 38 章真正记录的,不是三个页面,也不是一个开盘恢复分支。

它记录的是系统开始承认:工作台越接近真实使用,越要把权力往后收。前台负责把事实讲清楚,后台负责真正拥有事实。中间不能靠等待循环、桥接代码、临时页面或漂亮组件偷偷换老板。

侧记:2026-06-30 那些真正改变边界判断的会话

  • 开盘恢复的两次只读审计给出了一正一反的结论:repair contract、开盘价验证、部分成交和 blocked rows 的 owner 形状已经出现;但等待循环还没有完全做到只消费 supervisor contract。这个反面结论把“高频监控”重新定义成 owner-contract 问题,而不是循环间隔问题。
  • IBKR WebSocket 审计把 live facts 的 stream owner 边界说清楚:单一 order stream owner 可以继续收敛,但 runtime bridge 不能因此变成新的 broker truth 或 repair owner。连接更实时,不等于事实权威可以换人。
  • Operator WebUI 三个并行 worker 分别推进 Broker Cockpit、Operations 和 Tasks,但都被限定为 read-model 消费者。它们让页面更像工作台,同时也证明一个工作台的价值在于暴露 owner facts 和 owner gaps,而不是偷偷补齐它们。
  • d90g 研究线在当天继续大量巡检和证伪,但没有形成新的生产策略结论。它更像第 37 章的延长证据:研究自动化可以忙,但忙碌不能自动升级成故事转折。

证据(2026-06-30 ~ 2026-07-01)

  • 已读取 2026-06-30 全部 42 个本地会话原文,覆盖 d90g research 自动化、回忆录发布回查、开盘恢复只读审计、IBKR stream owner 审计、Operator WebUI 三个并行 worker、以及一个与量化系统设计语言相关的用户提问。
  • 当天主仓库工作树处于大范围并行开发脏树;本章只读取证据并更新回忆录,没有触碰交易代码、策略配置、自动交易配置或任何券商 mutation 路径。
  • 开盘恢复相关 diff 显示,变化集中在时间触发策略、自动恢复策略、开盘执行恢复、订单/repair contract、提交等待路径和对应测试;这支持本章“等待循环不能当 owner”的判断。
  • Operator WebUI 的三个 worker 都明确限制在页面组件和局部样式范围内,并以“只消费已有 read-model / owner 合同”为验收前提;真实 live 数据接入和主线程统一接线仍需要后续验收,不能在本章中宣称产品完成。

第三十九章:止损不是一个价格,而是一句话有没有说完整

第 38 章刚写完,我其实又犯了一个很熟悉的错误:以为“保护性止损”这条线已经讲得够多了。

前面已经写过缺止损、错止损、部分成交、gap-through、LIT 不等于硬止损、修复 mutation 不能被 submit window 误伤。按常理说,这条线应该已经从“有没有止损”进化到了“剩余风险是否被覆盖”。我一度以为,剩下的只是把这些规则继续实现干净。

2026 年 7 月 1 日下午的两次只读审计,把这个想法又往下按了一层。

第一份审计看的不是券商下单,而是 d90g 的 stop lifecycle 语义。它给出的结论很微妙:这不是一个“策略引擎公式写错了”的问题。生命周期 owner 大体是清楚的,真正可疑的是一个更隐蔽的污染路径:ADD 信号里的初始止损,有可能被本地买入侧元数据当成 canonical lifecycle stop 的来源。也就是说,系统不是不知道怎么算止损,而是仍然有地方可能把“这次加仓信号旁边带来的价格”误当成“这个仓位生命周期应该信的价格”。

第二份审计更直接,也更刺耳:当前代码对 d90g 来说并不安全。它主要比较固定止损价格,某些场景下也看固定止损订单类型,但如果券商那里挂的是一个覆盖全仓的原生 trailing stop,而且没有传统意义上的 stopPrice,系统可能静默认为对账通过。

这句话听起来像一个很小的技术细节。

但它真正刺中的是系统安全门的语言能力。

以前我总想把保护性止损简化成一个价格:预期价格是多少,券商价格是多少,差多少,能不能 repair。后来我知道数量、订单状态、部分成交也重要。到了这一天,问题变成:止损不是一个价格字段,而是一句完整的交易表达。

这句话至少要说清楚几件事:

  • 它是固定 stop,还是 broker-native trailing stop?
  • 如果是 trailing stop,trail percent 是多少?
  • 这个止损来自 entry-day initial stop,还是持仓生命周期里的 hard stop / trailing / super-winner / chandelier 规则?
  • 这个表达是策略 owner 说出来的,还是 repair / reconcile / broker normalization 在中间自己翻译出来的?
  • 如果券商订单和系统预期都是“保护性止损”,它们保护的是同一种风险吗?

只要其中任何一层被省略,系统就可能发生一种很危险的误会:双方看起来都在说“止损”,但一个说的是固定触发价,一个说的是跟踪百分比;一个说的是初始买入保护,一个说的是持仓生命周期保护;一个说的是风险收缩动作,一个说的是某个订单刚好长得像保护。

这就像两个人都说“我会负责”,但一个负责写邮件,另一个负责按按钮。听上去责任到位,真出事时才发现他们说的不是同一件事。

当天后面的提交,不再只是给旧判断补一个字段。它们沿着审计结论把 owner 语言补全:固定止损订单类型要由 owner 发布,percent trail 要和 fixed protection 对齐,生命周期 stop target 要共享验证,reconcile rows 要成为 repair 的来源,冗余止损清理也要由 repair owner 授权。更重要的是,后续补上了 broker execution full-chain golden test 和 LongBridge deferred stop recovery 覆盖,说明这已经不是单点修复,而是在把“止损表达是否完整”压成可回放的合同。

同一天的回测工作台也发生了一个小但很有象征性的变化:收益/回撤主图去掉了买卖点。乍看这是 UI 调整,其实逻辑和止损线一样:账户收益曲线应该表达账户级每日权益,不应该把交易 marker 混进主叙事里。成交点当然重要,但它应该去 attribution、drilldown 和单票复盘里解释,不该伪装成账户权益本身的一部分。

这两件事一前一后,让我更清楚地看到一个规律:

系统里很多 bug 并不是因为事实不存在,而是因为事实被放错了句子。

止损价格是真的,trailing percent 也是真的;交易 marker 是真的,账户权益也是真的。但如果系统把它们混成一句话,操作者听到的就不再是事实,而是一种看似合理的误译。无人值守交易最怕的不是系统说“不知道”,而是系统用一个不完整的句子说“我知道”。

这章给我的教训,比又修了一串 stop repair 更具体:

owner contract 不是为了让字段看起来正式,而是为了让系统说完整的话。

当一个止损被表达成完整合同,repair 才知道能不能安全动作;reconcile 才知道比较的到底是什么;Operator WebUI 才知道自己只能展示哪一层事实;自动循环才不会把“没有继续暴露新风险”误当成“旧风险已经收回”。反过来,如果合同只剩一个价格,系统越自动,误会就越危险。

所以 7 月 1 日这条线,不是第 31 章或第 24 章的简单重复。前面几章是在问“有没有保护”和“剩余风险有没有被覆盖”。这一章问的是更靠近语言本身的问题:

系统说“我有止损”的时候,到底有没有把这句话说完整?

侧记:2026-07-01 那些真正改变止损语义判断的会话

  • d90g stop lifecycle 只读审计先把问题从“策略公式是否错误”改写成“生命周期 owner 是否会被 ADD signal 初始止损污染”。这让修复目标从局部价格计算,转向持仓生命周期来源的边界。
  • protective stop broker expression 审计明确指出,单靠固定 stopPrice 对账不够;broker-native trailing stop、订单类型、trail percent 和 stop source 必须进入 expected contract,否则一个全仓 trailing order 可能静默通过。
  • 当天的修复提交沿着这个结论继续收口:fixed stop order type、percent trail 对齐、lifecycle target validation、reconcile owner rows、repair owner 授权、broker auth retry 和开盘恢复 owner guard 都被串进同一条风险收缩链。
  • 回测工作台去掉收益曲线上的交易 marker,和止损修复共享同一个教训:真实事实也要放在正确的 owner 句子里。账户权益、交易事件、单票复盘和券商止损表达不能互相抢叙事主语。

证据(2026-07-01 ~ 2026-07-02)

  • 已读取 2026-07-01 全部 14 个本地会话原文,覆盖 Operator WebUI 三个 runtime/read-model worker、一次回忆录 no-op 与一次第 38 章发布、Operator 自动化接管、回测收益曲线修正、d90g stop lifecycle 双审计、以及三个回测工作台独立组件任务。
  • 当天主仓库提交集中在 stop lifecycle、protective stop repair、broker execution golden test、backtest workbench、IBKR stream facts、opening recovery owner guard 等方向;其中止损表达和开盘恢复相关提交形成了新的安全语义转折。
  • 本章没有触碰策略代码、交易配置、自动交易配置或任何券商 mutation;所有判断来自只读会话原文、提交日志、当前回忆录结构和公开发布规则检查。
  • 回测工作台相关内容只作为“事实放回正确叙事层”的旁证;本章不宣称回测产品已经完整验收,也不把 UI 改动替代止损 owner contract 的验收。

第四十章:有些 fail-closed 不是安全,而是把风险留给明天

第 39 章刚把止损讲成“一句话有没有说完整”,我很快又被迫承认:就算系统终于能把话说完整,也不代表它一定会做对下一步。

以前我对自动交易安全有一个很粗糙的直觉:不确定的时候就停,fail-closed 总比乱动好。这个直觉在很多地方仍然成立。真实下单、开新仓、扩大风险、选择账户、确认现金、判断券商可用性,这些地方如果证据不够,系统就应该宁可不动。

但 2026 年 7 月 2 日的会话把这个直觉撕开了另一面。

那天晚上的几轮只读审计先确认了一个 P1 级别的问题:某些自动运行路径在遇到诊断性 blocker、修复失败、cycle facts 缺失或异常时,可能直接让无人值守循环停下来。表面上看,这仍然叫 fail-closed;可如果当时系统需要做的是保护性平仓、紧急退出、缺失止损修复、券商 truth 重读,或者只是继续保持只读监控,那么停下来就不一定是安全。它可能只是把风险从“自动链路正在处理”变成“明天人工才会发现”。

这就是我当天被提醒的一句话:

若某些历史 payload 还没有 expected-actions contract,保持 fail-closed/不推断,为什么会有历史 payload,你应该解决 legacy 的问题,自动交易链路严禁随意 fail-close。

这句话很重。它不是在要求系统为了兼容历史数据继续瞎猜;恰好相反,它是在要求我不要把“历史 payload”当成一个可以无限保留的豁免区。一个无人值守交易系统不能一边说自己有唯一 owner contract,一边又在 steady-state 运行里接受半旧半新的 payload,然后在遇到缺字段时摆出一副很负责的样子说“我停了,所以安全”。

真正的问题是:为什么旧形状还会流进当前链路?谁允许它继续成为输入?它应该被迁移、拒绝、修复,还是只允许在明确的历史导入工具里出现?

这和前面几章反复说的“第二套真相”其实是同一个问题,只是换了一个更危险的外壳。以前的第二套真相常常表现为 UI 多解释一句、监控多合并一个字段、通知多猜一个状态。7 月 2 日这一天,它变成了另一种样子:运行时把旧 payload、诊断 blocker、显示投影、submitted evidence、compact diagnostics、repair reason 放在一起,某些路径可能不再主动恢复,也不再明确交给 owner 处理,而是用 fail-closed 结束对话。

如果只看局部,这种代码很容易显得合理:

  • 缺少 expected-actions contract,所以不要从 plan rows 里重建。
  • broker order truth 不完整,所以 stop repair 先别继续。
  • 有 duplicate risk 或 partial fill warning,所以整批 mismatch rows 先跳过。
  • 账户状态正在被另一个流程写,所以 preflight reconcile 先失败。

每一句单独看都像安全。但合在无人值守链路里,它们可能变成另一种风险:系统越来越擅长拒绝负责,却没有越来越擅长把问题送回真正的 owner。

当天后半夜的工作,其实就是把这几类“看起来安全的停下”重新分类。

第一类,是重复推断层要收口。targetPlanMismatchRows 不能继续被结果投影、通知、operator event、progress rows 和 auto progress contract 各自解析;BUY / ADD / CANCEL_STALE 也不能让展示层继续从 plan row 里猜。于是新的投影 owner 被推到前面,显示层只消费 owner contract;submitted evidence 不再从 submitted rows 重新生产 strategy expected actions;compact diagnostics 继续保留输出,但被标记成 diagnostic-only / non-authoritative。

第二类,是 stop repair 不能被无关 blocker 整体压死。缺失止损和错配止损是风险收缩动作,它们当然不能在券商事实不足、账户选择不明、订单真相危险时盲目执行;但也不能因为一个和当前 symbol、当前 repair action 无关的 blocker,就把所有安全修复都跳过。这里真正需要的不是更宽的放行表,而是 symbol 级、owner 级、action 级的边界:哪些 blocker 必须阻断,哪些 blocker 只应该阻断开新风险,哪些 blocker 仍然允许安全止损修复继续。

第三类,是自动循环不能把一次 recoverable failure 当成终局。保护性止损修复失败、券商认证暂不可用、order truth 读不到、startup/preflight owner facts 不完整,这些都可能需要 retry、monitoring wait、owner follow-up routing,而不是让 runner 悄悄退出。后面的提交名字越来越集中在这一点上:保持自动 runner 活着、保持 stop repair 可重试、把 broker mutation 通过 decision owner、把 IBKR session owner 收敛成唯一 runtime owner、把 preflight state 写入串行化。

第四类,是并发安全不能靠“我没写成功”来假装安全。preflight reconcile 和 submit / cancel / flatten 可能写同一个 paper account state。如果 broker/network 读取放在锁外,这是合理的;但真正的 account state load-mutate-save 段必须进入可等待的账户锁。否则系统在锁忙时立刻失败,可能又变成一个漂亮的 fail-closed 标签,实际没有解决同账户状态竞争。

这一天的教训让我对“安全”这个词更谨慎了。

安全不是所有路口都放红灯。
安全是系统知道哪条路是开新风险,哪条路是收旧风险;知道什么时候必须停,什么时候必须等,什么时候必须重试,什么时候必须把旧 payload 清出主链;知道一个 UI 字段只是 diagnostic,不能因为它刚好长得像 owner fact,就让别的层拿去当交易事实。

更深一层看,7 月 2 日其实把第 38、39 两章连在了一起。

第 38 章说,工作台不能当老板。
第 39 章说,止损必须把话说完整。
第 40 章补上的,是老板说完整以后,系统还要敢于把“下一步该由谁负责”执行到底。

如果一个风险修复 owner 已经存在,显示层就不能自己修;但如果显示层没有资格修,也不代表自动链路可以随便停。正确的动作应该回到 owner:修复、等待、重试、升级为人工、或明确拒绝。不能把“我不推断”偷换成“我不处理”。

这就是无人值守系统最难的一层:它必须同时抵抗两种冲动。

一种冲动是瞎猜,看到缺字段就自己补一个解释;另一种冲动是甩手,看到旧字段就直接停。前者会制造第二套真相,后者会制造第二套逃避。真正的 owner contract 要同时防住这两边。

同一晚还有两次更刺耳的纠偏,把这个教训从代码抽象拉回了操作现场。

第一次是关于“无待补止损”的输出。系统把两个券商都显示成没有待补/待修复保护止损任务。单看这句话,它也许是在描述当前 broker truth:此刻没有已成交且裸露的持仓需要马上补止损。但用户指出,IBKR 仍需要开盘后检查成交状态和 5 分钟窗口,长桥也仍需要开盘校验成交后马上补保护止损。也就是说,pending primary BUY 后仍然存在生命周期义务;它不等于当前已有裸仓,也不等于以后没有保护责任。

这一下让我意识到,清理旧字段时最容易删掉的不是代码,而是阶段性语义。一个显示字段如果把“现在没有可执行的止损修复任务”压平成“没有待补止损”,操作者就会失去下一步风险的上下文。正确输出应该保留这种差异:当前没有已成交裸露风险,但仍有 pending fill 后的开盘检查、deferred protective stop submit 和必要 repair workflow。

第二次是重启验证。自动 runner 的 tmux 会话原本有稳定的双 pane 形态:上方滚动 runner 输出,底部固定状态监控。重启工具按 session 级目标控制,却没有锁定 runner pane,结果新 runner 启到了底部 pane,把持续监控面板挤掉了。runner 活着、pid 正确、commit 正确,都不能掩盖这个事实:操作者观察现实的面板被破坏了。

这也不是一个纯运维小插曲。无人值守系统的可观察性不是装饰,尤其当系统正在做开盘等待、券商 truth 重读、保护止损 repair、retry 和 manual-required 升级时,稳定的观察面板本身就是安全边界的一部分。验证不能只证明程序还在跑,也要证明操作者仍能按原来的视角看见它。

我以前总觉得 fail-closed 是一句很硬的安全口号。到了这一天,我才开始把它拆成更具体的判断:

新开风险时,fail-closed 是底线。
风险收缩时,随意 fail-closed 可能是负安全债。
历史 payload 进入当前链路时,fail-closed 不是答案,迁移和去权威化才是答案。
显示层缺事实时,fail-closed 不是让它猜,也不是让它沉默,而是让 owner contract 暴露 gap。
pending fill 还没变成持仓时,“当前没有止损任务”不能抹掉后续保护义务。
runtime 重启后,“runner 活着”不能替代操作者面板仍然可用。

这不如“安全第一”四个字好听,但它更接近真实系统。

侧记:2026-07-02 那些真正改变 fail-closed 判断的会话

  • P1 只读审计确认,自动运行链路存在从诊断 blocker、修复失败或 cycle facts 缺失一路走向无人监管停止的可达路径。这个结论把“没有继续 mutation”从安全证明降级成只是一条必要但不充分的证据。
  • 测试与契约审计发现,某些测试 fixture 已经跟不上当前 owner contract:有的还依赖 raw continueAutomation,有的暴露 submit monitoring owner API 漂移。这说明问题不是少写一个断言,而是测试本身也可能在保留旧世界。
  • P2 双审计把 stop repair 的 blocker allowlist 分歧摆出来:无关 co-blocker 不能整体压死缺失止损或错配止损修复,但放行也必须有 symbol/action/owner 边界,不能靠一个大而宽的 compatible blocker 列表解决。
  • 晚间收敛任务把 target mismatch、expected action display、submitted evidence、compact diagnostics 从多个展示/通知/状态层的重复推断中收回来。用户对“历史 payload 就 fail-closed”的纠偏,是这一天最重要的叙事转折。
  • P3 并发审计确认,preflight reconcile 和 submit / cancel / flatten 会竞争同一账户状态;正确修复方向不是把网络读取也锁住,而是把真正的 account state load-mutate-save 段放进可等待的账户锁。
  • 用户对“无待补止损”输出的反驳,把 pending primary fill 后的保护义务重新放回生命周期 owner:没有当前裸露止损缺口,不等于开盘后不需要成交检查、延期保护止损提交或 repair。
  • tmux 双 pane 被破坏后,重启验收标准也被抬高:不能只看 runner pid、commit 和 dirty 状态,还要保留操作者依赖的固定监控面板。

证据(2026-07-02)

  • 已读取 2026-07-02 全部 9 个本地会话原文,覆盖前一轮第 39 章发布、auth-checker 工具开发、P1 fail-close 审计、P2 stop repair blocker 双审计、重复推断层收敛任务、用户对保护止损生命周期输出的纠偏、重启破坏监控面板的现场恢复,以及 P3 账户状态并发安全复核。
  • 当天主仓库提交集中在 IBKR session owner、broker mutation in-process、opening recovery、stop repair retry、runtime owner storage、preflight state serialization、operator output 和 WebUI 可见性等方向;这支持本章“安全门开始区分冒险与补洞”的判断。
  • 本章没有修改交易仓库源码、策略配置、自动交易配置或任何券商 mutation;只更新回忆录文本,并把内部会话证据改写成公开可分享的叙事。
  • auth-checker 工具开发有实用价值,但与量化系统主线叙事关系较弱,所以不进入正文主线,只作为当天完整会话覆盖范围的一部分记录。
  • 当前工作树仍有未提交 WebUI 和自动恢复策略相关改动;本章不把这些 dirty 项当成验收结果,也没有修改交易代码或运行配置。

第四十一章:产品不是把内部细节折叠起来

到 7 月 5 日,Operator WebUI 已经不再是一个空壳。

它能看到真实 owner facts,也能显示券商流、任务、数据、配置、回测和自动链路。按早一点的标准,这已经很像进展了:页面多了,卡片多了,表格多了,状态也多了。最危险的是,它看起来真的更像产品了。

但这一天真正的转折,恰好来自一句很刺耳、也很准确的反驳:为什么还要折叠详情显示这些东西?WebUI 应该是一个完整的产品。

这句话把我从一个很常见的工程师幻觉里拽了出来。之前我潜意识里还在想,内部字段、owner 名、source、schema、reason code、API 口径这些东西,如果不适合放在主界面,至少可以折叠起来。好像只要默认不展开,它们就不会打扰操作者。

后来才意识到,这其实还是把产品当成调试面板:只是把调试面板收进抽屉,而不是把它改造成交易员真正能读懂的界面。

一个完整产品不是没有证据。恰恰相反,它更需要证据。但证据不能以“把内部合同原样贴出来”的方式出现。操作者要知道现金、权益、P&L、券商事实、自动链路、数据管线和任务状态是否可信;不需要在主界面被迫阅读内部字段名,猜 source 到底是不是权威,或者在一堆英文机器状态里判断系统今天是不是能托付。

那天的多轮只读审查把这个问题拆得很清楚。

有些地方是真的缺 runtime 或 API owner 合同,页面不能硬凑。比如部分 row-level broker truth、submit readiness、repair recovery、open fill、窗口修复和 LongBridge live facts,如果 owner 没有稳定发布,WebUI 就只能显示 gap,不能从旧路由、事件流或 blocking reason 里把事实拼出来。

但更多地方不是缺合同,而是已有合同被 WebUI 用错了表达方式。Command Center、Broker Cockpit、Operations、Tasks、Data、Config、Backtest 都已经能接到一部分 read model;问题是主界面仍然像诊断台:同一条自动链路重复出现,现金和权益三个数字互相矛盾却不解释来源,顶栏说“未读取”而正文已经有任务数据,owner-gapschemasourceroutestatus code 和一堆内部名字直接暴露在产品界面上。

这里的教训很微妙:以前我防的是 UI 偷偷推断交易事实;这一天我又补上了另一半,UI 也不能把“不推断”偷换成“把内部世界原样倒给人看”。

前者会让页面变成第二套交易系统。
后者会让页面变成一份穿了西装的日志。

于是验收标准被重新写了一遍:主 WebUI 是产品,不是开发者诊断面板。它可以保留证据入口,可以标出 owner gap,可以解释数值来源,可以在矛盾时亮 warning;但它不能让主界面充满 API 路径、schema key、owner 内部名、英文机器状态和 reason code。更不能在真实数据不一致时,为了看起来整齐,把其中一个数字偷偷覆盖掉。

这也是我第一次更清楚地区分“可信”和“专业”之间的关系。

专业不是把页面做得漂亮。
专业是它知道哪些数字应该大,哪些数字应该小;知道盈亏颜色应该来自数值正负,而不是来自“数据读取成功”;知道 0 个失败任务不应该红着脸吓人;知道金额不能在小数点前后断成两行;知道“对账完成”和“监督器需要复检”不是同一个中文词;知道如果现金、权益、持仓市值对不上,应该把矛盾标出来,而不是替现实化妆。

更重要的是,专业界面必须仍然守住 owner 边界。那一轮修复只动只读 WebUI 和展示合同消费,不改 gate、提交、撤单、对账、止损或恢复判定。它让页面更像产品,但没有让页面变成交易链路的老板。晚间的七页桌面和移动验收也按这个口径验:没有横向溢出,没有金额中间折行,没有重复自动链路,没有 0 任务红底,没有顶栏与正文互相打架;当核心 read model 不可用时,页面显示 owner gap,而不是自己合成一个看起来完整的世界。

这章让我记住一件挺反直觉的事:越接近产品,越不能放松工程边界。

原型可以把内部细节露出来,因为它本来就是给开发者看的。真正的操作台不行。它面向的是一个可能要在开盘前几分钟决定是否相信系统的人。这个人不应该在 API 名、source 字段和重复卡片之间猜谜;也不应该被一个过度自信的漂亮界面骗过去。

所以 WebUI 的目标不再是“把所有事实都展示出来”。更准确的目标是:把 owner 发布的事实翻译成人能行动的语言,把缺口诚实地留成缺口,把矛盾标成矛盾,把调试语汇赶出主界面。

以前我总说,显示层不能当老板。

到这一天,我才补上后半句:显示层也不能当翻译腔很重的实习生。

侧记:2026-07-05 那些真正改变产品判断的会话

  • 早间两次回忆录自动化仍保持 no-op,理由是前一天缺少对应的量化开发原始会话,不能只靠提交和文档写新章。这再次确认了回忆录不是按提交数量自动增长。
  • 下午的多轮只读审查把 Operator WebUI 分成两类问题:真正缺 owner 合同的事实,和已有合同但 WebUI 没有产品化消费的事实。这个区分阻止了“前端补字段”和“runtime 补合同”互相甩锅。
  • 用户明确反驳“折叠详情”方案,要求主 WebUI 成为完整产品,而不是把内部字段藏在折叠区里的诊断面板。这是本章的叙事转折点。
  • 后续修复把重复自动链路、现金/权益/P&L 矛盾、顶栏未读取冲突、对账文案歧义、大数字层级、盈亏颜色、任务空状态、金额折行和券商布局空洞逐项收口,同时保持 WebUI 只读、只消费 owner read model。
  • 晚间目标报告把验收拆成三层:数据可信在 owner gap 状态下部分证明,七页视觉一致性通过,自动交易合同闭环在 WebUI 范围内通过;但真实 owner 数值的最终证明仍受核心 read model 暂时不可用影响,不能被夸大成全量完成。

证据(2026-07-05 ~ 2026-07-06)

  • 已读取 2026-07-05 全部 24 个本地会话原文,覆盖一次本地磁盘清理支线、两次前日回忆录 no-op、一次与量化无关的二进制工具支线、Operator WebUI owner 合同只读审计、产品化审查、多 worker 页面修复、用户对“折叠详情”的纠偏、逐页 UI/UX 巡检、以及晚间七页桌面/移动验收。
  • 当天主仓库提交从 7 月 5 日下午的 owner-backed surfaces、live broker stream、developer diagnostics removal、command center product layout,一直推进到 7 月 6 日凌晨的 display contradiction fix、broker source evidence、layout overflow、goal validation。提交数量本身不是叙事重点;重点是产品验收开始同时要求 owner 边界和交易员阅读路径。
  • 终态报告记录:WebUI 没有改变提交、撤单、对账、保护止损、恢复或 runtime gate 语义;当前核心 read model 曾返回不可用状态,页面正确显示 owner gap,没有合成缺失事实。
  • 本章没有修改交易策略、自动交易配置或券商行为;只更新回忆录文本,并把内部会话证据改写为公开可分享的产品与架构叙事。

第四十二章:产品语言不能替事实闭环背书

第四十一章写完以后,我一度以为 Operator WebUI 的问题已经进入比较舒服的阶段:少一点内部字段,多一点交易员能读懂的语言;少一点重复卡片,多一点清楚的层级;少一点调试面板,多一点产品。

7 月 6 日的会话把这个想法又往前推了一步,也往回拽了一下。

往前推,是因为页面确实在变好。多轮审计确认,重复链路被删掉了,现金和权益的来源开始标出来,金额不再在小数点附近断成两截,任务空状态不再吓人,配置和回测页面也开始从英文碎片和原始 key 里退出来。一个操作台终于不只是“有数据”,而是开始有阅读路径。

往回拽,是因为真正的缺口也因此更显眼了。

当页面还很丑、很像调试台时,很多问题会被归类成“前端还没 polish”。一旦页面变得专业,剩下那些错就不能再躲在“体验不完善”后面了。那天的只读审计反复指出同一类问题:有些字段不是没翻译好,而是 owner 没有发布;有些显示不是排版问题,而是把 display-only stream 说得太像 broker truth;有些 fallback 不是友好降级,而是在缺 owner 状态时把“未读取”或“缺口”包装成一个看似可行动的结论。

最典型的是 Data 页。专门的数据接口显示数据管线是健康的,可运行时 read interface 里给 WebUI 的 compact 数据却是空的。页面如果只按自己的 fallback 逻辑显示“数据未读取”和一堆 0,就会让操作者误以为系统事实不存在;如果反过来偷偷去另一个接口补齐,又会让页面绕过当前 owner contract。正确答案并不舒服:要么 owner contract 补上这份事实,要么页面诚实标出“当前只读操作台没有拿到 owner 发布的数据”,不能用局部聪明替系统背书。

Broker 页也有类似问题。一个 display-only broker stream 可以帮助人看见实时诊断,但它不能因为看起来很实时,就被主界面称作“券商事实”。“止损覆盖”这几个字尤其危险:如果它来自展示诊断,而不是 broker truth / protective stop owner 的完整合同,它就只能是提示,不能变成安全结论。产品语言越简洁,越要小心它把非权威证据压缩成一句像判决的话。

晚上关于 IBKR 401 的两轮审计,则把这个问题从页面拉回了无人值守主链。

authentication_unavailable 并不是一个陌生错误。系统已经有一条路径:当它以 broker retry/result contract 的形式进入主循环时,会走恢复或 manual-required 逻辑。但审计发现,普通的 interval wait / session keepalive 认证失败只是被记录到 heartbeat 或操作台显示里,并不会自动触发 reauth,也不会直接进入 MANUAL_REQUIRED。换句话说,某些认证失败已经被看见了,却还没有被主恢复 owner 接住。

这和 WebUI 的问题其实是同一件事的两个面。

在页面上,事实被看见,不等于事实被正确授权。
在自动循环里,错误被记录,不等于错误被正确恢复。

前一天我学到:主界面不能把内部细节折叠起来就算产品。7 月 6 日我又补了一句:主界面也不能因为自己说得更像人话,就替事实闭环背书。越是产品化,越要把三件事分清楚:这是 owner 发布的事实,这是 display-only 诊断,这是缺口或待恢复义务。

否则,一个漂亮操作台会比粗糙日志更危险。粗糙日志至少让人知道自己还在看证据;漂亮界面一旦用错词,就会让人以为证据已经变成结论。

侧记:2026-07-06 那些真正改变判断的会话

  • 多轮 Command Center / Broker Cockpit 审计确认,主要跨页现金来源矛盾已经收敛到 active path,但仍留下 plan/action reconstruction、P&L fallback、display-only stream 命名过强等 owner-contract 风险。
  • Visual audit 的结论从“页面还丑”转向“有些页面已通过,但少数问题具有产品语义风险”:大数字、正负色、移动端压缩、空列和 raw label 不再只是审美问题,而是操作者能否快速判断事实的问题。
  • Data / Config / Backtest 审计反复暴露同一个边界:已有健康事实不等于当前 read interface 已发布事实;前端不能用 fallback 把缺 owner 数据变成成功状态,也不能绕开 owner 去拼一个看似完整的页面。
  • Broker 页和 Command Center 审计把 display-only broker stream、实时诊断、broker truth 和 protective-stop owner 分开。尤其“止损覆盖”这类词,必须由真正的保护止损合同支撑,不能由展示流直接背书。
  • IBKR 401 / session_keepalive 审计发现,认证失败在某些路径里只是进入 telemetry 或 operator display,并没有自动接到 reauth / MANUAL_REQUIRED。后续又确认,如果 recovery path 已经产出 manual-required result,runner 会保持 alive,但顶层 heartbeat reason 可能被归一成 blocked,详细原因需要在 blocking/manualRequired/workflow/operator 里保留。

证据(2026-07-06 ~ 2026-07-07)

  • 已读取 2026-07-06 全部 28 个本地会话原文,覆盖前日第 41 章发布复核、Operator WebUI data contract / visual / output correctness 多轮只读审计、Data / Config / Backtest / Command / Broker / Operations / Tasks 页面输出审查,以及 IBKR 401 / authentication_unavailable / manual-required 路径审计。
  • 当天主仓库提交集中在 Operator WebUI 展示标签、broker owner gap label、position fields、operations status、broker fill count、stable wait 输出和 status bar 收敛。提交推进了产品表达,但会话证据显示,产品表达仍必须服从 owner contract 和恢复链路闭环。
  • 关键安全发现不是“页面还有 bug”,而是“可见诊断不能自动升级为权威事实,可见错误不能自动等于恢复路径已闭合”。这使本章从第四十一章的产品语言问题,推进到产品语言与事实闭环之间的边界问题。
  • 本章没有修改交易策略、自动交易配置、券商配置或运行进程;只更新回忆录文本,并把内部路径、会话文件和报告路径改写为公开可分享的叙事证据。

第四十三章:逐页验收,比漂亮界面更不讲情面

第四十二章写完时,我已经接受了一个现实:产品语言不能替事实闭环背书。

但 7 月 7 日的会话又把这个原则推进了一层。原则本身听起来很正确,真正难的是把它变成验收动作,而且不是验收一个页面、一个字段、一个截图,而是把整个 Operator WebUI 一页一页摊开,问同一个问题:这句话、这个数字、这个“已读取”、这个“0”、这个“可用”,到底是谁发布的?

这一天最有代表性的瞬间,不是某个修复提交,而是用户的一句追问:这些是合同未发布吗?止损单看不到止损价格?

这个问题很尖锐。因为它逼着系统不能再用“可能是前端还没接好”来糊弄过去。答案后来很明确:不是合同未发布,owner 已经有止损价;问题出在 WebUI 的 normalizer 或展示层没有把这个字段保留下来。换句话说,事实已经存在,只是操作员看不见。

这比“没有 owner 合同”更刺眼。

没有合同时,页面诚实地说“未发布”,至少还算守边界。合同已经发布而页面仍然说“未发布”、显示 0、显示可用、或者干脆漏掉字段,风险就变了:系统不是不知道真相,而是在最后一公里把真相弄丢了。对交易系统来说,这种丢失很危险,因为操作者不会去读所有底层证据;他会相信页面。

于是当天的工作从“修几个可见问题”变成了“逐页验收”。Command、Backtest、Brokers、Operations、Data、Tasks、Config 都被拆开检查:哪些是 owner 已经发布但 UI 没展示,哪些是 UI 把空值推成 0,哪些是把缓存事实说成当前事实,哪些是把内部 reason code 直接丢给人看,哪些是失败任务用 100% 进度制造了假完成感。

这里的关键变化是,验收标准开始比页面本身更像产品。

以前我会说:“页面是不是好看?是不是中文?是不是没有白屏?是不是移动端不崩?”这些当然重要,但不够。7 月 7 日之后,真正的验收问题变成:

  • owner 已发布的事实,页面是否完整、准确、同层级地呈现?
  • owner 没发布的事实,页面是否诚实留白,而不是显示 0 或“已读取”?
  • display-only、缓存、诊断、实时流、broker truth 和保护止损合同,是否被明确区分?
  • 用户看到的每个行动判断,是否能追溯到正确 owner,而不是前端自己重建出来的推断?

这一天也暴露出一个更隐蔽的矛盾:WebUI 越像产品,越容易让人忘记它只应该是 consumer。

一个粗糙调试页显示一堆内部字段,人会天然保持怀疑;一个成熟控制台用清楚的中文、漂亮的卡片和状态灯告诉你“已读取”“清晰”“可用”,人会本能地相信它。正因为如此,漂亮界面反而需要更不讲情面的逐字段验收。它不是为了挑剔 UI,而是为了防止 UI 在无意中变成第二个交易事实老板。

所以这一天的教训不是“WebUI 还有很多 bug”。

更准确地说,是系统终于开始把“操作员看见什么”也当成交易安全的一部分。止损价不是只要在 owner 里存在就够了;如果操作者所在的页面看不见,它在操作层面仍然像不存在。资金、持仓、任务进度、数据覆盖、回测默认策略、提交就绪状态也是一样。事实链不能停在后端合同,必须穿过 read model、normalizer、组件、文案和布局,最后仍然保持同一个含义。

我以前总觉得,交易系统最难的是让它做对动作。后来发现,让它在每一层都诚实地说明“为什么这样做、还缺什么、不能做什么”,并不比做动作简单。

7 月 7 日的逐页验收,就是把这件事从一句架构原则,变成了可以执行、可以反复追问、可以让页面不舒服的标准。

侧记:2026-07-07 那些真正改变判断的会话

  • 当天先确认了前一日第四十二章已经发布,随后新的会话没有重复写“产品语言不能替事实闭环背书”,而是把问题推进到逐页验收:合同已发布但 UI 丢字段,和合同未发布,是两种完全不同的风险。
  • 用户围绕“止损单看不到止损价格”追问,最终确认 owner 已经发布止损价,WebUI 展示链路没有正确呈现。这成为当天最清楚的叙事转折:事实存在,不等于操作者已经看见可信事实。
  • 多轮只读审计覆盖 Command、Backtest、Brokers、Operations、Data、Tasks、Config 七类页面,反复出现同一类问题:假 0、假已读取、缓存账户事实错配、失败任务 100% 进度、内部 reason code 暴露、display-only 和 broker truth 混淆。
  • 用户多次要求“收敛”“只列已经确认项”,这把审计纪律从“尽量多找问题”拉回“只报告证据已经闭合的问题”。这和交易系统本身的 owner-contract 纪律是一致的:未确认就不能变成结论。
  • 后续提交集中修复 Operator WebUI 的显示合同、owner-backed read model 消费、券商事实展示、任务/运行状态表达和自动阶段通知,但这些提交本身不是故事重点;重点是验收标准开始覆盖操作员最终看到的每个事实。

证据(2026-07-07 ~ 2026-07-08)

  • 已读取 2026-07-07 全部 67 个本地会话原文,覆盖前日回忆录发布、Operator WebUI 多轮只读审计、被中断后的收敛报告、七页逐输出验收、认证门阻塞下的降级审计、以及后续围绕 owner/API 已发布但 UI 未展示的问题复核。
  • 当天主仓库提交集中在 Operator WebUI owner-backed display、broker truth 展示、Command Center/Operations/Tasks 状态、运行面板健康、打开恢复输出、券商 retry history、auto stage notification 等方向。提交数量不是本章重点;重点是这些提交服务于同一个验收标准:页面不能自行发明、丢失或美化交易事实。
  • 本章没有修改交易策略、自动交易配置、券商配置或运行进程;只更新回忆录文本,并把内部会话、临时截图和报告路径改写为公开可分享的叙事证据。

第四十四章:补救订单不是字符串能证明的事实

第四十三章把焦点放在“操作者看见什么”。我当时以为,接下来主要会继续清理页面和 read model:让 owner 已发布的事实不要在最后一公里丢掉,让未发布的事实不要被页面显示成 0。

但 7 月 10 日真正推开的门,不在页面,而在一笔开盘补救单。

PENG 的原始开盘单没有成交,系统随后提交了一笔 DAY 补救买单,并且这笔补救单真实成交,保护性止损也已经挂上。按人的直觉看,这件事应该结束了:没买到的开盘单,被补救买到了;买到以后,止损也有了。

可系统没有这么理解。它还在把这件事往旧的 OPG 数量、旧的本地 ID 字符串、旧的 plan projection 里拉。最刺眼的问题是:补救单已经用新的 canonical idempotency key 提交并成交,后续 reconcile 仍可能靠旧字符串去识别它是不是“开盘补救”。一旦 submitted evidence 没有被正确投影,系统就可能把已经完成的补救单误看成残余缺口,继续计算一个不该存在的 BUY。

这不是一个小的字符串 bug。

它证明了一件更深的事:在无人值守交易里,“我知道这张单是补救单”不能靠名字、描述、旧 token、日志习惯或人的记忆。这个身份必须从 plan annotation、lifecycle persistence、broker request、broker response、broker snapshot correlation、post-submit reconcile 一路穿过去,而且中间任何一步丢了结构化语义,都不能假装成功。

当天几轮审计把这个问题拆得很细。正常 v2 路径最后不再依赖旧的 OPENING_AUCTION_REPAIR 字符串,生命周期里有补救订单的 contract,broker correlation 也能从结构化证据里认出它。但审计同时指出,如果 broker ACK 后 submitted-plan projection 持久化失败,后续 reconcile 仍可能按原 OPG 逻辑算出残余买入。这就是这一天最重要的教训:修掉一个旧识别条件,不等于整条证据链已经闭合。

同一天,另一个看似无关的问题也把这个教训重复了一遍。broker truth contract 里,持仓存在,活动保护止损也在 action rows 里,可 protectiveStops.count 却是 0。审计结论很清楚:这不是执行 gate 直接读错了止损,而是 broker truth projection 自己内部矛盾。它当时主要影响可观察性和 parity,不直接改变 submit 或 repair gate;但这并不轻。因为一旦 owner 发布了一个自相矛盾的事实,UI 不该补,监控不该补,操作者更不该猜。修法必须在 broker truth owner 里发布活动保护止损,而不是让页面从 action rows 里重新解释。

还有第三个转折,是 stop lifecycle 的日期和 basis。PENG 的 buy date、execution date、最新完成行、entry stop basis 和 broker avg entry 同时出现以后,系统一度想要一个旧的止损价。后来复核把边界收窄:历史持仓决策可以继续用历史 stop decision,当前 HOLD maintenance 和 ADD 需要消费 next-execution stop levels,保护止损 expectations 也要消费同一个 stop-level owner。否则系统看似在维护止损,实际上是在用两个日期的世界观计算同一笔风险。

这些问题看起来分散:一个是开盘补救身份,一个是保护止损计数,一个是止损 basis,一个是 IBKR quote hot path,一个是 read-interface stale refresh,一个是 run log retention。

但它们在同一天连成了一条线:系统正在从“能做补救动作”,走向“能证明补救动作被同一个事实链理解”。

IBKR 那条线也很典型。热 quote cache 后来证明不能替代 preflight 里的权威 broker-truth reads;全链路延迟仍在十几秒级,因为真正慢的是一组串行的 live-order probes,而不是 quote 读被 REST lock 卡住。于是结论不是“再加缓存”,而是保持一个 in-process IBKR session owner,区分 quote freshness、order truth、accounts readiness 和 WebSocket owner context。缓存可以加速观察,但不能冒充交易事实。

最后,运行证据保留也被拉进了同一个标准。一个 active run 如果因为 retention 被删掉早期 segments,或者未完成 run 被当成普通旧文件删掉,那不是磁盘清理成功,而是审计链断了。无人值守系统不能只在下单时讲 owner;它还要在事后能回答:当时是谁判断的,为什么判断,哪里失败,哪个证据还留着。

所以 7 月 10 日给我的不是一个“开盘补救修好了”的轻松结论。

它更像一次更严厉的提醒:交易系统里每个“补救”动作,都会产生第二层责任。第一层责任是别让风险裸着;第二层责任是别让系统在补救之后忘了它补过什么。补救订单、保护止损、券商快照、运行日志、页面显示,最后都在问同一个问题:

这件事到底由谁证明?

如果答案是一段字符串、一个旧字段、一个页面推断、一个缓存命中,或者一份已经被清理掉的日志,那就还不够。

侧记:2026-07-10 那些真正改变判断的会话

  • 当天最核心的转折来自 PENG missed-open-entry repair:原始 OPG 没成交,DAY 补救单真实成交并挂上保护止损,但 post-submit reconcile 仍暴露出可能沿旧字符串和旧 projection 误算残余 BUY 的风险。这把问题从“识别字段不对”推进到“补救身份必须贯穿完整生命周期证据链”。
  • broker truth 的保护止损投影暴露出另一种 owner 内部矛盾:活动止损已经存在,summary count 却为 0。审计判断它主要影响 observability/parity,而不是直接执行 gate,但修法仍必须在 broker truth owner 中完成,不能让 UI 或监控补推断。
  • stop lifecycle 审计把“当前该维护哪个止损价”从单个价格问题,推进到 effective date、historical decision 和 next-execution level owner 的分工问题。历史判断和当前维护可以同时存在,但不能互相冒充。
  • IBKR pre-open 审计证明,quote hot cache 不能替代权威 broker-truth reads;加速只能发生在正确 owner 边界内,不能用缓存把 order truth、account readiness 或 recovery path 变成 display-only 事实。
  • runtime read-interface 与 run log retention 的复核则提醒我:审计证据本身也是交易安全的一部分。active run 的早期 segments 和未完成 run 不能被普通磁盘清理逻辑抹掉,否则事后复盘会失去链路。

证据(2026-07-10)

  • 已读取 2026-07-10 全部 33 个本地会话原文,覆盖前一日回忆录自动化、PENG 开盘补救单身份链路、broker truth protective stop 投影、stop lifecycle basis、IBKR session / quote / order context、多轮 execution-safety 复核、runtime read-interface stale refresh、state retention、run log rotation 和一个无关个人项目会话。
  • 当天主仓库提交集中在 missed-open repair lifecycle、submitted repair recovery、active protective stop publication、IBKR owner-session quote/order context、runtime read facts、owner snapshot current/history 分离、broker money facts 和 account roster acceptance。提交很多,但叙事重点不是提交数,而是“补救动作必须被同一条事实链证明”。
  • 本章没有修改交易策略、自动交易配置、券商配置或运行进程;只更新回忆录文本,并把内部会话、运行日志和报告路径改写为公开可分享的叙事证据。

附录 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 的信号核心可以冻结,新的系统默认改进主要来自组合行为语义,而不是再换一套选股故事
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 从默认叙事里退场;组合行为合同消费也被统一
2026-06-21 entry reservation ledger 从 backtest / paper 两套实现中抽成共享 owner,重复 reservation 账本被测试禁止重新出现
2026-06-21 D90 market filter 的研究 wrapper 与正式引擎出现动作范围差异,问题被定性为 action-scoped 语义不一致
2026-06-21 d90g 被定义为 BUY + ADD 都受市场过滤约束的候选策略,必须经过晋升门后才可能成为默认
2026-06-22 d90g 被切为默认策略;随后配置、runtime、paper planner、replay 和旧策略入口继续向标准合同与 planner owner 收束
2026-06-23 默认策略后的清账继续推进:策略合同审计阻止提前收口,自动交易通知开始输出完整计划与原因,保护止损审计暴露出 LIT/gap-through 与部分成交覆盖的动态风险
2026-06-24 最新只读报告开始显式带出 gap-through、partial fill、protective stop quantity mismatch 等对账计数,但运行闭环仍不能用部分路径通过来替代
2026-06-24 自动交易卡住排查把“终端沉默”暴露为安全问题;交易员第一屏必须按安全、阶段、原因、下一步和证据排序
2026-06-25 25% 仓位与开盘限价/跳高保护的语义被追到执行股数层面:保护参数不会放大股数,但可能在现金约束下造成仓位不足
2026-06-25 自动交易 E2E 验收被重新定义为证据网:broker truth、reconcile、repair、operator projection 和 notification 都必须保住 owner 粒度
2026-06-26 Operator WebUI 从调试卡片推进到交易员操作台,但同时被要求严格消费 owner facts,不成为第二套交易系统
2026-06-26 运行时代码 provenance、数据门禁、券商工具链网络和 WebUI owner gap 被分开报告;“可操作”开始包括人介入时能否看见正确事实
2026-06-26 Operator WebUI 被推进成七页集成控制台,但 P0 验收卡在 IBKR owner facts、same-origin real Core、runtime provenance 和通知投递闭环
2026-06-26 自动运行排查证明旧 runner / 旧 heartbeat / 旧 commit 不能替当前代码作证,data gate 和 Bark 投递也必须分别验收
2026-06-27 重复推断被正式命名为 auto-trading failure pattern:同一个交易事实不能被 plan、repair、parity、operator 和 notification 多处重新解释
2026-06-27 target parity、repair action、submit selection scope、operator projection 和 raw target-plan fields 被继续压回唯一 owner contract
2026-06-27 Serena / CodeGraph 被定位为只读代码侦察工具:能减少找调用链的成本,但不能替代源码、测试和 owner-boundary 证据
2026-06-28 Operator WebUI 从“不能重复解释事实”推进到“第一屏必须正确呈现 owner facts”:真实数据存在,不等于操作者已经看见可信现实
2026-06-28 Google Finance / TradingView 被重新放回非权威行情参考位置;无人值守系统的事实行情仍必须来自有契约的券商、交易所或授权数据链
2026-06-29 d90g 研究线从旧 wrapper 线索推进到 formal-engine 证伪流水线:多数 clue 被关闭或降级,少数候选只获得继续审问资格
2026-06-29 broker mutation 第一阶段被审计为可以继续,但前提是 executor 成为唯一 provider mutation 入口,不能长成新的 wrapper 或第二事实源
2026-06-30 开盘恢复和 Operator WebUI 同时暴露出同一个边界:等待循环、stream bridge 和页面工作台都只能消费 owner contract,不能成为新的事实老板
2026-07-01 d90g 止损语义被继续压实:系统必须同时表达 stop order type、trail percent、stop source 和 lifecycle owner,不能只拿一个价格宣称保护成立
2026-07-02 fail-closed 被重新分类:开新风险要停,风险收缩不能随意停,历史 payload 必须迁移或去权威化;pending fill 后的保护义务和 tmux 监控面板也被纳入验收
2026-07-05 Operator WebUI 的目标从“安全地显示 owner facts”推进到“把 owner facts 翻译成完整产品语言”:主界面不能暴露内部合同语汇,也不能牺牲 owner 边界
2026-07-06 产品化后的 WebUI 审计暴露出新边界:display-only stream、fallback、owner gap 和 session_keepalive 认证失败都不能因为被看见或被说得更顺,就自动变成事实闭环
2026-07-07 Operator WebUI 进入逐页逐字段验收:owner 已发布但 UI 丢字段、假 0、缓存错配和假完成感,都被视为交易事实链断点
2026-07-10 PENG 开盘补救单把问题推进到完整生命周期证据链:补救订单身份不能靠旧字符串、旧 projection 或页面推断证明
2026-07-10 broker truth protective stop 投影、stop lifecycle basis、IBKR owner context 和 run log retention 被放进同一个标准:补救动作之后,系统还必须能证明自己补过什么

附录 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 补救路径同样在改写交易现实
组合行为推广 在信号核心不变的前提下,把组合行为作为正式执行语义与系统默认来验证和推广 逼自己承认收益改善不一定来自“更聪明的信号”
市场过滤语义 把 market filter 从布尔开关拆成 action-scoped contract,明确 entry-only 与 BUY + ADD 是不同策略 研究 wrapper 和正式引擎同名时仍可能做出不同动作,名字统一不能替代动作级一致
默认策略晋升 d90g 只能作为候选经过 promotion gate,不能因单次回测结果直接成为默认 高收益和高 profit factor 仍要接受 ordinary execution pressure、回撤、纸面 smoke 和修复链路验证
并行开发 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 语义和正式引擎执行语义不能长期分居 否则回测里赢的东西,未必是系统真正会执行的东西
研究自动化晋升门 research runner 只能提出可证伪的 formal contract 假设,不能把忙碌网格当成策略进步 否则研究自动化会把“跑得很多”伪装成“越来越可信”
共享 reservation 账本 pending entry / prefilter / ADD reservation 必须由共享 strategy owner 表达,backtest 和 paper 只能消费同一账本 两条执行链各自算现金预留,会把同一个策略合同重新分裂成两种现实
默认策略晋升后的清账 默认切换后必须删除、迁移或去权威化旧 loader、旧 facade、旧 policy layer 和旧 projection path 默认策略不是一个配置指向,而是其它解释者失去解释权
保护止损覆盖 “有止损订单”不等于“剩余风险被覆盖”,必须按订单类型、触发语义、成交状态、剩余数量和当前持仓共同判断 否则 partial fill、gap-through 或 limit-trigger 订单会把安全门骗成 PASS
交易员终端第一屏 先显示安全、阶段、阻断原因、下一步、人工动作和自动继续资格,再展示计划与证据 终端沉默或乱序会逼操作者猜测,从而把显示层变成事实解释者
Operator WebUI 验收 页面可见、mock 通过、fixture 有数据都不能替代 same-origin real Core 和 owner-published facts 控制台越像产品,越不能让浏览器、BFF 或 UI 获得交易事实解释权
运行时证据 auto runner 必须用当前 runId、活 pid、匹配 commit 和干净启动状态来证明 旧 heartbeat 或旧 tmux 画面不能证明当前代码正在安全运行
通知与数据门 readiness、send requested、download attempted 都不是最终成功状态 操作者需要知道事实是否真正送达、数据是否真正新鲜,而不是只看到流程开始过
阶段收口 策略合同迁移不能因为多数路径和多数测试通过就宣布完成,剩余旧规则/投影入口必须继续阻塞收口 诚实的未完成比包装成 PASS 更接近可托付系统
旧入口退役 旧 TUI、旧 wrapper、旧控制别名必须端到端退役,不能只移除一个入口 半退役路径会继续在文档、测试、UI 或人的记忆里制造第二套现实
证据工作台 WebUI 不只展示结果,还要展示 job evidence、owner coverage、read-model coverage 和 before/after artifacts 否则前端会把操作者重新推回猜谜,而不是审阅证据
验收证据网 不用单个绿色测试、单个成功运行或单个界面截图代替整条交易链证据 无人值守系统必须同时证明“为什么行动”和“为什么不行动”
操作台边界 Operator WebUI 可以组织事实、发起有界非券商工作流和显示缺口,但不能推断 broker truth、submit readiness、自动状态或 runtime authority 操作台越好用,越容易影响人的交易判断;因此必须比普通 dashboard 更守边界
操作台第一屏 第一屏必须把活性、阶段、等待原因、下一步、owner 覆盖和降级缺口讲清楚 事实存在但不可见,会把操作者重新推回日志和猜测
外部框架引入 成熟框架只能作为底层传输或对照候选,不能绕过 broker truth / submit / repair / display owner 连接成熟不等于交易系统边界成熟
研究证据门槛 d90g 候选必须经过 formal engine、修正窗口、资金模式和扰动实验共同审问,负结果要明确关闭 否则研究会退回旧 wrapper 和参数神话
Broker mutation 收口 provider mutation 应集中到唯一 executor,submit engine 保持状态推进和编排,不把旧 callable 当 fallback mutation 入口分散会让“谁真正改券商状态”重新变得模糊
工作台/恢复边界 开盘恢复 supervisor、IBKR stream owner、Operator WebUI 页面都必须保持 consumer/owner 分工,不能让等待循环、桥接层或页面组件重新解释交易事实 功能越接近真实操作,越容易影响操作者判断;因此越要防止展示层和连接层偷偷获得权威
自动链路清账 删除 legacy / fallback 时必须保留生命周期义务和操作者观察约束 干净的 contract 如果把 pending primary fill、延期保护止损、开盘后 repair 或 tmux 状态面板抹掉,就只是把旧耦合换成新盲点
产品化边界 Operator WebUI 主界面必须用交易员语言组织 owner facts,内部字段、source、schema、reason code 只能作为证据或诊断,不应成为主产品语言 把内部细节折叠起来仍然是调试面板思维;完整产品要同时保持可信证据和可读表达
产品语言与事实闭环 display-only stream、fallback、owner gap、telemetry 和 keepalive 错误必须被明确标成各自语义,不能由产品文案或视觉 polish 自动升级为权威事实或恢复完成 越像产品的界面越容易影响操作者判断,因此每一句简洁文案都必须有 owner contract 或恢复链路背书
逐页输出验收 owner 已发布的事实必须穿过 read model、normalizer、组件和文案仍保持同义;未发布事实不能被显示成 0、可用或已读取 操作者只看最终界面;事实在后端存在但页面丢失,在操作层面仍然像不存在
补救订单身份 missed-open repair 的身份必须由 lifecycle / broker correlation / submitted evidence 共同证明,不能靠本地 ID 字符串或旧描述 token 补救单一旦真实成交,系统不能因为证据链投影缺口继续制造残余买入
补救后的审计责任 保护止损投影、止损 basis、IBKR owner context、runtime snapshot 和 run log retention 都必须保持 owner 语义 风险收缩动作做完以后,系统还要能证明它如何理解、展示和保留这次动作

附录 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
c36e8219 Set D90G as default strategy
d3374548 Converge strategy configs on standard contracts
23e1dde9 Deauthorize paper entry ledger target inference
486a85dd Route runtime strategy config through contract owner
176683d3 Retire duplicate strategy config owner
635b575a Route entry selection through planner owner
62557cb2 Route entry sizing through planner owner
ecf2c4bf Route entry evaluation through planner owner
f6356566 Retire flat strategy loader entrypoints
52f3ed4f Reject partial strategy runtime projections
88889810 Prune legacy operator console renderers
9c8ed67a Split data DAO owner modules
0daa0260 Auto-cancel redundant protective stops
0b8b593c Add operator event stream prototype
850df390 Add vendor data availability policy
4aff32cc Route stop price mismatch to auto repair
050bad8d Recognize submitted dry-run plan orders
073c7b69 Localize notifications and suppress owner-gap noise
1b28bfcf Classify vendor-zero reference gaps
0fb6a665 Converge protective stop order contract
2f747366 Deauthorize planned entries as target owner
66571e08 Deauthorize broker facts as expected buy targets
36d0e143 Stop console synthesizing cycle stage rows
13f8775e Prune monitor operator inference paths
c3cd252d Converge strategy action owner contracts
186dce18 Stream auto data refresh step progress
3d783e07 Restore auto refresh progress output
5470bbe5 Summarize startup data prewarm in operator stream
6b0d6ad0 Show broker operation wait progress in auto stream
a1c216f9 Show plan and submit-window facts in operator stream
9bb6f2e6 Add auto lifecycle read model for webui
c4416a55 Add readonly broker event stream
ad798932 Filter operator broker facts to current and today
0acc9f9a Hide historical broker order counts in operator facts
8263cb81 Converge order repair and operator projections
bd4d027d Honor opening-limit caps over premarket quote guard
c28ed5c2 Keep risk-reduction evidence from replacing buy plan owner
695adf83 Clarify auto operator mismatch notifications
f9cc655f Send Bark realtime updates for auto stages
aa913d15 Fix paper order repair idempotency
b6fc2adf Fix intraday entry day-limit contract
f3268a5d Cap intraday entry limits by live quote
81f84d55 Tune intraday entry limit offset
7cecc074 Fix operator plan owner projection
eac1200d Add false breakout research diagnostics
04e7b68f Add operator webui goal execution plan
30008fb2 Publish operator auto state machine read model
31273336 Publish IBKR broker stream availability
92f59229 Support local SSE streaming in operator BFF
4178b9b3 Validate same-origin operator stream in browser
28b5aa76 Validate real Core broker stream in browser
53c58182 Publish IBKR recent event carrier
b61cfe39 Deauthorize broker snapshot fixture fills
ee905786 Record post-restart IBKR stream acceptance
3e4af00c Stabilize IBKR stream owner facts
180838cf Connect Operator WebUI dev mode to live Core data
dea83e7f Expose dedicated broker facts stream in WebUI
973c1fe1 Add broker facts scope interface
2b5ada54 Stabilize operator auto runtime reporting
5bed1484 Add research signal diagnostics tools
c7169cf8 Add IBKR live facts bridge for operator UI
85def649 Route auto resume actions through loop owner
d82d1fcb Improve operator command center freshness
f326d2f5 Remove sync operator event stream facade
18b191fe Keep operator SSE stream async-only
cd8929ed Consolidate broker mutation execution
ea5f4ca7 Keep auto runner alive on recoverable safety gaps
70e9ca42 Keep stop repair active with scoped blockers
be400e51 Serialize preflight reconcile state writes
636f2326 Fix operator order display and state retention guards
5b381065 Keep paper auto runner alive on cycle failures
4b990634 Fix IBKR flat positions and CLI plan visibility
2009f412 Fix Operator WebUI display contradictions
0b9ec29b Polish Operator WebUI visual states
bc5c5415 Clarify Operator WebUI broker sources and key metrics
7d10918d Polish Operator WebUI contract and empty states
fbd3be5b Tighten Operator WebUI display contracts
846127ea Polish Operator WebUI command display
8c3cf1e9 Fix Operator WebUI command layout overflow
e2d6d710 Show Operator WebUI broker source evidence
b54420aa Document Operator WebUI goal validation
7cf16935 Suppress stable waits in event stream
ed4137bf Carry owner position fields into progress view
4865ee72 Preserve position fields across broker progress
2f6234b2 Polish broker owner gap labels
76c4877a Polish operator page display labels

附录 D:代码量变动统计(git numstat 汇总)

我后来发现,很多“系统变得更强了”的错觉,来自一种很偷懒的脑补:看一眼 commit 数量,或者看一眼“改了很多行”,就自动脑补成“做了很多正确的事”。

所以这里留一个很粗糙、但很诚实的统计:只记录代码量的变化,不替它解释意义(尤其是当某一天出现几十万行级别的 churn 时,这个数字本身更像是在提醒你:今天很可能发生了重构、归档、自动生成或批量迁移,而不是“天降 alpha”)。

以下统计以当时的主仓库为准,按 2026-05-30 起至 2026-06-29git 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-22 87 1,517 1,933
2026-06-23 169 21,384 16,891
2026-06-24 948 130,018 54,311
2026-06-25 295 10,383 2,841
2026-06-26 199 31,449 5,020
2026-06-27 281 5,141 3,123
2026-06-28 282 22,809 2,473
2026-06-29 55 3,749 543
2026-06-30 0 0 0
2026-07-01 223 22,918 1,582
2026-07-02 324 16,242 2,804

补记到 2026-07-02 后,区间总计为:13,647 files+27,836,603 / -661,672。这个数字继续变大,并不意味着系统突然更接近可用;它更像是在提醒我,最近这几天真正发生的不是“又写了很多代码”,而是“系统终于开始把研究语义、运行语义、展示语义、数据刷新语义、止损闭环、操作台第一屏、验收证据和自动恢复责任分别钉在正式合同和证据对象上”。如果只看 churn,很容易把这段历史误读成“又一波大改”;但如果把它放回前面几章,它其实更像一次迟来的清账。7 月 2 日又补了一层:清账本身也会制造风险,尤其当它碰到 pending fill、延期止损、无人值守恢复和操作者面板这些“仍然欠着动作”的状态时。

尾声:这不是一个完成品

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

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

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

2026-06-21
Contents
  1. 第一幕:第一条权益曲线太好看了
    1. 序章:最开始只是两篇文章
    2. 第一章:回测第一次骗过了我
    3. 第二章:股票池变大以后,偏差也变得更诚实
    4. 第三章:纸面交易暴露了两个真相
    5. 第四章:V6 看起来更强,也更危险
    6. 第五章:漂亮的回测数字开始互相打架
  2. 第二幕:漂亮数字被迫接受审计
    1. 第六章:项目架构一开始就欠了债
    2. 第七章:5 月 23 日,项目从原型变成工程
    3. 第八章:最严肃的一次安全审计
    4. 第九章:界面也会让人做错事
    5. 第十章:Task 018 和 fail-closed 的代价
    6. 第十一章:10 年数据和指标热身门槛
    7. 第十二章:最夸张的数字,最低的置信度
      1. 中场复盘:我后来真正记住的几件事
  3. 第三幕:券商事实和本地状态开战
    1. 第十三章:纸面交易第一次断桥
      1. 证据(2026-05-30)
    2. 第十四章:给自动化装上回执
      1. 证据(2026-05-30 ~ 2026-05-31)
    3. 第十五章:纸面交易承认券商才是现实
      1. 证据(2026-06-01 ~ 2026-06-02)
      2. 侧记:运行时负担、脏工作树和数字口径
    4. 第十六章:重构最怕“语义应该没变”
      1. 证据(2026-06-02 ~ 2026-06-03)
    5. 第十七章:恢复逻辑开始学会闭嘴
      1. 证据(2026-06-04 ~ 2026-06-05)
      2. 侧记:风控链路里最危险的错觉
    6. 第十八章:容量不是数字,是能不能下单
      1. 证据(2026-06-05 ~ 2026-06-06)
      2. 侧记:容量和持仓上限第一次变成边界
  4. 第四幕:系统里到底谁有解释权
    1. 第十九章:Alpha 藏在怎么持有里
      1. 证据(2026-06-07)
    2. 第二十章:观察层先学会不夺权
      1. 证据(2026-06-13 ~ 2026-06-15)
      2. 侧记:运行时证据和人眼看到的画面不是一回事
    3. 第二十一章:一个不吵闹、也不撒谎的界面
      1. 证据(2026-06-13 ~ 2026-06-15)
      2. 侧记:旧入口不会因为不在菜单里就自然死亡
    4. 第二十二章:回测从结果变成证据
      1. 证据(2026-06-16)
    5. 第二十三章:我为什么一直追问
      1. 证据(2026-05-27 ~ 2026-06-17)
      2. 侧记:复盘真正补上的不是日期
    6. 第二十四章:止损修复不再是补丁
      1. 证据(2026-06-18 ~ 2026-06-19)
      2. 侧记:实盘前真正该还的债
  5. 第五幕:默认策略还没有资格按下按钮
    1. 第二十五章:策略版本真正怎么推进
      1. V1:把文章直觉压成第一套可跑系统
      2. V2:收益更激进,但纸面交易开始制造第二套现实
      3. V6:系统形态完整了,但不能因为完整就自动相信
      4. final40:Alpha 开始从信号转向持有方式
      5. D90:把研究暗知识写成正式合同
      6. 这一串版本真正改变了什么
    2. 第二十六章:研究暗知识被写进合同
      1. 证据(2026-06-20 ~ 2026-06-21)
      2. 侧记:只还阻塞实盘的债
    3. 第二十七章:三条旁支补回主线边缘
      1. 证据(2026-05-12 ~ 2026-06-18)
    4. 第二十八章:同名策略也会有两套语义
      1. d90g 不是“又一个更强默认”,而是一道晋升门
      2. 证据(2026-06-21)
    5. 第二十九章:默认策略切过去以后,真正该删的是旧解释器
      1. 证据(2026-06-21 ~ 2026-06-22)
    6. 第三十章:默认策略还没有资格按下按钮
      1. 侧记:2026-06-22 那些真正改变判断的会话
      2. 证据(2026-06-22 ~ 2026-06-23)
    7. 第三十一章:止损成交以后,系统还要学会算剩下的风险
      1. 侧记:2026-06-23 那些真正改变判断的会话
      2. 证据(2026-06-23 ~ 2026-06-24)
    8. 第三十二章:交易员终端不是日志窗口
      1. 侧记:2026-06-24 那些真正改变判断的会话
      2. 证据(2026-06-24 ~ 2026-06-25)
    9. 第三十三章:验收不是一条绿勾
      1. 侧记:2026-06-25 那些真正改变判断的会话
      2. 证据(2026-06-25 ~ 2026-06-26)
    10. 第三十四章:控制台开始像控制台,现实却不肯配合演出
      1. 侧记:2026-06-26 那些真正改变判断的会话
      2. 证据(2026-06-26 ~ 2026-06-27)
    11. 第三十五章:不是字段太少,是事实被重复解释了
      1. 侧记:2026-06-27 那些真正改变判断的会话
      2. 证据(2026-06-27 ~ 2026-06-28)
    12. 第三十六章:不是没有数据,是第一屏还没有说真话
      1. 侧记:2026-06-28 那些改变第一屏判断的会话
      2. 证据(2026-06-28 ~ 2026-06-29)
    13. 第三十七章:研究开始学会先否定自己
      1. 侧记:2026-06-29 那些真正改变研究判断的会话
      2. 证据(2026-06-29 ~ 2026-06-30)
    14. 第三十八章:工作台越像工作台,老板越不能换人
      1. 侧记:2026-06-30 那些真正改变边界判断的会话
      2. 证据(2026-06-30 ~ 2026-07-01)
    15. 第三十九章:止损不是一个价格,而是一句话有没有说完整
      1. 侧记:2026-07-01 那些真正改变止损语义判断的会话
      2. 证据(2026-07-01 ~ 2026-07-02)
    16. 第四十章:有些 fail-closed 不是安全,而是把风险留给明天
      1. 侧记:2026-07-02 那些真正改变 fail-closed 判断的会话
      2. 证据(2026-07-02)
    17. 第四十一章:产品不是把内部细节折叠起来
      1. 侧记:2026-07-05 那些真正改变产品判断的会话
      2. 证据(2026-07-05 ~ 2026-07-06)
    18. 第四十二章:产品语言不能替事实闭环背书
      1. 侧记:2026-07-06 那些真正改变判断的会话
      2. 证据(2026-07-06 ~ 2026-07-07)
    19. 第四十三章:逐页验收,比漂亮界面更不讲情面
      1. 侧记:2026-07-07 那些真正改变判断的会话
      2. 证据(2026-07-07 ~ 2026-07-08)
    20. 第四十四章:补救订单不是字符串能证明的事实
      1. 侧记:2026-07-10 那些真正改变判断的会话
      2. 证据(2026-07-10)
  6. 附录 A:关键时间线
  7. 附录 B:关键决定
  8. 附录 C:代码演进索引
  9. 附录 D:代码量变动统计(git numstat 汇总)
  10. 尾声:这不是一个完成品

⬆︎TOP