公开研究与业内复盘普遍把回测-实盘的盈利偏差源头归为两类:第一类是数据层问题——时间戳错位、行情缺失与供应商归一化处理——这一类往往占据了偏差的大头;第二类是交易成本建模与撮合机制建模。本文专注于第二类中拆得最细、影响最直接的两个组件:滑点与手续费,并顺带讨论与之强耦合的撮合与数据问题。
回测的目的不是证明策略赚钱,而是证明策略在扣完全部摩擦成本之后还能赚钱。任何不能在摩擦成本检验中站住脚的预期收益,在实盘里都不构成可观测的承诺。
一、交易成本的隐形杀手:为何回测收益总是"虚高"
一笔真实的实盘交易,由五类成本叠加而成。新手的回测,往往只建模了第一类。
显性成本。 佣金、印花税、过户费。这一类是确定值,可以查表,每一笔交易都明确给出。
隐性成本。 滑点、市场冲击、排队损失。这一类随订单规模、市场波动率、流动性即时变化,是建模的重灾区。
资金成本。 杠杆利息、保证金占用、隔夜融资。日内策略可忽略;隔夜持仓与中长线策略必须建模。
系统成本。 柜台延迟、网络抖动、撤单失败、行情中断。这一类属于物理层损耗,难以精确量化,但可以通过保守参数近似。
机会成本。 涨跌停板导致的成交失败、停牌期间的策略空仓、滑点扩大后的延迟成交。这一类最难建模,常常被新手直接忽略。
当策略日均换手率进入高频区间(30%以上),隐性成本与系统成本会逐步吞噬全部毛收益。如果进一步把换手率推高到日内级(80%+),即便设置一个5bp 的保守滑点,年化损耗依然可能吞噬掉两位数百分点的毛收益——具体幅度取决于策略的订单簿占用与流动性偏好。
成本的难点在于非线性。固定金额的佣金在小资金账户中占比有限,但单笔最低限制会反向放大——尤其是在小额拆单策略中。滑点则与换手率、订单规模、市场波动率三者耦合,在以日线 Bar 为粒度的回测里,模型看不到订单在盘口队列中真正的排队位置。回测按"价格"撮合,实盘按"时序"撮合——这是模拟与现实之间最大的鸿沟,也是后文专门拆解撮合机制的动因。
成本结构拆解(A股个人账户典型值)
| 成本类型 | 计算依据 | 典型值 | 量化策略敏感度 |
|---|---|---|---|
| 买入佣金 | 成交金额 × 费率 | 万3(约0.03%),可与券商议价 | 中 |
| 卖出总费用 | 成交金额 × 税率 | 千1.3(约0.13%),含印花税 | 中 |
| 单笔最低佣金 | 固定金额 | 5元 | 高(小资金高频) |
| 沪市过户费 | 成交金额 × 费率 | 万0.1 | 低 |
| 滑点(保守) | 名义价 ± 基点 | 5bp(约0.05%) | 高 |
| 市场冲击 | 订单量 / 成交量 | 价格影响系数0.1 | 极高(大单) |
| 杠杆利息 | 资金占用 × 日率 | 约日万1.5 | 中 |
| 隔夜融资 | 融资金额 × 年化 | 约年化6–8% | 中长线策略 |
关键观察一:买卖费率严重不对称。 A股卖出端费率约为买入端的四倍以上。回测代码中如果把买卖费率对称设置,等同于每笔低估约0.1%的成本。日均换手20倍的策略,年度累计低估的成本可达到毛收益的几个百分点——量级足以把中等收益策略的"漂亮回测"打成"实际微利甚至亏损"。
关键观察二:单笔最低佣金对小资金高频是隐形杀手。 以10万元账户、单笔成交1万元为例:买入佣金按费率算为1万元×0.03%≈3元,触发5元下限后,实际费率从0.03%抬升到0.05%;当单笔成交额进一步压低到5000元附近时,佣金侵蚀就超过0.1%——这还没把卖出端的印花税算进来。如果策略又恰好对小额拆单有依赖,回测与实盘的差距会被这个"5元起步价"反复撕开。
关键观察三:滑点与换手率是耦合放大器。 一个看似无害的5bp 保守滑点,在年化换手100倍的策略上累计就是5%;在300倍的策略上累计就是15%。这个数字不是回测里的"预留安全垫",而是年化收益的等额扣减。这也是为什么后文会专门拆解滑点模型的差异——保守给一个 bp 数字并不等于真实建模。
二、滑点模型解析:从固定基点到成交量比例的实战应用
定义
滑点:订单预期成交价与实际成交价之间的偏差。买单推高价格、卖单压低价格。学术上把它称作"价格影响"(price impact),工程上把它当成"成交确定性"的对价。
模型一:固定基点滑点(FixedBasisPointSlippage)
基点设定为5(约0.05%)。买单成交价 = 名义价 × (1 + 0.05%);卖单成交价 = 名义价 × (1 − 0.05%)。
这个模型做了一个隐含假设:订单对市场价格的冲击是常数,与订单规模无关。
适用场景: 流动性充裕的标的(日均成交额过亿)、中低频策略(日换手20%以内)。
优势: 参数稳定、可解释性强、便于敏感性测试。
缺陷: 忽略订单簿深度,对大体量订单失真严重;对流动性差的小盘股也常常明显低估真实滑点。
数值示例。 标的当前价10.00元,买单名义10000股、市值10万元。在5bp 模型下,实际成交价 = 10.00 × 1.0005 = 10.005元,单笔额外成本50元,对应名义本金0.05%。如果这个规模每天触发50笔,按250个交易日折算,年度额外成本约62.5万元,对应总成交额约12.5亿元——成本占总成交额约0.05%,量级并不夸张,但已经足以压缩一个中等收益策略的盈利空间。
更要命的是"恒定冲击"的假设本身。真实盘口中,订单簿深度非均匀、不同价位挂单数差异巨大。一个小额买单可能一次成交,价格无显著变化;一个稍大的卖单就可能击穿一档挂单,把成交价向下推两个 tick。这个非线性,是固定基点模型系统性低估的根源。
模型二:成交量比例滑点(VolumeShareSlippage)
参数体系:
volume_limit:单笔订单占市场总成交量的最大比例,默认常见设置2.5%(即0.025)。price_impact:价格影响系数,默认0.1。- 滑点公式:滑点幅度 =
price_impact × (实际成交量 / 总成交量)²。即滑点随订单占比呈平方级放大。
当单笔订单超过 volume_limit 时,订单被拆分或按比例部分成交——超额部分会被延迟到下根 Bar 或更后的窗口。这个机制在分钟级或 Tick 级回测里表现得更接近真实。
数值示例。 标的当日成交量1000万股,策略一次性提交50万股,占当日成交的5%,超出默认 volume_limit。实际按上限成交25万股,滑点幅度 = 0.1 × (25/1000)² = 0.0000625(约0.625bp),实际成交价 ≈ 10.000625元,单笔成本约156.25元。
对比固定基点模型的单笔成本50元,VolumeShare 在订单占比低于 volume_limit 时反而更宽松——这是因为该模型默认订单可全部成交,没有为"价格推动"付溢价。但只要订单占比越过5%这个量级,VolumeShare 的滑点会以平方级速度上升,把固定基点远远甩在后面。从这个角度看,两个模型不是简单的孰优孰劣,而是适用区间的差异:固定基点对中小单友好,VolumeShare 对大单才显出威力。
固定基点是入门级,VolumeShareSlippage 是工业级。两者在策略年化收益上的差异,主要由订单相对规模与市场流动性共同决定——大单、流动性差的标的,会让 VolumeShare 的成本显著拉开。
模型选择决策表
| 场景 | 推荐模型 | 推荐参数 | 备注 |
|---|---|---|---|
| 日线策略、流动性好 | 固定基点 | 5bp | 保守优先 |
| 分钟线策略、中频换手 | VolumeShare | volume_limit=0.025 | 接近真实 |
| Tick级回测、大资金 | VolumeShare + 自定义冲击 | volume_limit=0.01 | 严格管控 |
| 流动性差的小盘股 | 双模型并行 | 取保守值 | 双向验证 |
自定义冲击模型
头部机构通常不会直接使用平台默认的固定基点或 VolumeShare 参数,常见做法有三类:
- 基于历史 Tick 数据回归真实滑点分布,再用拟合得到的非线性曲线替代默认平方律。
- 按市值、换手率、波动率做分层建模——大盘股和小盘股用不同的冲击系数。
- 在不同市场状态下(牛/熊/震荡)切不同参数集,而不是一套参数走到底。
固定基点是均值估计,VolumeShare 是结构估计。两者共同的盲区是订单簿的瞬态——当下盘口深度、撤单速度、做市商行为随时间快速变化,而这些信息在事后回放里都已经"凝固"了。当前的所有公开模型,都在用一个静态结构去近似一个动态过程——这也是为什么业内头部团队最终都会走向私有定制。
三、手续费的精细化计算:A股与台股市场的差异化设置
手续费不是单一参数。地域、标的、券商、账户类型都会改变最终费率。
A股市场默认参数
- 买入佣金: 成交金额 × 万分之3(约0.03%),可与券商议价至更低。
- 卖出总费用: 成交金额 × 千分之1.3(约0.13%),含印花税与佣金。
- 单笔最低: 不足5元按5元收取。
- 过户费(沪市): 成交金额 × 万分之0.1。
注意卖出费率是买入的四倍以上。回测代码如果把买卖费率对称处理,等同于把每笔卖出少扣了约0.1%的成本。
台湾股市默认参数
- 手续费率: 约0.1425%(买卖双向收取),有议价空间。
- 单笔最低: 约20元新台币。
- 证交税: 0.3%(千分之3),仅卖出单边收取。
台股与A股的差异体现在两点:一是台股的手续费率本身就高于A股的双向均摊水平;二是台股的证交税单独计算,与手续费分离建模。跨市场策略的回测代码需要分市场写入费率结构,而不是用一套费率走遍所有市场。
跨市场手续费对比
| 参数 | A股 | 台湾股市 |
|---|---|---|
| 买入费率 | 万3(约0.03%) | 约0.1425% |
| 卖出费率 | 千1.3(约0.13%),含印花税 | 约0.4425%(手续费0.1425% + 证交税0.3%) |
| 单笔最低 | 5元 | 约20元新台币 |
| 印花税方向 | 卖出单边 | 卖出单边(称证交税) |
| 计算精度 | 按金额比例 | 按金额比例 |
实战注意事项
最低手续费陷阱。 单笔最低限制对日内高频策略的杀伤力远超直觉。A股小资金账户的5元下限,常常把实际费率从万3放大到0.1%甚至更高。台湾市场的20元新台币下限在大额订单上不敏感,但在拆单策略里会反复触发——一笔拆成十笔的委托,前几笔加起来可能就突破了下限成本。
券商议价空间。 头部券商对量化账户的佣金折扣通常落在万2.5至万1之间,具体底线通常不公开。回测中无法精确建模——保守做法是按默认万3建模,激进做法是按万1建模并单独跑一遍敏感性测试,看策略是否依赖那部分议价空间。
资金量变化对冲。 策略资金量从100万放大到1亿,单笔订单的市场占比显著上升,固定基点模型基本失效,必须切到 VolumeShare 模型并把 volume_limit 压低。如果策略打算跨规模复用,就得跑"资金-滑点-容量"三维敏感性,而不是只跑一次10万元账户的回测结果。
四、撮合机制的陷阱:Bar 数据与真实柜台交易的鸿沟
回测平台基于 Bar 数据(日线、分钟线、Tick)模拟撮合。当委托量超过 Bar 内成交量时,模拟撮合结果就开始失真。
模拟撮合的四重局限性
第一,订单在盘口队列中的位置不可见。 实际撮合遵循"价格优先、时间优先"。同价位下,先到的订单先成交。回测模型拿不到订单进入交易所撮合主机的精确时间戳,也就无法重现排队行为——这是模拟与现实的最大鸿沟。
第二,撤单与改单的时序冲突被压缩掉。 实际交易里,订单在挂单、撤单、改单之间快速切换。回测模型以 Bar 收盘价或下根 Bar 开盘价成交,把订单生命周期内的状态变化直接抹平。
第三,交易所撮合引擎的差异化规则被简化。 不同交易所在涨跌停板、集合竞价、断路熔断上有各自的细则。回测模型通常用一套简化规则覆盖,无法复刻所有边界条件。
第四,涨跌停板下的流动性被错估。 当标的封住涨停板时,涨停价位之上堆积的多头买单里,能真正撮合成交的只是队列靠前的那一小撮;回测模型如果以"按 Bar 收盘价成交"的假设处理这种状态,会虚增那些"挂上去却被拒之门外"的买单的成功率,进而虚增策略收益。反过来,跌停板上挂出的卖单大多也无法成交,但想低位接货的买盘撮合通常都能成——回测如果对涨跌停不分方向地"假设成交",结果同样是失真。规则上处理涨跌停时,必须区分买卖方向,分别设置可撮合概率。
实盘撮合的真实链路
订单进入券商柜台 → 柜台风控校验 → 报送交易所前置 → 撮合主机按价格/时间优先级匹配 → 回执确认。
整条链路的常规延迟在50–300毫秒之间。极端行情下,券商系统拥堵、交易所前置降流,延迟可达数秒乃至更长。回测里以 Bar 收盘价成交的假设,与实盘真实成交价之间存在结构性偏差,这一偏差在 Tick 级策略上会被放大到几个 bp 量级。
缩小偏差的工程手段
用 Tick 级回测替代分钟级。 数据量增加10–100倍,但撮合精度显著提升。Tick 数据来自交易所 Level-2 行情,包含逐笔成交与多档盘口。
引入盘口深度数据建模排队位置。 基于历史盘口数据,估算订单在不同价位、不同时间进入队列时的预期成交概率。这一思路在学术上属于"市场微观结构模拟"。
在策略层加入"未成交撤单"的逻辑分支。 当订单在 N 秒内未成交,触发撤单并按市价单重新报单。这一逻辑在回测里难以精细建模,通常退而求其次,以滑点扩大的方式近似处理。
设置"最大单笔订单占比"硬约束。 在策略代码里写硬约束:单笔订单不超过当日该标的预计成交量的某个比例(实务上常用1–5%)。这一约束可在回测中通过订单拆分函数建模,是把"撮合限制"显式化的最直接方式。
五、数据源与复权处理:规避虚假信号的底层逻辑
数据是回测的输入。输入错了,输出再漂亮也是废纸。
复权方式的选择
回测必须使用前复权(qfq)数据。原因:除权除息会带来价格的非交易性骤降,未复权数据上生成的买卖信号没有任何意义——它把"分红"误读成"暴跌"。
- 前复权(qfq): 以最新价格为基准,调整历史价格。价格序列连续,是策略输入的默认选项。
- 后复权(hqf): 以最早价格为基准,调整后续价格。适合用来计算长期持仓的真实成本与累计收益,但不适合直接驱动信号。
- 不复权: 仅在研究股本变动、计算分红再投时使用。禁止直接进入回测。
时间戳与数据完整性
数据层问题在回测-实盘偏差中占据大头,常见的几类具体表现:
时间戳错位(毫秒级)。 导致 Tick 与 Bar 无法对齐。不同数据源对同一笔成交的时间戳可能相差数毫秒——对日线策略无害,对分钟以内策略却可能直接生成虚假信号。
缺失 Tick 的填充方式。 用前收盘价或成交量加权价填充,会人为压低或抬高某根 Bar 的边界值。当标的流动性差、Tick 稀疏时,填充价格与真实价格偏差可能拉到几个 bp 的量级。
跨市场时区。 A股、港股、美股的交易时间窗口有重叠,跨市场策略如果不显式处理时区,信号的时序会错乱——"下午三点之后某港股标的放量"的信号可能被错误地复用到同一天的美股盘前。
数据清洗清单
| 检查项 | 检查方法 | 失败后果 |
|---|---|---|
| 复权方式 | 验证除权日价格连续性 | 虚假暴跌信号 |
| 时间戳对齐 | 检查毫秒级连续性 | Tick 错位 |
| 异常值过滤 | 3σ 原则或成交量加权 | 滑点异常放大 |
| 停牌处理 | 验证成交量=0 区间 | 误成交 |
| 涨跌停处理 | 验证价格边界,按方向分别建模 | 流动性误估 |
| 上市 / 退市日 | 验证交易日历 | 错配标的池 |
| 分红送股 | 验证复权因子 | 价格跳空 |
数据源的选型逻辑
不同数据源在覆盖范围、更新延迟、价格修正上存在差异。Wind 等商业终端覆盖最全,费用也最高;开源数据源在 A 股覆盖完整,Tick 精度通常有限;券商 Level-2 行情延迟最低(毫秒级),但通常需要付费且与交易账户绑定。
生产级回测建议多数据源交叉验证——任何单一数据源都可能存在系统性偏差,跨源对齐本身就是一种校验。当策略对 Tick 精度敏感时,单一数据源的"看起来正常"是不够的,必须用第二数据源做一致性比对。
六、参数敏感性测试与模型边界
核心原则
单一参数的最优值并不重要,重要的是参数在某个区间内变化时,策略表现保持稳定。
尖峰参数 = 过拟合嫌疑。平坦高原 = 真实信号特征。
测试方法
第一步:选定基准参数集。 固定其他参数,只扫描目标参数(滑点基点、volume_limit、手续费率等)。
第二步:逐参数 ±20%、±50% 扫描。 在基准值上下以等比或等差步长取值,观察策略收益曲线是否塌陷。
第三步:绘制参数–收益热力图。 横轴为参数值,纵轴为策略收益,颜色或等高线表示收益分布。寻找"平坦高原"而非"单点尖峰"。
第四步:选择稳健区间。 在稳健区间内,策略表现稳定;区间外,策略失效或收益骤降。把这个区间写进策略上线前的"参数容忍清单"。
模型局限性与不可控因素
即便参数设置已经完备,实盘仍可能亏损。物理层因素无法被完整建模。
柜台断线。 极端行情下券商系统拥堵,撤单指令无法及时送达或送达但被前置拒收,整个策略进入"挂单不撤、被动持仓"的状态。这是系统侧的真实风险,单靠扩参数无法对冲。
网络延迟。 跨地域部署的策略受物理距离限制。最快下单延迟受光速约束——上海到深圳的光纤往返延迟约25毫秒,跨太平洋则会拉长到百毫秒量级。
黑天鹅事件。 流动性瞬间枯竭,滑点模型失效。保守给到的5bp 在这种行情下远远不够,实盘可能滑到几十 bp 乃至更高。
政策切换。 T+1 改 T+0、印花税调整、涨跌幅限制变化等规则变更直接破坏策略假设。这种风险事前难以预测,只能在策略层加上"规则敏感性"的应急开关。
资金规模冲击。 策略容量上限被突破后,边际收益递减甚至为负。回测里假设的资金量与实盘的真实资金量通常不一致——小账户跑得好好的策略,放大到机构规模后可能在第一个季度就被滑点和冲击成本吃掉所有 alpha。
边界认知
回测的价值在于"在限定条件下证明策略逻辑成立",而不是"预测实盘收益"。任何把回测结果当作承诺的认知,都是对量化方法论的误读。
模型的局限性就是模型的边界。在边界内,策略可被信任;在边界外,策略失效。量化交易者的核心任务不是追求更高的回测收益,而是更精确地定义策略的失效边界。在"交易成本被完整建模"、"撮合机制匹配策略频率"、"数据源经过严格校验"这三条约束全部满足之前,任何回测收益数字都不构成实盘承诺。
Related reading: 线性模型与深度学习在量化交易中的博弈:简单与复杂的策略抉择.
