vectorbt is built on NumPy and Numba — it can run thousands of parameter combinations in the time other frameworks take for one. But its speed advantage only matters if you feed it real, clean historical data.
Loading OHLCV Data from Parquet
import polars as pl
import pandas as pd
df = pl.read_parquet("BTC_OHLCV_1h.parquet").sort("candle_time")
df_pd = df.to_pandas()
df_pd["candle_time"] = pd.to_datetime(df_pd["candle_time"], unit="ns")
df_pd = df_pd.set_index("candle_time")
close = df_pd["close"]
print(f"Loaded {len(close):,} candles")
SMA Crossover Backtest
import vectorbt as vbt
fast_ma = vbt.MA.run(close, window=20)
slow_ma = vbt.MA.run(close, window=50)
entries = fast_ma.ma_crossed_above(slow_ma)
exits = fast_ma.ma_crossed_below(slow_ma)
pf = vbt.Portfolio.from_signals(
close, entries, exits,
init_cash=10_000, fees=0.001, freq="1h"
)
print(pf.stats())
Parameter Optimization
fast_ma = vbt.MA.run(close, window=list(range(5,50,5)), short_name="fast")
slow_ma = vbt.MA.run(close, window=list(range(20,200,10)), short_name="slow")
pf = vbt.Portfolio.from_signals(
close, fast_ma.ma_crossed_above(slow_ma),
fast_ma.ma_crossed_below(slow_ma),
init_cash=10_000, fees=0.001, freq="1h"
)
sharpe = pf.sharpe_ratio()
best = sharpe.idxmax()
print(f"Best: fast={best[0]}, slow={best[1]}, Sharpe={sharpe.max():.2f}")
This runs hundreds of combinations in seconds on 6 years of hourly BTC data.
Interpreting Results Honestly
The best Sharpe from an optimization grid is always overfitted. Split your data: optimize on the first 4 years, validate on the remaining 2. If the best parameters from training perform reasonably on the holdout, you have something worth investigating further.
Ready-to-load OHLCV Parquet files for vectorbt — BTC, ETH, XRP, SOL and more, 6 timeframes, 6.5 years. → Browse the catalog ($19)
Summary
vectorbt’s strength is speed. Loading audited Parquet OHLCV data into vectorbt takes about 10 lines of code. The rest is strategy logic and honest evaluation of results.