3. 成交仿真入门
回测中最容易被低估的环节,不是策略信号,而是“信号如何变成一笔真实成交”。如果成交仿真过于理想化,再优秀的策略也会在实盘中迅速失效。本节围绕四个核心问题展开:订单有哪些类型、撮合按什么顺序发生、成交价格如何确定、滑点如何建模。
一、核心原理:从信号到成交的四个决策点
一笔回测成交的诞生,本质上要经过四个决策点:订单类型决定“以什么条件进场”,撮合逻辑决定“谁先成交”,成交价格决定“以什么价成交”,滑点决定“实际成交比预期差多少”。四者缺一不可,且相互影响。
常见的订单类型包括:
- 市价单(Market Order):不限定价格,追求立即成交,代价是可能承受较大滑点。
- 限价单(Limit Order):限定最高买价或最低卖价,价格优先,但可能无法成交。
- 止损单(Stop Order):价格触发后转为市价单,常用于风控,但跳空时成交价可能远差于触发价。
- 止损限价单(Stop-Limit):触发后转为限价单,兼顾风控与价格约束,但存在触发后不成交的风险。
撮合逻辑的核心是价格优先、时间优先。买单出价越高越优先,卖单出价越低越优先;同价则先到先得。回测中若忽略时间优先,同一根K线内的多笔订单可能被错误地全部成交。
二、方法流程:成交价格的确定方式
成交价格的确定方式直接决定回测收益的可信度。常见做法有以下几种:
- 收盘价成交:信号在收盘确认,以当日收盘价成交。实现简单,但存在“未来函数”风险,因为收盘价在盘中不可知。
- 次日开盘价成交:信号在收盘确认,次日开盘成交。更贴近实盘,但需处理隔夜跳空。
- 当前价成交:以触发信号时的最新价成交,适合分钟级或Tick级回测。
- 限价单撮合:只有当市场价格触及或穿过限价时才成交,成交价取限价或更优价格。
关键原则:成交价格必须使用“当时可获得的信息”。若用当日最高价或最低价成交,等于假设你能精准买在最低、卖在最高,这是典型的回测幻觉。
下面是一个简化的撮合伪代码,展示限价单如何按价格优先、时间优先成交:
def match_orders(orders, market_price):
# orders: 按价格优先、时间优先排序的订单列表
for order in orders:
if order.side == "BUY" and market_price <= order.limit_price:
fill_price = min(order.limit_price, market_price)
execute(order, fill_price)
elif order.side == "SELL" and market_price >= order.limit_price:
fill_price = max(order.limit_price, market_price)
execute(order, fill_price)
else:
order.status = "PENDING"
三、滑点:回测与实盘之间的“摩擦成本”
滑点是指预期成交价与实际成交价之间的差额。它来源于流动性不足、买卖价差、市场冲击和延迟。回测中若不建模滑点,等于假设市场无限深、零延迟,结果必然偏乐观。
滑点建模常见方式:
- 固定滑点:每笔成交加减固定金额或固定比例,简单但不够真实。
- 比例滑点:按成交价的一定百分比计算,适合流动性稳定的品种。
- 基于波动率的滑点:波动越大滑点越高,更贴近真实市场。
- 基于成交量的滑点:订单量占市场成交量比例越高,冲击成本越大。
| 订单类型 | 成交确定性 | 价格确定性 | 典型滑点 | 适用场景 |
|---|---|---|---|---|
| 市价单 | 高 | 低 | 较大 | 快速进出、流动性好 |
| 限价单 | 低 | 高 | 较小或无 | 震荡市、挂单等待 |
| 止损单 | 高 | 低 | 跳空时极大 | 风控止损 |
| 止损限价单 | 中 | 中 | 中等 | 兼顾风控与价格 |
四、案例与复盘:一个被滑点“吃掉”的策略
假设某策略在收盘价确认信号,次日开盘买入。回测中未设滑点,年化收益18%。加入0.1%比例滑点后,年化降至11%;再加入成交量冲击模型后,年化进一步降至7%。这说明滑点不是小修小补,而是决定策略生死的关键参数。
常见误区:用当日收盘价成交、忽略涨跌停无法成交、忽略停牌、忽略最小变动价位、用未来数据判断能否成交。这些都会让回测结果严重失真。
复盘建议:每次回测后,检查成交明细中是否存在“买在最低、卖在最高”的异常记录;统计滑点分布,确认其是否与品种流动性匹配;对限价单,检查未成交比例是否合理。