纸上交易全流程:它当场抓到了一个回测掩盖的缺陷
第一版 209 笔委托里 112 笔被拒,全部是"资金不足"——下单数量按开盘价算,却按含滑点与佣金的价格成交
把系统跑成闭环的第一次,它就抓到了一个缺陷:
第一版:委托 209 成交 97 被拒 112
被拒原因:资金不足 112 笔(100.0%)
53.6% 的委托因为"资金不足"被拒——而账户里明明还有钱。
根因很简单,也很典型:
下单数量按开盘价算,却按「开盘价 × (1+滑点) + 佣金」成交。
每次想满仓,都会差那么一点点。
修复后:
修复后:委托 206 成交 98 被拒 108
被拒原因:不足一手 108 笔(100.0%)
笔数几乎没变,但原因从"一个 bug"变成了"一个真实约束"。
而这个缺陷,在 P4 的回测里跑了整整一章都没被发现。
你按标价算好了要买 10 件,到收银台才发现还有税。
结账时刷不过——不是你没钱,是你算的时候没把税算进去。
而如果你只是在 Excel 里模拟购物,永远不会发现这件事。
一、一天的处理顺序(顺序即纪律)
① 新的一天 → 解锁昨日买入(T+1)
② 推入今日 Bar → 更新行情
③ 用「昨日及之前」的数据算信号 —— 绝不用今日
④ 按信号生成委托 → 走 Gateway → 模拟撮合
⑤ 收盘后记账并落盘
与 P4 事件引擎完全一致。 而它与回测的唯一区别是:
数据是一天一天喂进来的,而不是一次性给全。
这个区别正是纸上交易的价值——它能暴露"我以为我没用未来数据"的那些地方,也能暴露这一节这种下单数量的计算缺陷。
二、缺陷是怎么被抓到的
回测掩盖它,纸上交易暴露它——原因在"谁来决定下单数量"。
| 谁算数量 | 结果 | |
|---|---|---|
| P4 回测 | 引擎内部按差额算,并自带"买不起就记被拒"的保护(p4.1) | 缺陷被引擎兜住了 |
| 纸上交易 | 调用方(策略循环)算好数量再发给撮合器 | 缺陷暴露在调用方 |
这不是说 P4 的引擎更好,而是说:一旦把"算数量"这件事移到系统边界外,它就成了一个独立的、需要被测试的东西。
修复:
# ⚠️ 下单数量必须按**含滑点与佣金的价格**算,并预留缓冲。
eff = bar.open * (1 + gw.match.slippage) * (1 + gw.match.commission)
if side == "BUY":
affordable = gw.match.cash / eff
qty = int(min(abs(diff) / eff, affordable))
一行
bar.open换成eff,112 笔"资金不足"归零。
🔧 动手做:重现纸上交易当场抓到的那个 bug(6 分钟)
跑纸上交易闭环的第一版(带 bug 的那版),看被拒统计。
你会看到(实测):209 笔委托里 112 笔被拒,原因 100% 是"资金不足"。而这不是策略没钱——是 bug:下单数量按开盘价算,却按含滑点与佣金的价格成交,于是每次都差那么一点点钱,系统性地"资金不足"。
想明白:这个 bug 在回测里根本不会暴露(回测不模拟"下单时和成交时价格不一致"这件事)——是纸上交易全流程跑起来才当场抓到的。这就是为什么止于回测不够,要有纸上交易:把下单、撮合、账户串成真实闭环,才会撞出回测口径掩盖的缺陷。修复之后剩下的 108 笔被拒,才是真实约束(涨跌停/停牌),不是 bug。
三、修复之后剩下的 108 笔,不是 bug
不足一手 108 笔(100.0%)
这是真实约束:仓位接近满仓后,策略仍在要求加仓,而剩余现金买不起一手。
它恰恰是 p4.1 要求必须记录、不许静默跳过的那种情形。
同样是 100+ 笔被拒,修复前是"我的代码错了",修复后是"市场就是这样"。
而分辨这两者的唯一办法,是看被拒原因的分布——不是看被拒数量。
四、纸上交易该记什么
| 记录 | 用途 |
|---|---|
equity.jsonl(每日权益与现金) | 净值曲线、回撤、水下期 |
fills.jsonl(每笔成交) | 成交点标注、换手统计、成本核对 |
rejects.jsonl(每笔被拒) | 诊断报告——本节的缺陷就是从这里发现的 |
| 策略指纹 + 数据指纹 | 追溯(p8.4 / p8.5) |
第三行是最容易被省掉、也是最有价值的一份。
五、纸上交易跑多久才算数
这是一个没有标准答案、但可以定量讨论的问题。
接 p5.8 的结论:在信息比率约 0.22 的量级下,要在统计上确认策略衰减需要 82 年数据。
所以纸上交易的目的从来不是"验证策略能赚钱"——那需要的样本量你等不起。
它验证的是三件几十笔交易内就能确认的事:
- 成交价与假设是否吻合(实际滑点 vs 假设滑点)
- 委托被拒的比例与原因(本节)
- 信号频率是否与回测一致(不一致 = 有一方用错了数据)
这三件事,正是 p5.8 给出的"更诚实的替代方案"——监控假设是否被推翻,而不是监控净值。
📌 免责:本课为技术教学,纸上交易为模拟撮合,不接真实券商通道;不构成投资建议。
四件事:
- 闭环第一次跑就抓到一个缺陷:209 笔委托里 112 笔"资金不足"(53.6%),而账户里明明还有钱。根因是下单数量按开盘价算,却按含滑点与佣金的价格成交。修复后 112 笔归零
- 回测掩盖它、纸上交易暴露它,原因在"谁算数量":P4 回测由引擎内部算并自带 p4.1 的保护,而纸上交易由调用方算——一旦把"算数量"移到系统边界外,它就成了一个独立的、需要被测试的东西
- 修复后剩下的 108 笔"不足一手"不是 bug,是真实约束。同样是 100+ 笔被拒,修复前是"我的代码错了",修复后是"市场就是这样"——而分辨这两者的唯一办法,是看被拒原因的分布,不是看被拒数量
- 纸上交易的目的不是"验证策略能赚钱"(p5.8:那需要 82 年数据),而是验证三件几十笔内就能确认的事:成交价与假设是否吻合 · 被拒比例与原因 · 信号频率是否与回测一致
它与回测的唯一区别是数据一天一天喂进来——而这个区别正是它的全部价值。
必记的四份记录里,rejects.jsonl 是最容易被省掉、也最有价值的一份:本节的缺陷就是从它发现的。
下一节讲怎么让这套系统活下去:进程守护、日志、告警、定时任务,以及故障演练。
🔎 来源与核验· 2 条,点开核对
