# Backtrader vs vectorbt: one test, reconciled to the cent

URL: https://fincodelabs.com/en/blog/backtrader-vs-vectorbt · Publisher: Fincode (Fincode Yazılım ve Teknoloji A.Ş.), Istanbul, Turkey · Category: Trading bots · Published: 2026-11-06 · Updated: 2026-11-06

Backtrader vs vectorbt on one synthetic schedule: why defaults differ, how to match them to the cent, and speed measured on stated hardware. No winner named.

**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](https://fincodelabs.com/en/blog/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.

**Final value from 100,000 of synthetic cash, fee 0.1% per side, run 5 November 2026 (an engine check, not a return)**

| 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`.

```python
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.5946
```

backtrader 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_signals` with no `price` filled at the signal bar's close in this run, a price your signal may have just used; see [backtesting pitfalls](https://fincodelabs.com/en/blog/backtesting-trading-strategy-pitfalls). To align them, shift vectorbt's signals one bar and pass `price=df.open`, or call `set_coc(True)` in backtrader, which its broker page calls cheating.
- **Size against cash.** backtrader's `PercentSizer` computes 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's `size=np.inf` solved the size at the fill price net of the fee, 980.73 units on the first buy. Log `notify_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's `fees=0.001`. A `CommInfoBase` subclass with the same number reads it as 0.001%, because `percabs` defaults 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=False` and `upon_long_conflict="ignore"`, drop a second entry and a same-bar conflict. With `accumulate=True` the second buy stays, but the exit sells only `size` units, 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.

**From each project's own pages, read 5 November 2026**

| 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.

**AMD Ryzen 9 5900HX, 16 logical processors, 15.4 GB RAM, Windows 11, Python 3.11.3, 6 November 2026, with other jobs running (medians of 3 backtrader runs and 5 vectorbt calls)**

| 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.

**VectorBT PRO page, 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](https://fincodelabs.com/en/blog/backtesting-python-libraries).

## 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.

1. **Does your logic depend on resting orders or on reacting to fills?** backtrader models them directly; free vectorbt needs callbacks.
2. **How many parameter sets will you run?** Thousands favor a vectorized engine; a handful may not.
3. **Should the same code run live?** backtrader lists live connections, but test the one you need, given its commit history.
4. **Can your product accept the license?** GPL-3.0, Commons Clause and PRO's non-commercial license bind differently, so have the terms reviewed.
5. **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](https://fincodelabs.com/en/blog/trading-bot-development) lists those parts. For integration work in general, see Fincode's [algorithmic trading](https://fincodelabs.com/en/services/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)](https://www.backtrader.com/docu/order/)
- [backtrader documentation: Broker (cash, checksubmit, set_coc)](https://www.backtrader.com/docu/broker/)
- [backtrader documentation: Commission schemes (percabs)](https://www.backtrader.com/docu/commission-schemes/commission-schemes/)
- [backtrader documentation: Features (order types, live trading)](https://www.backtrader.com/home/features/)
- [backtrader source: percents_sizer.py (PercentSizer)](https://github.com/mementum/backtrader/blob/master/backtrader/sizers/percents_sizer.py)
- [backtrader on GitHub (GPL-3.0, commit history)](https://github.com/mementum/backtrader)
- [vectorbt on GitHub: README (license, Rust extra, PRO features)](https://github.com/polakowo/vectorbt)
- [vectorbt source: _engine.py (engine selection, callbacks and Rust)](https://github.com/polakowo/vectorbt/blob/master/vectorbt/_engine.py)
- [vectorbt API reference: Portfolio (from_signals, from_order_func)](https://vectorbt.dev/api/portfolio/base/)
- [PyPI: vectorbt (release 1.2.0)](https://pypi.org/project/vectorbt/)
- [VectorBT PRO: official page (plans, private repository, footer links)](https://vectorbt.pro/)
- [VectorBT PRO: software license (non-commercial use, last updated 5 January 2025)](https://vectorbt.pro/terms/software-license/)
- [backtesting.py API reference: Backtest (trade_on_close, finalize_trades)](https://kernc.github.io/backtesting.py/doc/backtesting/backtesting.html)
- [Capital Markets Law No. 6362, Article 99/A (Turkish, consolidated text)](https://www.mevzuat.gov.tr/mevzuatmetin/1.5.6362.pdf)

> 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](https://fincodelabs.com/en/editorial-policy).

---

Published by Fincode (Fincode Yazılım ve Teknoloji A.Ş.), a software company in Istanbul, Turkey: https://fincodelabs.com/en/blog/backtrader-vs-vectorbt

Cite as: Fincode, "Backtrader vs vectorbt: one test, reconciled to the cent", updated November 6, 2026, https://fincodelabs.com/en/blog/backtrader-vs-vectorbt
