Short answer: Backtrader vs vectorbt is a choice between an event-driven engine that runs your Python code bar by bar and a vectorized engine that simulates arrays in one call, and neither wins in general. Fed the same synthetic prices, the same fixed schedule and a 0.1% fee on 5 November 2026, their defaults ended 171.90 apart because backtrader fills at the next open and vectorbt at the same bar's close; once aligned, they matched to the cent. On a separate 100,000-bar schedule vectorbt ran several hundred times faster on one machine, while backtrader lists more order types than free vectorbt.
This guide runs both libraries on the same synthetic prices, fee and fixed schedule of buy and sell bars, so no trading rule is involved, then explains every gap. The final values only check the engines: they are not returns, and a backtest does not guarantee future results.
Versions used on 5 November 2026: Python 3.11.3, backtrader 1.9.78.123 (last commit 19 April 2023), vectorbt 1.2.0 (PyPI, 19 October 2026), backtesting.py 0.6.6, pandas 3.1.1 and NumPy 2.4.6. Licenses and maintenance are compared in backtesting Python libraries; this page adds the test.
Backtrader vs vectorbt: the same test, side by side
Once fill timing is aligned the engines produce identical fills and final values; every gap in this table comes from a default, not a calculation error.
| Case | backtrader | vectorbt | Gap | Cause |
|---|---|---|---|---|
| Each engine's default fill | 99,419.87 | 99,591.77 | 171.90 | Next open vs same-bar close |
| Both at next open | 99,419.87 | 99,419.87 | 0.00 | vectorbt shifted one bar, priced at the open |
| Both at same-bar close | 99,591.77 | 99,591.77 | 0.00 | backtrader with set_coc(True) |
| All cash on each buy, next open | 100,000.00; all 3 buys refused | 94,030.90 | 5,969.10 | Sizing against cash |
| Second buy while long (bars 5 and 8, sell 15) | 101,131.30 | 100,197.59 | 933.71 | backtrader adds; vectorbt ignores it |
| Buy and sell on one bar (buy 5 and 10, sell 10 and 15) | 100,178.49 | 100,197.59 | 19.11 | backtrader does both, paying two fees; vectorbt ignores both |
Reading the table (synthetic data, not returns). The base schedule is 100 units per order on 60 daily bars: buy at bars 5, 25 and 45, sell at bars 15 and 35, with the last buy left open. In the two aligned rows all five fills matched on date, size, price and fee to four decimals, and the final values differed by 0.0 and 2.9e-11, which is floating-point noise.
The code: one synthetic schedule in both engines
This script reproduces every row of the table and needs only pip install backtrader vectorbt.
import backtrader as bt, numpy as np, pandas as pd, vectorbt as vbt
rng = np.random.default_rng(20261105) # synthetic prices, no market data
n = 60
close = 100 * np.exp(np.cumsum(rng.normal(0, 0.02, n)))
open_ = np.r_[100, close[:-1]] * np.exp(rng.normal(0, 0.01, n)) # gap from the prior close
df = pd.DataFrame({"open": open_, "high": np.maximum(open_, close) * 1.002,
"low": np.minimum(open_, close) * 0.998, "close": close, "volume": 1e6},
index=pd.date_range("2026-01-05", periods=n, freq="B"))
CASH, FEE, SIZE = 100_000.0, 0.001, 100
BUY, SELL = {5, 25, 45}, {15, 35} # bar numbers fixed in advance, not signals
def backtrader_run(buy=BUY, sell=SELL, coc=False, sizer=None):
class Schedule(bt.Strategy):
def next(self):
i = len(self) - 1
if i in buy:
self.buy() if sizer else self.buy(size=SIZE)
if i in sell:
self.close()
c = bt.Cerebro(stdstats=False)
c.adddata(bt.feeds.PandasData(dataname=df, openinterest=None))
c.addstrategy(Schedule)
c.broker.setcash(CASH)
c.broker.setcommission(commission=FEE) # 0.1% of order value
c.broker.set_coc(coc) # True: fill at the signal bar's close
if sizer:
c.addsizer(bt.sizers.PercentSizer, percents=sizer)
c.run()
return round(c.broker.getvalue(), 4)
def vectorbt_run(buy=BUY, sell=SELL, next_open=False, size=SIZE):
flag = lambda bars: pd.Series(np.isin(np.arange(n), list(bars)), index=df.index)
entries, exits = flag(buy), flag(sell)
kw = dict(size=size, fees=FEE, init_cash=CASH, freq="1D")
if next_open: # shift one bar and price at the open
entries, exits = entries.shift(1, fill_value=False), exits.shift(1, fill_value=False)
kw["price"] = df.open
return round(float(vbt.Portfolio.from_signals(df.close, entries, exits, **kw).value().iloc[-1]), 4)
print("each default ", backtrader_run(), vectorbt_run())
print("next open ", backtrader_run(), vectorbt_run(next_open=True))
print("same-bar close", backtrader_run(coc=True), vectorbt_run())
print("all cash ", backtrader_run(sizer=100), vectorbt_run(next_open=True, size=np.inf))
print("second buy ", backtrader_run(buy={5, 8}, sell={15}), vectorbt_run(buy={5, 8}, sell={15}, next_open=True))
print("buy+sell same ", backtrader_run(buy={5, 10}, sell={10, 15}), vectorbt_run(buy={5, 10}, sell={10, 15}, next_open=True))Output, unchanged across repeated runs:
each default 99419.8674 99591.7663
next open 99419.8674 99419.8674
same-bar close 99591.7663 99591.7663
all cash 100000.0 94030.8992
second buy 101131.3 100197.5946
buy+sell same 100178.4865 100197.5946backtrader ran here on pandas 3.1.1 and NumPy 2.4.6 with no warnings, which covers only this code path.
Why backtrader and vectorbt results differ
Fill timing, sizing against cash and repeated signals explain every gap in the table; starting values, fee units and the open position cause gaps when left unset.
- Fill timing. backtrader's order documentation says a market order in backtesting takes the next bar's open. vectorbt's
from_signalswith nopricefilled at the signal bar's close in this run, a price your signal may have just used; see backtesting pitfalls. To align them, shift vectorbt's signals one bar and passprice=df.open, or callset_coc(True)in backtrader, which its broker page calls cheating. - Size against cash. backtrader's
PercentSizercomputes size from cash and the signal bar's close. At 100%, the broker's submission check priced each order at that close, so the fee alone caused the refusal: all three buys ended with status Margin whatever the next open;set_coc(True)changed nothing. No exception was raised and the run finished at 100,000.00, easy to misread as a flat result. vectorbt'ssize=np.infsolved the size at the fill price net of the fee, 980.73 units on the first buy. Lognotify_order, and pass both engines the same computed sizes. - Starting defaults. vectorbt 1.2.0's settings start with cash of 100, size at all available cash and long only. backtrader's broker starts with 10,000, and
self.buy()with no sizer bought 1 unit. Set these explicitly. - Fee numbers.
broker.setcommission(commission=0.001)means 0.1%, matching vectorbt'sfees=0.001. ACommInfoBasesubclass with the same number reads it as 0.001%, becausepercabsdefaults to False there. Here that cut the first order's fee from 10.19 to 0.10 and lifted the final value to 99,471.64. - Repeated signals. backtrader acts on every call. vectorbt's defaults,
accumulate=Falseandupon_long_conflict="ignore", drop a second entry and a same-bar conflict. Withaccumulate=Truethe second buy stays, but the exit sells onlysizeunits, leaving 100 open. - The open position. Neither engine closes it. Final value marks the last 100 units at the last close with no exit fee, which flatters any test that ends in a position.
Event-driven vs vectorized: what each can express
backtrader's order and callback model reaches further in the free version, while vectorbt expresses many parameter sets in one call; a fixed schedule exercises neither.
| Need | backtrader | vectorbt 1.2.0 |
|---|---|---|
| Order types | Market, Limit, Stop, StopLimit, StopTrail, StopTrailLimit, OCO, Bracket, MarketOnClose | from_signals with entries, exits and stop parameters; the README presents limit orders, leverage and contract multipliers as PRO features |
| Custom logic per bar | next() in plain Python, plus order and trade notifications | from_order_func callbacks, which the API page calls event-driven; the source says the Rust engine does not cover callback functions |
| Many parameter sets | One run per set | One column per set, in one call |
| Live trading | Features page lists Interactive Brokers, Oanda v1 and VisualChart, plus third-party packages; none tested here | README does not mention it |
Is vectorbt faster than backtrader? Measured on one machine
Yes for this shape of test, by a factor of several hundred once compiled; backtrader's Python loop runs once per bar, and vectorbt's compiled loop does not return to Python.
| Test | backtrader | vectorbt, Numba | vectorbt, Rust |
|---|---|---|---|
| 100,000 bars, 998 orders | 13.0 s | 0.021 s | 0.016 s |
| 200 schedules on 5,000 bars each | 146 s in total | 0.51 s | 0.64 s |
Numba's first call in a process also compiles, 34.4 s here; the Rust engine ships precompiled. Final values agreed within 2.5e-9 on the single run and within 3.9e-9 across the 200 schedules. backtrader's three runs ranged from 12.2 to 20.4 s, so read these as orders of magnitude; this workload has no indicators or callbacks. With the rust extra installed, the default engine setting, auto, picks Rust where it can, so name the engine when timing.
vectorbt vs VectorBT PRO: price and what the pages say
VectorBT PRO is a separate paid product, not tested here for lack of access, so this rests only on its official pages, read 6 November 2026.
| Plan | Listed price |
|---|---|
| Monthly | From $25 a month |
| 12 months | From $240 (shown against $300) |
| Lifetime | From $500, paid once |
Each plan includes a private source repository. The footer links a software license, last updated 5 January 2025, that allows only private or internal non-commercial use; commercial use, including paid hosting, consulting or support, needs a separate written agreement. The open-source README describes PRO as adding parallelization, more data integrations, portfolio optimization, limit orders, leverage and futures with contract multipliers. The open-source Apache 2.0 with Commons Clause license is covered in the library comparison.
Backtesting.py vs backtrader vs vectorbt on this schedule
backtesting.py 0.6.6 reached the same numbers: 99,419.8674 with its default next-open fill and 99,591.7663 with trade_on_close=True.
Its visible difference is the open position: with finalize_trades=True it closes that position on the last bar, at that bar's open with the fee, ending at 99,169.01. Three engines agreeing on arithmetic says nothing about whether any rule has value.
Neither is a bot: how to decide and what going live needs
Answer these questions, and treat either library as a research tool until the live parts exist.
- Does your logic depend on resting orders or on reacting to fills? backtrader models them directly; free vectorbt needs callbacks.
- How many parameter sets will you run? Thousands favor a vectorized engine; a handful may not.
- Should the same code run live? backtrader lists live connections, but test the one you need, given its commit history.
- Can your product accept the license? GPL-3.0, Commons Clause and PRO's non-commercial license bind differently, so have the terms reviewed.
- Who maintains it when something breaks? A library without commits since 2023 may become your fork to keep.
Going live still needs venue connectivity, order tracking and reconciliation, restart handling, risk limits outside the strategy code, monitoring and safe key handling. Trading bot development lists those parts. For integration work in general, see Fincode's algorithmic trading page; naming Interactive Brokers, Oanda or VisualChart here does not mean Fincode integrates them or has built on them. Which venue you may connect depends on your jurisdiction and its terms. For readers in Turkey, Article 99/A of Capital Markets Law No. 6362 treats activity by foreign crypto-asset platforms directed at Turkish residents as unauthorized. Automated trading can lose money.
Frequently asked questions
Why do backtrader and vectorbt give different results?
Mostly fill timing: backtrader fills market orders at the next open and vectorbt's from_signals defaults to the same bar's close. In a synthetic test on 5 November 2026, aligning that removed the whole 171.90 gap.
Is vectorbt faster than backtrader?
In this test yes, by several hundred times after compilation: 0.021 s with Numba against 13.0 s for 100,000 bars on the stated machine. That is one workload with no indicators, so measure your own.
How much does VectorBT PRO cost?
Its page, read 6 November 2026, lists from $25 a month, from $240 for 12 months and from $500 for lifetime access, under a license that allows only non-commercial use.
Is backtrader still maintained?
Its last commit and PyPI release date from 19 April 2023, checked 6 November 2026, and the repository is not archived. It still ran the test here on pandas 3.1.1 and NumPy 2.4.6.
Can I trade live with backtrader or vectorbt?
backtrader's features page lists live connections, none tested here. vectorbt's README does not mention live trading. Either way, you still need risk limits, reconciliation and monitoring.
Sources
- backtrader documentation: Orders (market order fill, Margin status) (opens in a new tab)
- backtrader documentation: Broker (cash, checksubmit, set_coc) (opens in a new tab)
- backtrader documentation: Commission schemes (percabs) (opens in a new tab)
- backtrader documentation: Features (order types, live trading) (opens in a new tab)
- backtrader source: percents_sizer.py (PercentSizer) (opens in a new tab)
- backtrader on GitHub (GPL-3.0, commit history) (opens in a new tab)
- vectorbt on GitHub: README (license, Rust extra, PRO features) (opens in a new tab)
- vectorbt source: _engine.py (engine selection, callbacks and Rust) (opens in a new tab)
- vectorbt API reference: Portfolio (from_signals, from_order_func) (opens in a new tab)
- PyPI: vectorbt (release 1.2.0) (opens in a new tab)
- VectorBT PRO: official page (plans, private repository, footer links) (opens in a new tab)
- VectorBT PRO: software license (non-commercial use, last updated 5 January 2025) (opens in a new tab)
- backtesting.py API reference: Backtest (trade_on_close, finalize_trades) (opens in a new tab)
- Capital Markets Law No. 6362, Article 99/A (Turkish, consolidated text) (opens in a new tab)
This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the editorial policy.