功能

真实回测

避免回测中的前视偏差、不可能成交和幸存者偏差

为什么回测看起来比实盘更好?可能订单在信号产生前就已成交,日线值在当天结束前就已出现,或交易成本吞噬了利润。VBT 让您控制订单时机、按信息实际可用时间对齐数据、模拟成本,并检查逐笔成交。下面介绍如何用这些工具避免前视偏差,在 Python 中构建更真实的回测。

Fill a limit order on the bar that created it, then on the next bar
data = vbt.BinanceData.pull(
    "BTCUSDT", start="2020-01-01", end="2025-01-01", timeframe="1h"
)
dip = data.close < data.close.vbt.rolling_mean(24)  
limit_price = data.close * 0.99
exits = dict(
    tp_stop=0.02,
    td_stop=24,
    time_delta_format="rows",
    stop_entry_price="fillprice",  
    fees=0.001,
)

same_bar = vbt.PF.from_signals(  
    data, entries=dip & (data.low <= limit_price), price=limit_price, **exits
)
next_bar = vbt.PF.from_signals(  
    data, entries=dip, price=limit_price, order_type="limit", limit_tif=2, **exits
)
metrics = ["total_trades", "win_rate", "total_return", "sharpe_ratio"]
stats = {"same bar": same_bar.stats(metrics), "next bar": next_bar.stats(metrics)}
pd.DataFrame(stats).astype(float).round(2)
                  same bar  next bar
Total Trades        802.00    663.00
Win Rate [%]         83.29     66.37
Total Return [%]  87489.57    -31.97
Sharpe Ratio          3.88      0.01

同根 K 线版本总能买到已经发生的低点,再卖出反弹,因此更快到达目标,交易也更频繁。它显示 87,000% 收益,而符合因果关系的版本亏损 32%。这种差距说明,花时间搜索参数之前应先检查订单时机。

符合因果关系的订单时机

决策只能使用作出决策时已知的信息,订单只能在该时刻或之后成交。vbt.PF.from_signals 的 price 参数指定每个订单在 K 线何处执行:

设置信号使用订单成交
price="close"(默认)本根收盘价在该收盘价
price="nextopen"本根收盘价在下一根开盘价
from_ago=1上一根 K 线在本根价格
order_type="limit" 配合 price="close"本根收盘价从下一根起,在限价触及时

限价单也遵循相同规则。开盘时下单,可在本根任何位置成交;收盘时下单,等待下一根;中间下单,则只能检查收盘价。止损对后续各根最高与最低价进行检查,limit_delay 或订单延迟会进一步推迟成交。全部选项见订单与执行,延迟见订单延迟,同根内同时存在止损和目标时的处理见真实止损成交。

止损成交与数据分辨率

只有提供最高价和最低价,才能检查它们。若仅传入收盘序列,而非数据对象或 open、high 和 low,所有止损都要等收盘价越过水平后才触发:

Test a 3% trailing stop on closes only and on full bars
btc = vbt.YFData.pull("BTC-USD", start="2020-01-01", end="2025-01-01")
entries = btc.close.vbt.crossed_above(btc.close.vbt.rolling_mean(20))
pfs = {
    "close only": vbt.PF.from_signals(btc.close, entries, tsl_stop=0.03),
    "OHLC": vbt.PF.from_signals(btc, entries, tsl_stop=0.03),
}
stop_metrics = ["total_trades", "win_rate", "total_return", "max_dd"]
pd.DataFrame({k: pf.stats(stop_metrics) for k, pf in pfs.items()}).astype(float).round(2)
                  close only    OHLC
Total Trades           90.00   96.00
Win Rate [%]           38.89   41.67
Total Return [%]      530.06  213.53
Max Drawdown [%]       33.67   21.40

只使用收盘价时,只要日内下跌后收盘仍在止损之上,交易便会继续持有,因此捕捉更多反弹,收益增加一倍以上。止损最终触发时,成交价是低于止损水平的收盘价,加深了回撤。两个结果都不等同于交易所止损单实际会产生的结果。

高时间周期对齐

更长时间周期的数据存在同样陷阱。日线值要到当天收盘才已知,但若用普通前向填充对齐到小时 K 线,就会将当天收盘价提前交给当天各小时:

Align a daily trend filter to hourly bars, naively and safely
daily_close = data.close.resample("1D").last()
daily_trend = daily_close > daily_close.vbt.rolling_mean(20)
naive_trend = daily_trend.reindex(data.index, method="ffill")  
safe_trend = daily_trend.vbt.realign_closing(data.index, freq="1h")  
safe_trend = safe_trend.fillna(False).astype(bool)

pfs = {
    "naive": vbt.PF.from_signals(data, naive_trend, ~naive_trend, fees=0.001),
    "safe": vbt.PF.from_signals(data, safe_trend, ~safe_trend, fees=0.001),
}
pd.DataFrame({k: pf.stats(metrics) for k, pf in pfs.items()}).astype(float).round(2)
                      naive    safe
Total Trades         107.00  107.00
Win Rate [%]          93.46   25.23
Total Return [%]  854502.66  315.56
Sharpe Ratio           4.41    0.87
2020 至 2024 年使用错误和安全对齐的日线趋势过滤器的比特币小时策略净值,对数刻度 图表数据(JSON)

同样 107 笔交易,错误对齐时胜率 93%,安全对齐时为 25%。多时间周期分析详细介绍对齐与重采样。

时点资产池

只在今天仍上市的股票上测试,策略永远不会持有倒闭或退出指数的公司。为避免幸存者偏差,应保留曾属于资产池的全部品种,在其非成员期间填充 NaN,让回测看到各日期当时实际存在的资产池。没有价格的 K 线,VBT 会忽略全部信号,包括离场,因此应在品种仍属于资产池的最后一根 K 线上平仓。数据流程展示如何构建成员资格掩码。

交易成本与可用资金

即使使用正确价格,策略仍可能花掉实际上没有的资金。VBT 处理订单时计入手续费和滑点,因此成本既影响最终收益,也影响可买入数量。使用比例佣金、固定手续费和滑点,检验策略在成本后是否仍有效。手续费、滑点和杠杆介绍这些设置及借贷成本。

多个资产竞争同一笔资金时,使用共享现金余额。数量限制、整股或手数取整及部分成交设置,可模拟符合账户能力的订单。成交、限制与拒单提供订单被限制、部分成交和忽略的示例。

回测与实盘一致性

实盘与回测结果不同时,应逐步比较。相同输入、设置,以及使用随机性时的相同随机种子,可复现 VBT 模拟并找到首个差异。

  • 数据: 检查实时 K 线与历史数据是否一致,包括最后一根仍在形成的 K 线。
  • 指标与信号: 在实时数据上重新计算,比较数值与时间戳。各平台的指标定义也可能不同,例如 RSI 的平滑和预热方式,因此应先逐根比较数值,再比较交易。
  • 订单: pf.orders.readable 列出每笔订单的信号、创建和成交 K 线,以及止损类型,可定位两次运行从哪里出现分歧。
  • 成本: 检查回测遗漏的手续费、滑点和价差,详见手续费、滑点和杠杆。

订单已到执行阶段,却未按要求成交时,可启用 log=True 并检查 pf.logs.readable。日志展示请求订单、执行结果及前后账户状态,帮助区分资金不足与数量限制导致的减单,无须从净值曲线猜测。

实时模拟在新数据上逐根继续回测,日内与逐笔回测则在各根内部用更细数据检验成交。

结果好得不寻常时

先在图上查看几笔交易。若恰在 K 线最低或最高价成交,检查订单是否在达到该价格前已存在。超过 80% 的胜率或远超市场的收益值得深入检查,但单凭其中任何一项都不能证明策略偷看未来。

版权所有 © 2021–2026 Oleg Polakow。保留所有权利。

本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。