从零搭建个人回测框架:避开六大致命陷阱,让回测结果不再自欺欺人

引言:回测是策略上线前的”测谎仪”

上一篇数据地基把数据层踩稳了,这一篇接着讲回测层——策略在拿真金白银上线之前,唯一能替你说服自己”这套逻辑真有效”的,就是回测。

但回测有一个天然的道德风险:它是全世界最容易被自己骗自己的环节。一个策略在历史数据上跑出年化 50%,你信了,上实盘,半年亏掉 30%——这种故事每天都在发生。问题往往不在策略逻辑,而在回测框架本身漏掉了某些”现实摩擦”。

因子指南里我们列举过幸存者偏差、前视偏差、过拟合等常见陷阱。这一篇把它们展开成可操作的工程清单,并补上滑点、手续费、涨跌停、撮合假设这几条回测里最常被忽略的”现实裂缝”。目标是:让回测结果尽可能接近实盘,而不是看起来好看的自欺欺人。


一、为什么个人需要理解回测框架(而不只是用平台)

聚宽、米筐这类在线平台内置了回测引擎,开箱即用。那为什么还要讲框架原理?三个理由:

  1. 黑盒回测最容易自欺:你不知道平台默认假设了什么撮合规则、怎么处理涨跌停、滑点设多少。默认值往往偏乐观,回测好看但实盘失真。
  2. 理解框架才能诊断问题:实盘和回测偏离时,只有懂框架的人才能定位是数据问题、撮合问题还是逻辑问题。
  3. 个人策略的灵活性:平台的策略表达受限,自己的框架能实现任何想法,与 QMT 实盘衔接也更顺畅(可以参考用 DeepSeek 写 QMT 代码的思路,把回测和实盘代码统一)。

结论:起步可以用平台快速验证 idea,但策略一旦要上实盘,必须有一套自己看得懂、改得动的回测框架,哪怕它很简陋——简陋但透明,胜过精致但黑盒。


二、回测框架的最小骨架

一个回测引擎再复杂,核心也只有五个环节:

1
[数据加载] → [信号生成] → [撮合] → [成本] → [统计]
  1. 数据加载:按交易日历逐日推进,只喂给策略”当日已知”的数据(杜绝前视,见陷阱1);
  2. 信号生成:策略在每个交易日 t 收到截至 t 的数据,输出目标持仓或买卖信号;
  3. 撮合:把信号转成成交单,确定成交价(次日开盘价?当日收盘价?VWAP?);
  4. 成本:扣手续费、印花税、过户费、滑点;
  5. 统计:计算净值、收益、回撤、夏普、胜率等指标。

陷阱几乎全在第 1、3、4 环节。下面逐一展开六大陷阱。


三、六大陷阱详解

3.1 前视偏差(未来函数):回测的头号杀手

症状:回测收益高得离谱,实盘完全跑不出来。

机理:策略在 t 日用了 t 日才能知道、甚至更晚才公布的信息。最典型的就是数据篇讲过的财报未来函数:用 4 月 1 日的因子选股,却喂进了 4 月 28 日才公告的一季报。

其他常见形态

  • 用当日收盘价决定当日开盘买入——收盘价要收盘才知道;
  • 用”当日成交量”做当日盘中决策;
  • 用事后修订的财务数据;
  • 用未来才确定的退市/ST 标记做历史筛选。

对策

  • 严格 point-in-time:数据只喂到 t 日开盘前已知的信息,财报按公告日对齐;
  • 撮合时把”决策时点”和”成交时点”物理分开——t 日收盘生成信号,t+1 日开盘成交(见 3.6 撮合);
  • 数据库里任何”带未来信息”的字段(如退市标记)都要有”当时是否已知”的版本,或干脆不用于历史筛选。

自检:把策略时间整体往后挪一个交易日,如果收益大幅变化,几乎可以断定有未来函数。

3.2 幸存者偏差:只回测还活着的票

症状:回测收益虚高,尤其是小市值策略。

机理:股票池里只有今天还在交易的股票,退市、ST 摘牌的票被悄悄排除掉了。这些退市票往往是亏损惨烈的,排除它们等于把最差的成绩单撕掉再算平均分。

对策

  • 股票池必须用历史时点的成分:t 日的可交易股票 = t 日实际上市、未退市、未停牌的股票;
  • 数据库要保留退市股票的完整历史(数据篇第六节强调过,入库时不要过滤退市股);
  • 用 point-in-time 的上市/退市日期判断 t 日某股票是否可交易。

自检:回测期间内股票池数量是否稳定?如果只增不减或长期持续下降,说明退市股被剔除了。

3.3 滑点、手续费、印花税处理不当:被忽略的现实摩擦

症状:回测显示盈利,实盘扣除成本后亏损。

机理:很多人回测只设了个”千三佣金”,漏掉了印花税、过户费和滑点。其中滑点最致命——你以为能以收盘价成交,实盘可能高买低卖一个点。

对策(A股完整成本清单)

  • 佣金:万 2.5~万 3(双边),按你的实际费率;
  • 印花税:千 1(卖出单边,2023 年减半后实为万 5,以最新政策为准);
  • 过户费:沪市双向,极小但别漏;
  • 滑点:保守设限价单 12 个最小价位、市价单 0.2%0.5%。滑点与策略换手率乘积惊人——月调仓 0.3% 滑点 ≈ 年化 3.6%,足以吃掉一个平庸策略的全部超额。

自检:把所有成本翻倍,看策略还赚不赚。还赚,说明对成本不敏感;一翻倍就亏,说明策略的”超额”全是摩擦假象。

3.4 涨跌停与停牌:撮合假设过于理想

症状:回测里完美成交,实盘里挂不上单。

机理:回测默认”想买就买得到、想卖就卖得出”,但 A 股有涨跌停板——涨停时买单排队可能一天成交不了,跌停时卖单同样。停牌期间更彻底,根本无法交易。

对策

  • 涨跌停撮合规则:若 t+1 日开盘价已涨停,买单不予成交(或部分成交);跌停同理。最简单的实现是”涨停日不能买入、跌停日不能卖出”;
  • 一字板:开盘即涨/跌停且全天封住,完全不成交;
  • 停牌:持仓冻结、不能调仓,复牌后按实际价格计算;
  • 流动性约束:单笔成交不超过当日成交量的 X%(如 10%),否则冲击成本激增。

自检:统计策略的”未成交委托占比”。如果超过 10%,说明回测里大量”假成交”在虚增收益。

3.5 过拟合:在历史数据上反复调参

症状:回测夏普 3.0,实盘 0.5 都没有。

机理:在历史数据上反复试不同参数,总能凑出一组好看的——但这是对历史噪声的拟合,不是对市场规律的发现。参数越多、调得越细,过拟合越严重。

对策

  • 样本外检验:留出最近 1~2 年数据完全不参与调参,最后看策略在样本外是否还有效;
  • 滚动回测(Walk-forward):用滚动窗口——比如用 2018-2021 训练参数,2022 检验,再 2019-2022 训练、2023 检验——看策略在不同时段是否稳健;
  • 参数稳定性检验:把参数上下浮动 10%~20%,看收益是否断崖式变化。如果微调就崩,说明参数过拟合;
  • 奥卡姆剃刀:能用 3 个因子解释的策略,就不要上 10 个。简单模型的普适性远超复杂模型;
  • 警惕超参搜索:用网格搜索/遗传算法在历史数据上搜出”最优”参数,几乎必然过拟合。

自检:你的策略有几次”刚好”踩中历史大顶/大底?超过两三次,多半是过拟合。

3.6 撮合假设过于理想:决策与成交时点不分

症状:回测里”信号一出就成交”,实盘里信号和成交差着一截。

机理:很多人回测用 t 日收盘价既算信号又当成交价,等于”看着收盘价做决策、又用收盘价成交”——这是隐性的前视偏差。

对策(标准做法)

  • 决策与成交物理分离:t 日收盘后用截至 t 的数据生成信号,t+1 日开盘(或 VWAP)成交;
  • 这天然引入了一天的”信号延迟”,是真实交易里无法避免的摩擦;
  • 实盘中 QMT 的 委托/成交回调 也是”信号→下单→成交”异步的,回测撮合逻辑要与实盘对齐。

自检:把策略的成交价从”次日开盘”改成”当日收盘”,如果收益暴增,说明你之前的乐观假设在虚增收益。


四、六大陷阱的优先级

不是所有陷阱都同等重要。按对回测真实性的破坏力排序:

优先级 陷阱 破坏力 修复难度
1 前视偏差 致命 中(需 PIT 数据)
2 幸存者偏差 致命 中(需退市股历史)
3 涨跌停/停牌撮合 中(撮合逻辑)
4 滑点/成本 低(参数设置)
5 过拟合 高(需纪律)
6 撮合时点 低(流程改造)

个人起步至少要把 3、4、6 修掉(改动小、收益大),1、2 是进阶必修(数据层功夫),5 是终身功课。


五、回测与实盘的预期校准:把回测”打折”再看

即使修掉了所有陷阱,回测和实盘仍有差距。成熟的做法是主动把回测预期打折

  • 收益预期打折:实盘收益通常是回测的 50%~70%,因为实盘有数据延迟、心理扰动、资金占用成本;
  • 回撤预期放大:实盘最大回撤通常是回测的 1.2~1.5 倍,因为实盘里有回测模拟不到的极端情况;
  • 夏普预期打折:实盘夏普打 7~8 折是常态。

如果回测年化 15%、最大回撤 10%,按打折后预期(实盘年化 ~9%、回撤 ~15%)你仍然满意,再上实盘。这就把程序化解决情绪里的”情绪隔离”和回测里的”预期校准”连成一条线:回测打折后还值得做,实盘才不会在第一个回撤期就崩溃。


六、最小可用框架的代码结构建议

不需要从零造轮子,但建议理解这个骨架,便于读开源框架或自研:

1
2
3
4
5
6
7
8
9
10
11
Backtest:
for t in 交易日历: # 逐日推进
data = load_data_up_to(t) # ① 只喂 t 已知数据(防前视)
universe = get_tradable_universe(t) # ② t 日可交易股票(防幸存者偏差)
target = strategy(data, universe, t) # ③ 策略输出目标持仓
fills = match(target, t+1_open_price, # ④ 次日开盘撮合(防撮合时点)
limit_up_down_filter, # 涨跌停过滤
volume_cap=0.1) # 流动性约束
cost = commission + stamp_duty + slippage # ⑤ 完整成本
update_portfolio(fills, cost)
stats = compute_metrics(净值序列)

每一步都对应一个陷阱的防御点。关键不是代码多漂亮,而是每一步是否真的”诚实”——回测引擎的诚实,比策略的聪明更重要。


七、总结:回测的价值不在好看,在可信

回顾本文核心:

  1. 回测是测谎仪:它的价值不在于让策略看起来好看,而在于尽可能诚实地暴露策略的真实表现;
  2. 六大陷阱:前视偏差、幸存者偏差、涨跌停/停牌、滑点/成本、过拟合、撮合时点——按破坏力排序,前三个最致命;
  3. 起步必修:撮合时点分离、完整成本、涨跌停过滤(改动小、收益大);
  4. 进阶必修:point-in-time 数据防前视、保留退市股防幸存者偏差(数据篇的地基在这里兑现);
  5. 终身功课:过拟合——样本外检验 + 滚动回测 + 参数稳定性 + 奥卡姆剃刀;
  6. 预期打折:实盘收益按回测 57 折、回撤按 1.21.5 倍预期,打折后还满意才上实盘。

数据层和回测层,是个人量化最容易被忽视、却最决定成败的两个环节。这两层做扎实了,差异篇里说的”错位竞争”才有底气——你的策略不是在历史噪声上自欺,而是在真实摩擦里依然有效。下一篇我们继续往上层走,聊聊个人量化的仓位管理与风控。


⚠️ 免责声明

本文仅提供回测方法论与工程经验:

  • 回测结果不代表实盘表现,历史有效不代表未来有效
  • 文中提到的费率、印花税等以最新政策为准,请自行核实
  • 文中内容不构成任何投资建议,因使用本文方法造成的任何损失,由使用者自行承担

本文更新于2026年9月7日