撮合层:把约束推到会绑定的场景
7/7 单元测试通过,而滑点从 0 到 50bp 让收益从 +35% 变成 -60%
p4.1 留下一个问题:三条交易约束在大盘股 + 低频策略下,一次都没绑定。
那撮合层白写了吗?这一节回答它,分三步:
- 单元测试:七条规则逐条验证,不依赖任何行情数据、毫秒级跑完——7/7 通过
- 极端行情段:2015 年 6–9 月,一字板密度是全区间的 3 倍
- 滑点敏感性:从 0 到 50bp,同一条策略的累计收益从 +35.32% 变成 -59.58%
最后那个数字才是撮合层真正的重点:约束是否绑定要看场景,但成交价假设永远在起作用。
消防栓一年到头没人用。
你不会因此拆掉它——但你必须定期检查它能不能出水。
撮合层的约束就是消防栓:平时不绑定,而检查它"能不能出水"的办法,就是单元测试。
一、单元测试:七条规则逐条验证
撮合层最怕的不是"没实现",是"实现了但从来没被触发过,所以没人知道它对不对"。
解法是 p0.3 那条纪律的直接应用:用构造数据,让每条规则各响一次。
✓ 一字涨停买入 涨停封板买不进
✓ 一字跌停卖出 跌停封板卖不出
✓ 停牌日下单 停牌不可交易
✓ T+1 当日买入卖出 T+1 当日买入不可卖
✓ 资金不足(买 10 万股只有 5000 元) 成交 400 股
✓ 整手取整(下单 150 股) 成交 100 股
✓ 限价买 9.0(当日最低 9.8) 限价买单未触及
7/7 条规则按预期工作
注意后两条不是"拒绝",而是部分成交与取整——撮合层不是只会说"不",它要给出在约束下能做到的最好结果:
- 资金只有 5000 元却想买 10 万股 → 成交 400 股(而不是全部拒绝)
- 下单 150 股 → 成交 100 股(向下取整到一手)
这七条测试不依赖行情数据、毫秒级跑完,应该挂在每次提交上。
二、极端行情段:约束在什么时候绑定
p4.1 实测:8 只大盘股全区间,一字板占 0.038%。那极端行情呢?
2015-06-01 ~ 2015-09-30,20 只标的 1572 个交易日:
一字涨停 1 天、一字跌停 1 天(合计占 0.13%)
同 20 只标的全区间 76806 个交易日:一字板合计占 0.039%
→ 极端行情段的一字板密度是全区间的 3 倍
3 倍——但绝对数量仍然很小(1572 天里只有 2 天)。这个结果需要诚实说明。
原因是样本选择:我们的标的池是沪深300 成分股,也就是市值最大、流动性最好的那批。2015 年那轮"千股跌停",跌停的主力是中小市值股票,而它们不在这个池子里。
所以这里的正确结论不是"极端行情也不太绑定",而是:
在本样本(大盘蓝筹)上,即使极端行情段,一字板也很少。要真正压出撮合层,需要把标的池换成中小市值——那是另一份数据。
我保留这个"不够震撼"的结果,因为改样本去凑一个更好看的数字,正是这门课一直在批评的做法。
三、滑点:那个永远在起作用的东西
约束是否绑定要看场景。但成交价假设,每一笔交易都在起作用:
| 滑点设定 | 平均累计% | 平均成交笔数 |
|---|---|---|
| 0 bp | +35.32% | 434.7 |
| 5 bp | +19.87% | 416.8 |
| 10 bp | +6.23% | 402.5 |
| 20 bp | -16.52% | 402.0 |
| 50 bp | -59.58% | 381.3 |
从 +35.32% 到 -59.58%,95 个百分点。
这就是为什么撮合层不能省:它不只是"处理涨跌停"那几行代码,它决定了你每一笔交易的成交价——而成交价是回测里最基础、也最容易被默认掉的假设。
🔧 动手做:找出这条策略的"死亡线"(8 分钟)
0 成本下它 +35%,50bp 下 -60%。那它到底在多少滑点由赚转亏?别看答案,自己找。
把第 134 行的滑点档位改密,逼近那个拐点:
for slip in (0.001, 0.0012, 0.0014, 0.0016, 0.0018, 0.002): # 10~20bp
重跑 python matching_stress.py,你会看到(实测):12bp 还 +1.09%,14bp 就 -3.65%——盈亏平衡线在 ≈13bp。
想明白:13bp 是多小的数?这条策略跑在沪深300 大盘股上,平时滑点也许才几个 bp、还撑得住;可波动日、下大单、或换到流动性差的小盘股,单边十几 bp 分分钟。这条零成本下 +35% 的"好策略",利润薄到只剩一层滑点的厚度——滑点稍一放大就归零。 所以看到任何零成本下好看的策略,先算它的死亡线在哪、你实盘会不会碰到。
市价单 vs 限价单
| 市价单 | 限价单 | |
|---|---|---|
| 成交 | 一定成交 | 可能不成交 |
| 价格 | 吃滑点 | 省滑点 |
| 危险 | 极端行情下成交价可能很差 | 没成交的往往正是行情跑得最快的时候 |
限价单看起来更省,但它的代价是隐性的:你想追的时候追不上,而追不上的那几次,恰恰是决定收益的那几次。
真实项目里,限价单的"未成交率"必须单独统计。只报成交的那些,等于只统计了你运气好的时候。
四、撮合层的完整规则表
本引擎实现的 A 股规则(limit_pct 等均可配置):
| 规则 | 实现 | 来源 |
|---|---|---|
| 涨跌停 | 一字板不成交;成交价被限价钳住 | 交易所公开规则(主板 ±10%) |
| T+1 | 当日买入锁定,次日解锁 | 交易所规则 |
| 整手 | 买入按 100 股向下取整;卖出可不足一手 | 交易所规则 |
| 停牌 | tradable=False 直接拒单 | p1.3 实测 68 天 |
| 资金约束 | 逐手递减直到买得起 | 现实约束 |
| 佣金 | 双边,含最低 5 元 | 券商常见档位 |
| 印花税 | 仅卖出收取 | 现行规则 |
| 滑点 | 买入向上、卖出向下,并被涨跌停钳住 | 建模假设 |
⚠️ ST 类、创业板与科创板的涨跌停档位不同(且规则会调整)。本引擎把它做成参数而不是写死,正是因为规则会变,而写死的代码不会告诉你它过时了。
📌 免责:本课为技术教学,标的为沪深300 成分股按代码序取样(非精选),所有回测为历史模拟,不构成投资建议。交易规则以交易所现行公告为准。
三件事:
- 单元测试 7/7 通过——七条规则用构造数据各响一次,不依赖行情、毫秒级跑完。撮合层不只会说"不":资金不足时成交 400 股、下单 150 股时成交 100 股,它给出的是约束下能做到的最好结果
- 极端行情段一字板密度是全区间的 3 倍,但绝对数量仍很小(1572 天里 2 天)——因为样本是大盘蓝筹,而 2015 年跌停的主力是中小市值股。我保留这个不够震撼的结果,因为改样本去凑好看数字正是本课一直在批评的做法
- 滑点从 0 到 50bp:+35.32% → -59.58%,95 个百分点——约束是否绑定看场景,成交价假设永远在起作用
下一节讲账户层:持仓、可用资金、T+1 锁定、分红除权。其中有一个必须写在明处的约定——用后复权价就不能再单独入账现金分红,否则同一笔钱会被算两次。
🔎 来源与核验· 3 条,点开核对
