backend / quant fintech / 03_backtesting_libraries.md

Backtesting libraries: vectorbt, Backtrader, QuantConnect

5 interview angles 4 min read source

Backtesting libraries: vectorbt, Backtrader, QuantConnect

Verified 2026-08. This landscape moves and the naming matters — knowing which of these is still maintained is itself a signal.

The options

Library Model Status 2026 Use when
vectorbt vectorized, NumPy/Numba actively developed; open-source core plus a commercial PRO tier screening thousands of parameter combinations fast
Backtrader event-driven, pure Python effectively unmaintained — no active bug fixing you inherit an existing Backtrader codebase
QuantConnect / LEAN event-driven, C# engine with a Python API actively developed you want research, backtest and live deployment on one platform with managed data
NautilusTrader event-driven, Rust core with a Python API actively developed you need high-fidelity, high-performance event-driven backtesting and live trading from the same code
Zipline-reloaded event-driven, community fork of Quantopian’s engine maintained fork you want the classic Zipline API
backtesting.py event-driven, small maintained quick single-asset studies where you want to read the whole engine

Saying “I’d use Backtrader” without qualification is the answer that dates you. The accurate version: it is fine to maintain, not what you would pick for something new.

vectorbt

Signals in, portfolio out, with the whole parameter grid evaluated at once.

import vectorbt as vbt

price = vbt.YFData.download('BTC-USD').get('Close')
fast = vbt.MA.run(price, window=range(5, 50), short_name='fast')
slow = vbt.MA.run(price, window=range(20, 200), short_name='slow')

entries = fast.ma_crossed_above(slow)
exits   = fast.ma_crossed_below(slow)

pf = vbt.Portfolio.from_signals(price, entries, exits, fees=0.001, slippage=0.001)
pf.total_return()          # a Series indexed by every (fast, slow) combination

The strength is exactly this: a multi-dimensional sweep in one call, with results indexed by parameter. The weakness is fidelity — position sizing, margin, partial fills and intrabar logic are limited. Treat its output as a map of where to look, not as a result.

Watch the heatmap trap. A parameter surface with one sharp peak is overfitting; a broad plateau is a real effect. Reporting the peak is how backtests stop working in production.

Backtrader

Event-driven, strategy-as-a-class, with a broker and a cerebro engine.

import backtrader as bt

class SmaCross(bt.Strategy):
    params = dict(fast=10, slow=30)

    def __init__(self):
        f = bt.ind.SMA(period=self.p.fast)
        s = bt.ind.SMA(period=self.p.slow)
        self.crossover = bt.ind.CrossOver(f, s)

    def next(self):
        if not self.position and self.crossover > 0:
            self.buy()
        elif self.position and self.crossover < 0:
            self.close()

cerebro = bt.Cerebro()
cerebro.addstrategy(SmaCross)
cerebro.broker.setcommission(commission=0.001)

Worth knowing because the mental model — next() called once per bar, indicators pre-computed and indexed relative to now — is the shape of every event-driven engine. Its known weaknesses are speed and multi-asset handling.

QuantConnect / LEAN

An open-source engine (LEAN, C#) plus a hosted platform providing data across equities, futures, forex, crypto and options, with the same code path from backtest to paper to live.

class MyAlgo(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2020, 1, 1)
        self.SetCash(100000)
        self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol

    def OnData(self, data):
        if not self.Portfolio.Invested:
            self.SetHoldings(self.spy, 1.0)

The argument for it is operational, not algorithmic: point-in-time data with survivorship handled, a corporate-actions-aware price series, and one code path to live trading. The argument against is lock-in — the strategy is written against their API, and the data is theirs.

How to answer the choice question

Do not lead with a library. Lead with the constraint:

  • Searching a wide parameter space cheaply -> vectorized (vectorbt, or your own NumPy/Polars).
  • Validating a shortlist with realistic fills and capital constraints -> event-driven.
  • Needing the same code to run live, on managed multi-asset data -> QuantConnect/LEAN or NautilusTrader.
  • Inheriting an existing system -> whatever it is, plus an honest note on the maintenance risk.

Interview angle

  • “Which backtesting framework do you use?” - answer with the two-stage workflow rather than one name: vectorized for the search, event-driven for validation. Then name the constraint that would pick a specific engine.
  • “Is Backtrader a good choice today?” - it is effectively unmaintained, so not for a new system. It remains a fine reference for the event-driven model and is common in existing codebases.
  • “What does vectorbt give up for its speed?” - execution realism. Position sizing, margin, partial fills and intrabar behaviour are simplified, so its numbers are optimistic relative to an event-driven run.
  • “Why would you use a hosted platform like QuantConnect?” - point-in-time, survivorship-corrected, corporate-action-adjusted multi-asset data, and one code path from backtest to live. Assembling that data yourself is the expensive part of this domain.
  • “Your parameter sweep found a great combination. What now?” - check whether it sits on a plateau or a spike, re-run it walk-forward, then re-run the survivors in an event-driven engine with doubled cost assumptions. A single peak in a heatmap is a fitted artefact.