A vectorbt alternative built on Rust

vectorbt is an excellent, mature library for vectorized backtesting in Python. If you are weighing it against Manifold-BT, the real question is how your strategy is shaped: vectorized and signal-heavy, or path-dependent with stops and fills. Here is the honest comparison.

Where the two are built differently

vectorbt expresses the whole backtest as operations over NumPy arrays, signals and execution alike. That is extremely fast for signal research and sweeping huge grids of simple rules, and it fits naturally if you already think in pandas.

Manifold-BT evaluates signals the same vectorized way, over the full series in Rust, then runs a sequential pass for fills and PnL. That keeps the speed, years of bars in sub-second, while handling path-dependent logic, stop-losses, take-profits, partial fills, inventory, that is cumbersome to vectorize. It also models execution realistically by default: market-impact slippage, funding, and next-bar fills.

Side by side

 Manifold-BTvectorbt
Core engineRust: vectorized signals + a sequential fills pass (hybrid)NumPy / Numba, vectorized
Speed353x faster than vectorbt under the same conditions (13ms vs 4.66s); several times faster still on GPU4.66s on the same benchmark
Path-dependent logicFirst-class (stops, partial fills, inventory)Awkward, often needs manual workarounds
Execution realismMarket-impact slippage, funding, partial fills, signal delaySimplified; costs as flat parameters
Parameter searchParallel sweeps, 2-D heatmaps, walk-forwardVectorized parameter grids
Live tradingNo, research onlyNo, research only
API styleExpression DSL, no pandas requiredPandas / NumPy-centric
Best forRealistic, path-dependent research at scale and speedQuick vectorized signals in a pandas-native API

The same sweep, with realism intact

Here is a parallel parameter sweep in Manifold-BT. Unlike a vectorized sweep, every combination is evaluated through the full execution engine, so stops and realistic fills apply to each one, with no loss of speed:

sweep.py
import manifoldbt as mbt
from manifoldbt.indicators import close, ema
from manifoldbt.helpers import time_range, Slippage, Interval

fast_p = mbt.param("fast", default=20)
slow_p = mbt.param("slow", default=50)
fast, slow = ema(close, fast_p), ema(close, slow_p)
strategy = (
    mbt.Strategy.create("ema")
    .signal("fast", fast)
    .signal("slow", slow)
    .size(mbt.when(fast > slow, 1.0, 0.0))
)

start, end = time_range("2021-01-01", "2026-01-01")
config = mbt.BacktestConfig(
    universe={"binance": ["BTCUSDT"]},
    time_range_start=start,
    time_range_end=end,
    bar_interval=Interval.hours(1),
    initial_capital=10_000,
    fees=mbt.FeeConfig.binance_perps(),
    slippage=Slippage.fixed_bps(2),
    warmup_bars=50,
)
store = mbt.ingest(provider="binance", symbol="BTCUSDT", symbol_id=1,
                   interval="1h", start="2021-01-01T00:00:00Z",
                   end="2026-01-01T00:00:00Z")

# Sweep the whole grid in parallel on Rust, with stops and realistic fills intact
sweep = mbt.run_sweep(
    strategy,
    param_grid={"fast": [10, 20, 30], "slow": [50, 100, 150]},
    config=config,
    store=store,
)
print(sweep.best("sharpe"))

Is the DSL heavier than vectorbt?

A fair worry: the pandas-native API of vectorbt is famously terse, so a dedicated DSL can sound like more to learn. In practice it is not heavier, just explicit. The same long-only crossover is a handful of lines in both, and the Manifold-BT version reads top to bottom, signal then sizing, with no pandas accessors or array plumbing to wire up.

vectorbt.py
import vectorbt as vbt

price = vbt.YFData.download("BTC-USD").get("Close")
fast = vbt.MA.run(price, 20)
slow = vbt.MA.run(price, 50)
entries = fast.ma_crossed_above(slow)
exits = fast.ma_crossed_below(slow)
pf = vbt.Portfolio.from_signals(price, entries, exits, fees=0.001)
manifold.py
import manifoldbt as mbt
from manifoldbt.indicators import close, ema

fast, slow = ema(close, 20), ema(close, 50)
strategy = (
    mbt.Strategy.create("ema_cross")
    .signal("fast", fast)
    .signal("slow", slow)
    .size(mbt.when(fast > slow, 1.0, 0.0))
)
result = mbt.run(strategy, config, store)

The gap opens the moment you add path-dependent logic. A stop-loss or a take-profit is one chained call in Manifold-BT, for example .stop_loss(pct=3.0), whereas a vectorized engine usually means leaving the fast path and hand-rolling the exit. You keep the concise style and still get realistic, path-dependent execution.

Which should you pick?

Choose Manifold-BT if you want the backtest to match the fill: realistic execution on by default, stops, partial fills, funding on crypto perpetuals, and size-aware slippage, path-dependent logic that never leaves the fast path, and large parameter sweeps and walk-forward that finish in seconds, including on GPU. For systematic research where execution and costs decide the edge, that is the tool. vectorbt stays a fair pick only if you already live in its pandas ecosystem and your work is purely vectorized, cross-sectional signal research.

Keep reading

Run your first backtest

Install Manifold-BT and reproduce the backtest above in seconds. The Rust core runs years of bars sub-second so you can sweep parameters instead of waiting.

$pip install manifoldbt