Features
Compute backends
Run kernels with Numba, parallel Numba, Rust, or NumPy and choose the backend per call
Why is VBT fast, and how do you control what runs where? VBT PRO moves heavy backtesting and analysis operations into Numba-compiled functions. Supported functions can also run in parallel, use the optional Rust extension, or use a NumPy implementation you register. Choose a backend per call or globally while keeping the same Python research workflow.
Faster calculations from pandas
You can start with a pandas Series or DataFrame and call VBT's rolling statistics, indicators, and portfolio simulators without writing a compiled function yourself. VBT passes numerical arrays to the calculation and wraps results with their labels. Your dates, symbols, and parameter combinations remain available for analysis.
For custom logic, Numba lets you write numerical loops in Python and compile them. This is useful for a signal that depends on an open position or an indicator that updates its state at each bar. Your compiled functions work with NumPy arrays, while data preparation and reporting can stay in pandas. See custom indicators and event-driven backtesting for examples.
Compare NumPy, Numba, and Rust
import timeit
@vbt.register_jitted(task_id_or_func=vbt.nb.rolling_mean_nb)
def rolling_mean_np(arr, window, minp=None):
csum = np.cumsum(arr, axis=0)
out = np.full(arr.shape, np.nan)
out[window - 1] = csum[window - 1]
out[window:] = csum[window:] - csum[:-window]
return out / window
np.random.seed(42)
values = 100 + np.random.normal(0, 0.01, size=(100_000, 500)).cumsum(axis=0)
layouts = {
"row-major": pd.DataFrame(np.ascontiguousarray(values)),
"column-major": pd.DataFrame(np.asfortranarray(values)),
}
backends = {
"pandas": None,
"NumPy": "np",
"Numba": "nb",
"Numba parallel": dict(jitter="nb", parallel=True),
"Rust": "rs",
"Rust parallel": dict(jitter="rs", parallel=True),
}
def ms(func):
return min(timeit.repeat(func, number=3, repeat=5)) / 3 * 1000
timings = {}
for layout, prices in layouts.items():
for backend, jitted in backends.items():
if jitted is None:
func = lambda: prices.rolling(50).mean()
else:
func = lambda: prices.vbt.rolling_mean(50, jitted=jitted)
timings[(backend, layout)] = ms(func)
pd.Series(timings).unstack().loc[list(backends), list(layouts)].round(0).astype(int) row-major column-major
pandas 693 360
NumPy 489 204
Numba 561 163
Numba parallel 379 32
Rust 87 145
Rust parallel 71 67The table shows milliseconds per call on an Apple M3, and every backend returned the same values. The fastest run took 32 ms against 360 ms for pandas on the same array, but the winner changed with the layout: parallel Numba on column-major data and parallel Rust on row-major data. Rust's rolling mean walks the array row by row and first copies column-major input into row order, while Numba's runs down each column and gains the most when columns are contiguous. That is why VBT lets you choose a backend per call, set one globally, or let measured timings choose.
Which backend fits your workload?
| Your workload | What to try |
|---|---|
| Backtests with your own Python trading rules | Numba compiles numerical callbacks and simulation loops. |
| Rolling calculations across many symbols or parameter combinations | Parallel Numba or Rust can spread supported calculations across CPU cores. |
| Repeated calls to supported built-in functions | Rust runs native code without waiting for Numba to compile that function. |
| An existing NumPy implementation you want to reuse | Register it alongside the built-in implementation and select it with jitted. |
| A research pipeline with different calculation shapes | Measure the important calls and use per-call choices or AutoBench. |
Columns can represent both instruments and strategy configurations. Simulations parallelize independent columns or groups, while processing each strategy's bars in order. For distributing whole backtests across workers or splitting large arrays into chunks, see parallel execution and caching.
Numba compilation
Most numerical functions in VBT are compiled with Numba, from rolling statistics and signal
generators to the simulators behind vbt.PF.from_signals. The simulators are compiled loops over
bars rather than whole-array operations, which is why path-dependent rules such as stops and
position sizing from the current balance run at compiled speed. Compiled functions release the GIL,
so threads can run them side by side, and cache-enabled built-in functions store compiled code on
disk for reuse in later Python sessions. Changing data or parameter values does not itself require
recompilation when the argument types stay the same, making repeated research runs a good fit.
The first call in a fresh environment still compiles, which can take seconds. Your own callbacks,
such as a signal function for from_signals, are compiled too, and the simulator is compiled again
for each new callback, which took about 20 seconds on the machine that built this page. Redefining
the callback, for example by rerunning the notebook cell that defines it, counts as new, so keep
callbacks in their own cell or module. The
staticized option turns a simulation
that calls them into a function that can be cached on disk as well, so it survives restarts and new
worker processes. This caches the compiled simulator, so you can still run it on new market data and
parameters.
For debugging, jitted=False runs the underlying Python function, as the cumulative-sum example
below shows. You can also disable Numba before importing VBT through the numba.disable
configuration setting. This controls Numba compilation, while backend selection controls whether a
call uses Rust.
Backends and jitters
A jitted function in VBT is a registry entry with one or more implementations: nb for Numba, rs
for the optional Rust extension, np for vectorized NumPy, or any jitter you register. The jitted
argument picks one per call and passes options such as parallel=True, as the Jitting
and Parallel Numba highlights below show. Parallel versions process columns on
several cores, which pays off on wide arrays and costs a little on narrow ones. Rolling return
metrics, such as the rolling Sharpe and Sortino ratios, update running sums in one pass instead of
recomputing each window, as the Hyperfast rolling metrics highlight
below shows.
Backend selection
With the Rust extension installed, compatible calls go to Rust by default and fall back to Numba when Rust cannot handle the inputs. In the example above, the default call took 86 ms on row-major data and 147 ms on column-major data, the Rust timings. Three settings change that:
- Per call:
jitted="nb",jitted="rs", or a dictionary with options. - By module: resolution overrides set a backend for every function whose path matches, such as
everything under
vectorbtpro.returns. - By measurement: AutoBench reads a benchmark cache built on your machine and sends each call to an eligible backend using those measurements and, where needed, the input shape.
The Rust backend highlight on the Rust engine page shows each option. Backend selection happens inside the calculation, so you can keep the same pandas inputs and result handling when switching supported implementations.
Find your fastest backend
Compare backends on your own arrays with VBT's benchmark tools, then use the timings to select a backend for each calculation. Keep the same Python inputs and labeled results.
✅ Most Numba-compiled functions have been rewritten to process columns in parallel using
automatic parallelization with @jit.
You can enable this with a single command. This approach is best for lightweight functions that are
applied to wide arrays.
np.random.seed(42)
df = pd.DataFrame(np.random.uniform(size=(1000, 1000)))
%timeit df.rolling(10).mean() 45.6 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)%timeit df.vbt.rolling_mean(10) 5.33 ms ± 302 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)%timeit df.vbt.rolling_mean(10, jitted=dict(parallel=True)) 1.82 ms ± 5.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)✅ Jitting stands for just-in-time compiling. In the VBT universe, however, jitting simply means accelerating. While Numba remains the primary jitter, VBT now allows you to implement custom jitter classes, such as those for vectorized NumPy or even JAX with GPU support. Every jitted function is registered globally, so you can switch between different implementations or even disable jitting entirely with a single command.
data = vbt.YFData.pull("BTC-USD", start="7 days ago")
log_returns = np.log1p(data.close.pct_change())
log_returns.vbt.cumsum() Date
2023-01-31 00:00:00+00:00 0.000000
2023-02-01 00:00:00+00:00 0.024946
2023-02-02 00:00:00+00:00 0.014271
2023-02-03 00:00:00+00:00 0.013310
2023-02-04 00:00:00+00:00 0.008288
2023-02-05 00:00:00+00:00 -0.007967
2023-02-06 00:00:00+00:00 -0.010087
Freq: D, Name: Close, dtype: float64log_returns.vbt.cumsum(jitted=False) Date
2023-01-31 00:00:00+00:00 0.000000
2023-02-01 00:00:00+00:00 0.024946
2023-02-02 00:00:00+00:00 0.014271
2023-02-03 00:00:00+00:00 0.013310
2023-02-04 00:00:00+00:00 0.008288
2023-02-05 00:00:00+00:00 -0.007967
2023-02-06 00:00:00+00:00 -0.010087
Freq: D, Name: Close, dtype: float64@vbt.register_jitted(task_id_or_func=vbt.nb.nancumsum_nb)
def nancumsum_np(arr):
return np.nancumsum(arr, axis=0)
log_returns.vbt.cumsum(jitted="np") Date
2023-01-31 00:00:00+00:00 0.000000
2023-02-01 00:00:00+00:00 0.024946
2023-02-02 00:00:00+00:00 0.014271
2023-02-03 00:00:00+00:00 0.013310
2023-02-04 00:00:00+00:00 0.008288
2023-02-05 00:00:00+00:00 -0.007967
2023-02-06 00:00:00+00:00 -0.010087
Freq: D, Name: Close, dtype: float64✅ Rolling metrics based on returns have been optimized for maximum performance, with up to 1000x speedup!
import quantstats as qs
index = vbt.date_range("2020", periods=100000, freq="1min")
np.random.seed(42)
returns = pd.Series(np.random.normal(0, 0.001, size=len(index)), index=index)
%timeit qs.stats.rolling_sortino(returns, rolling_period=10) 2.79 s ± 24.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)%timeit returns.vbt.returns.rolling_sortino_ratio(window=10) 8.12 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)Related pages
- Rust engineRun simulations on the native Rust engine and write strategy callbacks in Rust
- BenchmarksCompare simulators on the same orders and measure each backend on your own machine
- Parallel execution and cachingChunk work across threads, processes, or Ray, cache results, and keep memory bounded
- Research toolkit › Time-series operationsRun compiled rolling windows, reductions, and date logic on labeled pandas data
Copyright © 2021–2026 Oleg Polakow. All rights reserved.
Site content and documentation are provided for using and evaluating VectorBT PRO and for educational purposes. Any other use, including building or supporting competing products or services, requires prior written consent.