100% FreeNo Signup Required

backtrader vs vectorbt: do they agree?

Comparisons of these two libraries list features and quote speed. Neither answers the question that decides whether a backtest means anything. So the same strategy was run on the same 2,000 bars through both, and the results compared.

The answer

They agree exactly — both report $115,017.00 and 21 closed trades, a relative difference of 0.0e+0.

That agreement is manufactured. It required pinning three defaults, and the practical lesson is the opposite of reassuring: a backtest result is a property of the harness as much as of the strategy. On their own defaults, the same strategy returns 0.15% or 15.02%.

What actually causes the difference

Changing one default at a time, in backtrader, on identical bars. This decomposition matters: the end-to-end gap looks like an engine disagreement and is almost entirely a position-sizing setting.

ConfigurationSizeFillFinal equityReturnTrades
backtrader defaults1next bar open$100,147.580.15%21
Sizing pinned only100next bar open$114,758.0014.76%21
Sizing + fills pinned100signal bar close$115,017.0015.02%21
vectorbt, same pinned settings100signal bar close$115,017.0015.02%21
+14.61pp
from position sizing
+0.26pp
from fill timing
98%
of the gap is sizing

Same strategy and same bars, changing one default at a time. Sizing accounts for the overwhelming majority of the divergence and fill timing for a small remainder. Reporting the end-to-end gap as though it were a fill-timing result would be technically true and materially misleading, which is why it is decomposed rather than quoted whole.

Speed

Median of 5 runs on the same bars. Reported second on purpose: a fast number that disagrees with a slow one is not obviously the better number.

vectorbt

v1.1.0
3.5 ms
fastest of 5: 3.5 ms

backtrader

v1.9.78.123
446.2 ms
fastest of 5: 443.2 ms

backtrader is 127× slower here. That is architecture, not inefficiency: vectorbt operates on whole NumPy arrays while backtrader steps bar by bar, and stepping is what allows a strategy to react to its own fills. vectorbt is vectorised and backtrader is event-driven, so vectorbt is expected to be far faster. That is a property of the architecture, not a verdict: an event-driven loop can express order types a vectorised engine cannot, and a fast number that disagrees with a slow one is not obviously the better number.

What was pinned, and why

Signal definition

Both libraries receive the SAME entry/exit boolean arrays, computed once from a shared pandas rolling mean. Letting each compute its own indicator would make an indicator difference look like a backtest difference.

Fill timing

backtrader defaults to filling at the NEXT bar's open; vectorbt fills at the signal bar's close. backtrader is set to cheat-on-close so both fill at the same bar's close. Without this the two are structurally one bar apart and nothing else reconciles them.

Sizing

Fixed at 100 shares in both. backtrader's default stake is 1 share and vectorbt's default invests all available cash, so an unpinned comparison compares two different strategies.

Commission

Pinned to zero in both; each applies its model at a different point.

The harness

# Both libraries receive the SAME entry/exit arrays, computed once.
# Letting each compute its own indicator would make an indicator
# difference look like a backtest difference.

pf = vbt.Portfolio.from_signals(
    close=df["close"], entries=entries, exits=exits,
    size=100, size_type="amount",
    init_cash=100000, fees=0, freq="1D",
)

cerebro.broker.setcash(100000)
cerebro.broker.setcommission(commission=0)
cerebro.broker.set_coc(True)   # fill at the signal bar's close, like vectorbt
# ...and self.buy(size=100) rather than backtrader's default 1 share

Measured on 2,000 AAPL bars (2018-09-04 to 2026-08-19), SMA(20) crosses SMA(50), long only. Versions: vectorbt 1.1.0, backtrader 1.9.78.123, pandas 3.0.5, numpy 2.4.6. Regenerate with python -m pipeline.framework_bench.

Limitations

One strategy one symbol

This is a single long-only crossover on one symbol. It exercises fill timing, sizing and equity accounting; it does not exercise short selling, stops, multi-asset portfolios or intrabar order types, where the libraries diverge further.

Speed is secondary

vectorbt is vectorised and backtrader is event-driven, so vectorbt is expected to be far faster. That is a property of the architecture, not a verdict: an event-driven loop can express order types a vectorised engine cannot, and a fast number that disagrees with a slow one is not obviously the better number.

Trade count definition

The two libraries count trades differently at the boundary. vectorbt's trade table includes a position still open at the end of the sample; backtrader's notify_trade fires only on isclosed and never counts it. Comparing the raw counts makes an identical backtest look like a disagreement, which is why closed round-trips are compared here.

Defaults are the real finding

The configuration above is what makes the two comparable. Run either library with its own defaults and the results will NOT match, which is the practical lesson: a backtest result is a property of the harness as much as of the strategy.

Frequently asked questions

Do backtrader and vectorbt give the same result?+

On this test, yes — exactly. Both report $115,017.00 final equity and 21 closed trades on the same 2,000 bars, a relative difference of 0.0e+0. But that agreement is manufactured: it required pinning position sizing, fill timing and commission. Run either library on its own defaults and the results do not match.

Why do my backtest results differ between libraries?+

Almost always position sizing, not the engine. Measured here by changing one default at a time: sizing accounted for 14.61 percentage points of the gap (98%) and fill timing for 0.26pp. backtrader's default stake is 1 share; vectorbt's default invests available cash. Comparing those is comparing two different strategies, not two engines.

How much faster is vectorbt?+

About 127× on this test — 3.5 ms against 446 ms, median of 5 runs. That is a property of the architecture: vectorbt is vectorised over NumPy arrays while backtrader steps bar by bar. The gap widens with more bars and more parameter combinations, which is why vectorbt is the usual choice for parameter sweeps.

Which should I use?+

They are not really competing for the same job. vectorbt is far faster and suits vectorised signal research and large parameter sweeps. backtrader is event-driven, which lets a strategy react bar by bar and express order types, broker behaviour and portfolio logic that a vectorised engine cannot represent naturally. Speed is only a tiebreak once both can express the strategy you actually want to test.

Why did the trade counts look different at first?+

The two libraries count trades differently at the boundary. vectorbt's trade table includes a position still open at the end of the sample; backtrader's notify_trade fires only on isclosed and never counts it. Comparing the raw counts makes an identical backtest look like a disagreement, which is why closed round-trips are compared here.

What does "cheat on close" mean in backtrader?+

It makes an order fill at the close of the bar that generated the signal, instead of the open of the next bar. It is set with cerebro.broker.set_coc(True). It is used here only to match vectorbt's convention so the two are comparable — it is not a realistic execution assumption, since it fills at a price known only after the bar has closed.

Related

Generated 2026-08-20T20:01:53+00:00. This is a comparison of software behaviour on historical data. The strategy shown is a test fixture, not a recommendation, and its return over this sample is not a forecast of anything.