Features

Compute backends

Run quantitative workloads with NumPy, Numba, and native Rust backends

Native Rust simulators

Recently added

✅ Take your strategy directly to Rust. VBT's native simulators let the same strategy process a full price array or step through individual bars as they arrive. Strategy callbacks can inspect cash, positions, and orders during execution, keeping portfolio state within reach of your trading logic.

Note

The examples below share the strategy definition and require vectorbtpro-rust and ndarray = "0.16". See the Rust setup guide for installation.

Define a strategy with price and position conditions
use vectorbtpro_rust::error::VbtResult;
use vectorbtpro_rust::portfolio::enums::Order;
use vectorbtpro_rust::portfolio::simulator::{
    FnOrderStrategy, OrderContext, OrderStrategy,
};

fn buy_the_dip() -> impl OrderStrategy {
    FnOrderStrategy::new(|ctx: &OrderContext<'_, '_>| {
        let price = ctx.close(ctx.col());
        let position = ctx.position(ctx.col());
        let size = if price <= 100.0 && position == 0.0 { 
            1.0
        } else if price >= 110.0 && position > 0.0 {
            -position
        } else {
            return Ok(None);
        };
        Ok(Some(Order::builder().size(size).build()))
    })
}
Run a batch simulation
use ndarray::array;
use vectorbtpro_rust::portfolio::simulator::{OrderSimulator, SimulationConfig};

fn main() -> VbtResult<()> {
    let close = array![[100.0], [98.0], [105.0], [112.0]];
    let groups = array![1];
    let config = SimulationConfig::builder()
        .target_shape(close.dim())
        .group_lens(groups.view())
        .close(close.view())
        .init_cash(1000.0)
        .build()?;
    let simulator = OrderSimulator::builder().config(config).build()?;
    let output = simulator.run_single(&mut buy_the_dip())?;
    for order in &output.order_records {
        println!("row {}: {:.0} share at ${:.0}", order.idx, order.size, order.price);
    }
    Ok(())
}
row 0: 1 share at $100
row 3: 1 share at $112

Tutorial

Learn more in the From Python to Rust tutorial.

Rust backend

✅ Install the optional vectorbtpro-rust extension and compatible jitted calls can take the Rust fast lane automatically. VBT exposes the extension as vbt.rs, registers Rust kernels under jitted="rs", and falls back to the normal implementation, usually Numba, when Rust is unavailable or unsupported.

Keep your workflow, get the Rust lane
data = vbt.YFData.pull("BTC-USD", start="2024")

fast_ma = data.close.vbt.rolling_mean(20)  
slow_ma = data.close.vbt.rolling_mean(50)
entries = fast_ma.vbt.crossed_above(slow_ma)  
exits = fast_ma.vbt.crossed_below(slow_ma)

pf = vbt.Portfolio.from_signals(  
    data,
    entries=entries,
    exits=exits,
    sl_stop=0.05,
    tp_stop=0.15,
    fees=0.001,
)

Benchmarks

✅ VBT now ships with correctness-aware backend benchmarks for almost the entire codebase. The latest benchmark overview covers 748 functions, 768 variants, 1,976 kernels, 15,709 cases, and 59,600 benchmark runs across Numba, Rust, raw backend calls, AutoBench, and AutoBenchMixed. This makes performance inspectable: users can see which backend wins, at which input size, and whether parallel execution is worth it for their workload.

Benchmark one family from the terminal
python -m vectorbtpro.benchmarks.bench_engine_cli \
  --backend nb \
  --backend rs \
  --input-model 1d \
  --pattern returns
Run the same benchmark from Python
results = vbt.run_benchmarks(
    input_model="1d",
    backend_ids=("nb", "rs"),
    patterns=["returns"],
)
print(results)  
function,task_id,ndim,shape,nb_s,rs_s,speedup,auto_bench_backend,nb_elapsed_s,rs_elapsed_s
...
Benchmark median runtime ranks by backend and total element count Figure data (JSON)

Parallel Numba

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

Benchmark the rolling mean without and with parallelization
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

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

Run different implementations of the cumulative sum
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: float64
log_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—up to 1000x speedup!

Benchmark the rolling Sortino ratio
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)

Copyright © 20212026 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.