Using vectorbt for Fast Vectorized Backtesting with Numba: A Complete Guide
vectorbt leverages Numba-accelerated NumPy operations to execute thousands of backtests in seconds without the overhead of event-driven loops.
The paperswithbacktest/awesome-systematic-trading repository catalogs high-performance tools for quantitative finance. Among these, vectorbt stands out as a Python library that eliminates the interpreter bottleneck of traditional backtesters by operating directly on pandas and NumPy data structures. This guide explains how to utilize its Numba integration for rapid strategy evaluation.
Why Vectorized Backtesting Outperforms Event-Driven Engines
Event-driven frameworks like Zipline or Backtrader process market data tick-by-tick using Python callbacks. In contrast, vectorbt works directly on pandas.DataFrame and Series objects containing raw price/volume series.
Because the heavy lifting stays inside NumPy's C-loops and is further JIT-compiled by Numba, the library evaluates strategies using pure array operations rather than per-tick Python logic. This design philosophy—"keep everything as arrays"—removes the dynamic dispatch overhead that slows event-driven toolkits.
According to the repository's documentation at README.md line 119, this approach enables practitioners to run high-frequency calculations on millions of rows without manual loop coding.
Core Architecture Components
The vectorbt framework organizes backtesting into five distinct layers, each optimized for vectorized execution.
Data Layer
The library ingests any pandas.DataFrame or Series of OHLCV data without requiring custom data objects. Native pandas indexing simplifies integration with sources like yfinance or AkShare, allowing seamless data ingestion into the pipeline.
Signal Engine
Entry and exit signals are generated by applying vectorized indicator functions that return boolean arrays. These signals can originate from built-in indicators such as SMA or RSI, or from user-defined functions wrapped with Numba's @njit decorator.
Portfolio Simulator
The simulation loop executes as a pure NumPy expression where position sizing, cash balance, and returns are computed through vectorized arithmetic. Numba JIT compilation (@njit) removes Python overhead entirely, delivering speeds comparable to pure C implementations while maintaining Python's readability.
Performance and Analytics
All metrics—including Sharpe ratio, CAGR, and drawdown—are derived from pre-computed returns series. Visualization utilities rely on matplotlib and plotly but operate on these arrays directly, ensuring the analytics layer never introduces computational bottlenecks.
Extensibility
Because the core operates exclusively on NumPy arrays, any function accepting NumPy inputs can be wrapped with @njit and integrated as a strategy component. This allows custom factor models and risk controls to execute at native machine-code speed.
Practical Implementation Guide
The following examples demonstrate the typical workflow: load data, create signals, run simulation, and analyze results. Each snippet references patterns found in the repository's strategy files such as static/strategies/volatility-risk-premium-effect.py and static/strategies/asset-growth-effect.py.
Simple SMA Crossover Strategy
This example implements a dual-moving-average crossover using vectorbt's built-in indicators:
import pandas as pd
import vectorbt as vbt
import yfinance as yf
# Load daily price data
price = yf.download("AAPL", start="2015-01-01", end="2024-12-31")["Close"]
# Generate moving average signals (fast 20-day, slow 50-day)
fast_ma = vbt.MA.run(price, window=20)
slow_ma = vbt.MA.run(price, window=50)
# Entry/exit signals (crossovers)
entries = fast_ma.ma_crossed_above(slow_ma.ma)
exits = fast_ma.ma_crossed_below(slow_ma.ma)
# Run portfolio simulator (vector-based, Numba-accelerated)
portfolio = vbt.Portfolio.from_signals(price, entries, exits, freq='1D')
# Inspect performance
print(portfolio.stats())
portfolio.plot().show()
The vbt.MA.run method executes fully vectorized calculations, while Portfolio.from_signals processes the backtest in a single NumPy expression automatically JIT-compiled by Numba.
Custom Numba-Accelerated Indicators
For proprietary factors, wrap custom logic with @njit to achieve C-speed execution:
import numpy as np
import vectorbt as vbt
from numba import njit
# Define user-custom factor with Numba compilation
@njit
def momentum_score(close: np.ndarray, lookback: int) -> np.ndarray:
out = np.empty_like(close)
for i in range(lookback, len(close)):
out[i] = close[i] - close[i - lookback]
out[:lookback] = np.nan
return out
# Wrap as vectorbt indicator
Momentum = vbt.IndicatorFactory(
class_name='Momentum',
input_names=['close'],
param_names=['lookback'],
output_names=['score']
).from_apply_func(momentum_score)
# Apply to price series
price = vbt.YFData.download('MSFT').close
mom = Momentum.run(price, lookback=30)
# Generate long-only signal when momentum > 0
entries = mom.score > 0
exits = mom.score <= 0
portfolio = vbt.Portfolio.from_signals(price, entries, exits)
portfolio.stats()
By decorating momentum_score with @njit, the rolling calculation compiles once and reuses optimized machine code for every backtest iteration.
Batch Parameter Optimization
Vectorized execution enables grid searches across thousands of parameter combinations without explicit Python loops:
import vectorbt as vbt
import yfinance as yf
price = yf.download('GLD', start='2000-01-01')['Close']
# Define parameter ranges
fast_ws = range(5, 31, 5) # 5,10,15,20,25,30
slow_ws = range(35, 101, 15) # 35,50,65,80,95
# Vectorized batch execution across all combinations
portfolios = vbt.Portfolio.from_signals(
price,
entries=vbt.MA.run(price, fast_ws).ma_crossed_above(vbt.MA.run(price, slow_ws).ma),
exits=vbt.MA.run(price, fast_ws).ma_crossed_below(vbt.MA.run(price, slow_ws).ma),
freq='1D'
)
# Extract Sharpe ratios for each (fast,slow) pair
sharpe = portfolios.sharpe_ratio()
print(sharpe)
This approach automatically broadcasts signal generation across the parameter grid, maintaining NumPy/Numbified performance even when testing dozens of configurations simultaneously.
Repository Resources and Strategy Migration
The paperswithbacktest/awesome-systematic-trading repository contains reference implementations suitable for vectorbt migration. Files such as static/strategies/volatility-risk-premium-effect.py, static/strategies/asset-growth-effect.py, and static/strategies/short-term-reversal-in-stocks.py demonstrate event-driven patterns that can be reimplemented using vectorbt's array-based approach.
The Chinese documentation at README_zh.md line 109 mirrors the English description, confirming vectorbt's suitability for high-frequency signal evaluation across both language communities. By converting these event-driven strategies to vectorized implementations, practitioners achieve orders-of-magnitude speed-ups while preserving analytical fidelity.
Summary
- vectorbt operates directly on pandas and NumPy arrays, eliminating the per-tick Python overhead found in event-driven backtesters like Zipline or Backtrader.
- Numba JIT compilation (
@njit) transforms user-defined indicator functions into optimized machine code, enabling C-speed execution of custom logic. - The Portfolio Simulator executes backtests as pure NumPy expressions, computing position sizing, cash balance, and returns through vectorized arithmetic.
- Batch testing across multiple parameter combinations requires no explicit Python loops, allowing thousands of strategy variations to be evaluated in seconds.
- The
paperswithbacktest/awesome-systematic-tradingrepository provides example strategies in files likestatic/strategies/short-term-reversal-in-stocks.pythat demonstrate ideal use cases for vectorbt's high-performance architecture.
Frequently Asked Questions
What makes vectorbt faster than event-driven backtesters like Backtrader or Zipline?
Event-driven frameworks process market data tick-by-tick using Python callbacks, which incurs heavy interpreter overhead. Vectorbt keeps all data as NumPy arrays and processes them in C-speed loops, optionally accelerated further by Numba's JIT compiler. As documented in the repository's README.md, this array-based approach eliminates the bottleneck of per-tick Python logic.
How does Numba JIT compilation improve backtesting performance?
Numba translates Python functions decorated with @njit into optimized machine code at runtime. When applied to indicator calculations in vectorbt, this removes Python's dynamic dispatch overhead and allows loops to run at native C speeds. This is particularly effective for high-frequency calculations like rolling-window statistics or matrix-based factor combinations.
Can I implement custom indicators with vectorbt?
Yes. The library's IndicatorFactory accepts any function that operates on NumPy arrays. By wrapping custom logic with @njit, users can create proprietary factors that execute at compiled speeds while integrating seamlessly with vectorbt's Portfolio.from_signals simulator. This extensibility applies to any function accepting NumPy inputs, including complex risk controls or multi-factor models.
Where can I find example strategies to implement with vectorbt?
The paperswithbacktest/awesome-systematic-trading repository includes reference implementations in static/strategies/volatility-risk-premium-effect.py, static/strategies/asset-growth-effect.py, and static/strategies/short-term-reversal-in-stocks.py. These files demonstrate systematic trading logic that can be migrated from event-driven patterns to vectorbt's vectorized framework for significant performance gains.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →