Backtesting.py / 回测审计

Backtesting.py 回测可信度审计:未来函数、过拟合与成本怎么查

回测的价值不是证明策略一定赚钱,而是尽早发现它为什么可能失效。本文给出 11 项可执行的审计检查,并逐项附上本站实测结果:98 组参数在样本内 89 组赚钱、样本外 0 组赚钱;同一策略只改执行时点差 33.94 个百分点;用了未来信息的版本「更好看」28.70 个百分点。每项都写明怎么查、判据是什么、以及哪些只能做方法说明。

核验方式:本站实测(backtesting 0.6.6)样本:6 只 A 股 + 1 只港股 + 官方内置样例实测日期:2026-09-24
未来函数静默高估收益
过拟合样本外集体失效
成本假设决定「离盈亏多远」
基准与口径决定结论方向
回测可信度的四条主线:前视偏差、参数过拟合、成本与执行假设、基准与统计口径;非官方图示,做法以官方文档与你安装的版本为准。
测试版本backtesting 0.6.6(PyPI 发布版)Python 版本本站实测 3.11.9;官方要求以 README / PyPI 为准最后核验2026-09-29(实验数据 2026-09-24 运行)数据来源新浪日线前复权 6 只 A 股 + 官方内置 GOOG 样例适用市场A 股 · 美股样例可直接复现是(11 项清单与检查步骤可逐条执行)含交易成本是(0 / 5 / 20bp 成本敏感性对照)样本外验证是(98 组参数样本内 / 样本外对照)
样本内 89/98 赚钱样本外 0/98 赚钱
执行时点差 33.94pp默认 vs 收盘成交
前视信息 +28.70pp不会报错,只会更好看
基准起点差 70.46pp指标预热影响买入持有
Checklist

回测做完,Backtesting.py 的结论该用哪 11 项去查?

下表把 Backtesting.py 回测的可信度拆成可执行的检查项:每一项都写清「怎么查」,并给出本站实测的结果。前 6 项属于即使不写策略也能查的通用项,后 5 项需要你回到自己的策略代码。

检查项怎么查合格判据本站实测结果
未来函数 / 前视偏差指标在「截断到第 i 根」与「全序列」两种算法下是否一致两种算法逐根一致刻意用全序列信息后,收益「改善」28.70pp,且无任何报错
参数过拟合样本内选参 → 样本外复跑,比较名次与收益样本外仍为正、名次不塌陷样本内收益最高的一组 461.23%,样本外 -22.86%;98 组里样本外正收益 0 组
交易成本把佣金/印花税/过户费按你的口径重跑一遍结论在合理费率区间内不反转成本从 0 加到 20bp,收益 -64.44% → -78.22%,结论不变
执行时点假设分别在默认与 trade_on_close=True 下跑同一策略差异可解释且已声明同一策略 -69.16% 与 -35.22%,差 33.94pp
基准对比确认「买入持有」的起点与口径基准起点与策略起点口径一致并写明把均线窗口换成 200 日后,基准从 117.19% 变 187.65%
统计显著性看交易笔数、SQN、盈亏比,而不是只看胜率笔数足够且盈亏比 > 1样本里出现 3 笔交易就下结论的案例(买入持有 +71052.85%)
样本代表性同一套参数换 5~10 个标的各跑一遍不依赖单一标的6 只 A 股里 0 只跑赢买入持有,5 只亏损
样本挑选偏差检查是否只展示表现居前的标的所有样本结果都公开只贴宁德时代(+319.46%)就会掩盖其余 5 只的亏损
数据质量查重复日期、缺日、价格越界、复权口径体检项全部通过或已说明港股不复权序列出现 12 行收盘价越界
规则可成交性统计成交是否落在涨跌停/一字板/停牌日已过滤或已声明近似6 只样本有 4 笔买在涨停日、1 笔卖在跌停日
结论表述是否同时给出基准、成本、区间与局限四项齐全本文所有数字均标注区间、成本与其他假设
实测口径(全页适用):Windows + Python 3.11.9 + backtesting 0.6.6 + pandas 2.2.3;数据为新浪接口日线,区间 2018-01-02~2026-09-18(贵州茅台 600519 前复权为主),初始资金 100000,默认撮合 + exclusive_orders=True + finalize_trades=True,A 股成本用自定义 callable(佣金 2.5bp 双边 + 过户费 0.1bp 双边 + 卖出印花税 5bp)。复现脚本与原始输出已随本项目研究材料归档(lab/ 目录)。数字只代表该区间该标的,不构成投资建议。
Look-ahead

Backtesting.py 有哪些未来函数陷阱?为什么它不会提醒你?

先说明一个容易误解的事实:backtesting.py 0.6.6 没有内建的未来函数检测——全包源码检索没有 LookaheadError 之类的机制。前视偏差不会报错、不会警告,只会让结果变得更好看。

常见写法是否构成前视为什么怎么发现
指标用全序列统计量(如 (收盘 - 全序列均值) / 全序列标准差)是每根 bar 都用到了未来的均值与波动截断重算:两种算法结果不一致即命中
self.I() 里对整段数据做归一化或分位数是归一化基准来自未来同上;把全序列换成扩展窗口(expanding)再跑一次
信号与成交用同一根 bar 的收盘价(trade_on_close=True)风险点决策与成交发生在同一价格上,隐含「能按刚看到的收盘价成交」两种时点各跑一次,看差异是否可接受
在 next() 里访问 self.data.Close[-1] 做判断后立即下单否订单要等下一根 bar 才处理,属正常写法确认 trades 的 EntryTime 晚于信号日期
多时间框架用 resample 聚合,未设 label='right'是官方注释明确指出:默认 label='left' 会产生前视偏差对照官方 resample_apply 文档的等价写法
用「本期已知的未来数据」做筛选(如按年报公布前的净利润选股)是信息在现实中的可得时点晚于回测用到的时点为每个字段记录现实发布日期,再对齐到交易日
# 截断重算:同一指标在「全序列」与「截断到第 i 根」下必须一致
def assert_no_lookahead(close, build, tail=30):
    full = build(close)
    bad = []
    for i in range(len(close) - tail, len(close)):
        part = build(close.iloc[:i + 1])
        if abs(float(part[-1]) - float(full[i])) > 1e-9:
            bad.append(i)
    return bad   # 实测:全序列 z-score 会命中,扩展窗口版本不会
实测:前视偏差有多大。同一策略、同一标的,只把过滤指标从「扩展窗口 z-score」换成「全序列 z-score」,收益从 -93.91% 变成 -65.21%——「改善」28.70 个百分点,交易笔数从 107 变 118。这 28.70pp 全部来自未来信息,而不是策略变好了。这就是前视偏差最危险的地方:它让你的回测更好看,同时让你在实盘里失去它。
Overfitting

Backtesting.py 参数优化后样本外失效,怎么判断是选参失误还是策略失效?

这两个结论的处理方式完全不同:前者回去重选参数,后者应该放弃这套逻辑。判据是「样本外的整体分布」,而不是某一个参数组合的表现。

观测项样本内 2018-01-02~2022-12-30样本外 2023-01-03~2026-09-18说明
参数组合数98 组(n1 3~30、n2 15~75,约束 n1<n2)同一批 98 组网格大小一致,可比
正收益组合数89 / 980 / 98样本内「随便挑都赚」,样本外「怎么挑都亏」
收益最高的组合n1=30, n2=51 → +461.23%(17 笔)同一组 → -22.86%(19 笔)参数没变、代码没变
该组合在样本外的排名分位—98.0%它仍是样本外居前的一档,说明不是「挑错了」
样本内/外收益相关性Spearman 0.540 / Pearson 0.534有正相关,但不足以挑出赢家
样本外收益最高的组合n1=30, n2=57(样本内只有 +243.38%)仍为 -20.78%样本外挑选出的那一组也在亏
同期买入持有+148.50%-19.95%样本外标的自身也在跌,必须分开看
结论与判据:当「样本内多数组合赚钱、样本外几乎无组合赚钱」同时出现时,问题不在参数选择,而在这套逻辑本身已经失效——回到参数网格里继续找,只会找到下一个样本内的巧合。实操建议:①用默认 maximize='SQN' 或自定义评分,不要只按收益高低挑参(本次两者恰好选出同一组参数);②在样本外留出时间上不相邻的一段(walk-forward:滚动窗训练 + 紧跟其后的窗口验证);③把「样本外是否为正」当作策略能否继续研究的门槛,而不是把样本外当终点线。
Cost reality

Backtesting.py 的成本要设到多少,结论才不会被推翻?

成本的意义不是「让数字更保守」,而是找出结论的稳定区间:把费率从 0 一路加上去,看结论在什么位置翻转。下面是同一策略的 8 组对照(贵州茅台 600519)。

成本设置区间收益费用合计Sharpe是否推翻「跑输」结论
零成本-64.44%0-0.448不推翻(买入持有 +107.61%)
仅卖出印花税 5bp-67.00%5243-0.482不推翻
佣金 2.5bp 双边-66.97%5171-0.482不推翻
佣金 2.5bp + 印花税 5bp(A 股常用)-69.20%10119-0.515不推翻
佣金 5bp + 印花税 5bp-70.64%14875-0.536不推翻
佣金 10bp + 印花税 5bp-73.63%23468-0.587不推翻
佣金 20bp 对称-78.22%34545-0.678不推翻
佣金 20bp + 印花税 5bp-79.46%37907-0.707不推翻
怎么用这组数据:本例中成本让收益恶化 15.02 个百分点,但因为策略本来就大幅跑输,结论没有被翻转——这说明「成本敏感性测试」的价值在于知道结论的余量:如果某个策略在零成本下只跑赢基准 3 个百分点,那它在任何合理费率下都不成立。反过来,实测港股腾讯 00700 零成本下 +5.03% 对买入持有 +1.43%,计入费用后变成 -20.91%——这就是一个被成本直接推翻的案例。
Benchmark

Backtesting.py 的回测统计里哪些口径会悄悄改变结论?

库输出的 33 项统计都很方便,但每一项都有口径。下面是从 0.6.6 源码核验出的关键口径,以及两项实测会直接改变结论的差异。

字段0.6.6 的实现口径对结论的影响你要做什么
Buy & Hold Return [%]从「指标预热结束后的首根 bar」起算,不是数据首根实测差 70.46pp(200 日均线策略:187.65% vs 117.19%)换窗口后基准会变,报告中写明起点
Exposure Time [%]持有仓位的 bar 数 ÷ 总 bar 数横盘标的与趋势标的不可直接比较结合交易笔数一起看
年化交易日周末 bar 占比 > 17% 用 365,否则用 252;周/月/年线用 52/12/1加密货币(7×24)与 A 股(无周末)年化基数不同跨市场比较前统一口径
Sharpe / SortinoSharpe =(年化收益 − rf)/ 年化波动;rf 来自 Backtest 外部传入风险口径不统一时排序不可比记录 rf 取值
Profit Factor用逐笔收益率求和:盈利和 ÷ |亏损和|与大额单笔盈亏强相关,笔数少时会剧烈波动与交易笔数、SQN 一起看
Win Rate [%]盈利交易笔数 ÷ 总交易笔数高胜率可以同时是负期望——本次布林带反转平均胜率 40.4%、平均收益 -35.75%同时看 Avg. Trade 与盈亏比
SQN√n × mean(PnL) / std(PnL)对笔数敏感,是默认优化目标报 SQN 时同时报笔数
Alpha / Beta以买入持有为基准计算(非市场指数)「Alpha」不是行业常说的相对指数超额不要直接当作券商报告里的 Alpha
两项实测提醒:①基准起点会变——把均线窗口从 20 日改成 200 日,同一区间的「买入持有」从 117.19% 变成 187.65%,而策略本身只是均线慢了点;②finalize_trades 会改数字——未平仓交易是否计入统计,实测让收益从 -68.99% 变 -69.16%、笔数从 122 变 123。这些都是「同一份代码、不同的报告口径」,写结论时把开关状态一起写出来。
Significance

交易次数太少时,Backtesting.py 的结果还能信吗?

胜率、盈亏比、SQN 这些指标在笔数少时统计意义很弱。实测中有两类极端:一类 100+ 笔但胜率 34%,一类只有 3 笔却拿着「买入持有 +71052.85%」的对照。

案例(实测)交易笔数胜率收益 / 基准能不能下结论
双均线 · 贵州茅台12333.3%-69.16% / +107.61%笔数够,结论可信(策略确实无效)
布林带反转 · 6 只均值100(均值)40.4%-35.75%(均值)笔数够,且胜率不低却仍亏——看盈亏比
RSI(14) 反转 · 6 只均值9(均值)72.9%+71.47%(均值)笔数偏少,需看逐年分布
参数优化收益最高组 · 样本外19—-22.86%不宜据 19 笔否定整个策略族,需多标的验证
FractionalBacktest · 内置 BTCUSD3—-100.00% / +71052.85%不能下结论:笔数过少且基准量级失真
官方内置样例 · GOOG9453.2%+469.86% / +607.37%笔数够,但仍跑输基准
判据(实操):交易笔数少于约 30 笔时,胜率与盈亏比的置信区间会宽到无法判断;此时应改用「多标的 + 分段」(同一套参数在 5~10 个标的、3~5 个时间段各跑一遍)来看结论是否稳定。注意「胜率高 ≠ 期望为正」:实测布林带反转平均胜率 40.4%、平均收益 -35.75%,而 RSI 反转平均胜率 72.9%、平均收益 +71.47%,但两者都跑输买入持有(平均超额 -267.40pp 与 -157.45pp)。
Sample bias

Backtesting.py 的样本挑选偏差怎么影响结论?为什么它最难自查?

同一套策略、同一区间、同样的成本,只改变「你选哪些标的」和「你决定展示哪一个」,结论可以从「策略赚钱」翻转成「策略全线亏损」。

标的(同策略同区间实测)策略收益买入持有交易笔数单独展示会给人什么印象
宁德时代 300750+319.46%+725.90%113「策略翻三倍」——但实际跑输基准 406 个百分点
赛力斯 601127-22.33%+149.16%116「策略亏钱」
比亚迪 002594-33.15%+318.78%124「错过主升浪」
贵州茅台 600519-69.16%+107.61%123「在长牛股上亏 69%」
平安银行 000001-72.09%+11.27%120「策略几乎必然亏」
招商银行 600036-74.07%+77.17%109「策略在蓝筹上失效」
6 只整体0 只跑赢买入持有6 只全为正平均 119 笔「这套规则在 A 股上没有稳定优势」
边界(本站未实测的部分,只做方法说明):严格意义上的幸存者偏差需要已退市、被并购、长期停牌标的的完整历史,本站未取得这类数据,因此不出具「加入退市股后收益会下降多少」的数字。可用的替代做法是:①标的池用「期初可得」的名单而不是今天的成分股;②把样本扩大到跨行业、跨市值;③对同一结论要求「剔除任何一只标的后仍成立」。以上为方法建议,不是实测结论;具体实现以你所选数据源的官方文档为准。
Limits

回测与实盘之间还有哪些差距,Backtesting.py 管不到?

审计的目的不是让回测「像实盘」,而是把差距显式写出来。下面几项是库层面无法覆盖的,需要在结论里声明而不是靠参数调优弥补。

差距项库的行为(0.6.6 核实)对结论的影响处置建议
成交量与冲击成本没有成交量模型,订单数量不受当日成交量限制资金量越大偏差越大限制单笔占比并写明假设
部分成交订单按整数股一次性成交大额订单会高估可执行性拆分下单或降低单笔规模
涨跌停与停牌不做可成交性判断出现现实中无法成交的记录见 A 股规则适配页的过滤写法
多资产与组合资金单资产引擎;MultiBacktest 为各标的独立账户无法验证组合层面的分散与再平衡组合需求换支持组合的框架
做空与融券成本支持空头,但没有融券费用与借券可得性模型空头策略收益被高估用 callable 佣金近似并声明
实盘执行与心理不涉及,库本身不含券商下单通道回测优势可能被执行延迟与情绪波动吞掉把它当研究工具,而不是收益承诺
FAQ

回测审计常见问题

backtesting.py 会不会提示我用了未来函数?

不会。0.6.6 全包源码检索没有内建的未来函数检测机制;官方仅有的一处相关提示是 resample_apply 的注释——多时间框架聚合时要设 label='right',否则会产生前视偏差。因此前视必须靠自查(截断重算)来发现,具体行为以官方文档与你安装的版本为准。

样本外亏损,是不是说明参数选得不够多?

不是。本次实测中样本内 98 组参数有 89 组赚钱,样本外 0 组赚钱,而且样本内表现居前的那一组在样本外仍排前 2%——扩大网格只会找到更多样本内的巧合。判据是「样本外整体分布」:当几乎所有组合在样本外都亏,问题属于策略族失效,而不是选参失误。

回测收益很高但交易次数很少,怎么处理?

先把笔数说清楚,再决定要不要下结论。笔数少于约 30 笔时,胜率与盈亏比的置信区间会宽到无法判断,此时应改成「多标的 + 分段」验证。实测中一个 3 笔交易的案例挂着一份 +71052.85% 的买入持有对照,这种量级的组合不适合用来支持任何结论。

为什么我的回测在加上手续费后由盈转亏?

常见于两类策略:交易频繁(手续费按次累积)和优势本来就薄(零成本下只跑赢基准几个百分点)。实测港股腾讯 00700 双均线零成本 +5.03%、计入费用后 -20.91%,就是被成本直接推翻的案例。处理办法不是「调小手续费」,而是把费率设在你真实可能拿到的水平,再看结论是否仍成立。

胜率很高的策略为什么还是亏?

因为胜率不含盈亏比信息。实测布林带反转平均胜率 40.4%、平均收益 -35.75%;RSI 反转平均胜率 72.9%、平均收益 +71.47%——但两者都跑输各自的买入持有基准。看统计时建议把「胜率 + 平均每笔收益 + 盈亏比 + 笔数」四项一起看,字段口径以官方源码为准。

怎么用 Walk-forward 替代「一次性分段」?

做法是滚动窗口:用一段时间训练选参,用紧跟其后的下一段验证,然后整体前移再重复,最后把所有验证段的结果拼起来看。它比单次样本内/外切分更接近真实使用方式,也更难被单段行情带偏。本页的 98 组对照是一次性切分,属更简单但更弱的版本。

这套审计清单能保证策略赚钱吗?

不能,它的目标是尽早排除不可信的结论:能过审计只说明「没有发现明显自欺的迹象」。本文所有数字都来自 2018-01-02~2026-09-18 的有限样本,只用于说明方法差异,不预测任何标的的未来表现,也不构成投资建议。

本页结论回测最容易骗人的地方是静默的:用未来信息让结果好看 28.70pp 且不报错;成本从 0 到 20bp 使收益从 -64.44% 变 -78.22%。
回测局限幸存者偏差与冲击成本本站未实测,只给方法;11 项清单是自查工具,不保证能发现所有问题。
风险提示回测只反映历史区间的表现,不代表未来收益,也不构成投资建议;换区间、换标的或换成本假设,结论都可能改变。

下一步:审计完该回头核对哪些实现细节?

审计通过之后,再回头检查数据口径与 A 股规则适配;如果你还没跑通一次回测,请先完成首次运行。