跳转到内容

回测引擎设计

回测引擎的可信度由四个显式建模的模块决定:成交、成本、延迟、资金。任何一块的简化都会让回测结果系统性偏向乐观——而偏乐观的回测比没有回测更危险。

假设乐观程度说明
收盘价成交最乐观信号在收盘才可得,实际只能次日成交——系统性前视
次日开盘价基准最小可行假设
次日 VWAP更真实对容量较大的策略应使用
撮合限价单最真实需要订单簿数据,见限价订单簿

最低标准:信号时刻与成交时刻分离。用收盘价给"收盘信号"成交,是几乎所有新手回测的共同 bug。

成本不是常数,应随成交量占比非线性增长:

成本=佣金+12价差+冲击(成交量占比),\text{成本} = \text{佣金} + \tfrac{1}{2}\text{价差} + \text{冲击}(\text{成交量占比}),

冲击项的常用形式为平方根律:impact≈c σ Q/ADV\text{impact} \approx c\, \sigma\, \sqrt{Q / \mathrm{ADV}},QQ 为订单量,ADV\mathrm{ADV} 为日均成交量。要点:

  • 成交量占比 > 1% 时,冲击成本进入非线性区间,容量上限由此而来;
  • 价差是时变的:刚暴跌的股票价差扩大数倍,见短期反转;
  • A 股还有印花税等制度性成本,见A 股:T+1、涨跌停与融券。
  • 延迟:信号计算 → 下单 → 成交的链路延迟,最小可行实现是"下一根 bar 成交 + 固定延迟惩罚";
  • 资金与保证金:期货/杠杆策略必须显式跟踪保证金占用、可用资金与强平线,见期货:保证金、换月与限仓;
  • 停牌与涨跌停:无法成交的日子要真实地"无法成交",见回测中的典型偏差。
fill-model.py
import numpy as np
def fill_price(side: str, next_open: float, spread: float,
q: float, adv: float, sigma: float, impact_coef: float = 0.5) -> float:
"""最小可行成交模型:次日开盘 + 半价差 + 平方根冲击"""
half_spread = spread / 2
impact = impact_coef * sigma * np.sqrt(q / adv)
return next_open * (1 + half_spread + impact) if side == "buy" \
else next_open * (1 - half_spread - impact)
  • 每笔模拟成交记录:时间、价格、信号价、假设的成本——事后审计与滑点分析的数据源,见监控与事故复盘;
  • 回测输出应能回答:"这笔成交用了什么假设?"
  • 默认成交假设:次日开盘/VWAP + 半价差 + 平方根冲击;
  • 容量上限按成交量占比 1–5% 设定,随策略频率调整;
  • 每笔模拟成交可审计,成本假设可配置且记录在案。
  • 用收盘价成交是最乐观的假设,应至少用次日开盘或 VWAP
  • 成本模型应随成交量占比非线性增长(平方根律)
  • 记录每笔模拟成交以便事后审计