vectorbt = game changer for parameter testing.
10x faster than backtrader.
vectorized operations instead of event-driven.
the speed problem #
traditional backtesting:
backtrader loops through each bar.
processes events sequentially.
slow for parameter optimization.
my experience:
256 parameter combinations × 10 windows = 2,560 backtests.
backtrader: 45 minutes.
too slow for rapid iteration.
learned about vectorbt from NexusFi discussions on high-performance backtesting frameworks for algorithmic trading.
vectorbt approach #
vectorized operations:
processes entire dataset at once using numpy.
parallel parameter testing.
same 2,560 backtests:
vectorbt: 3.5 minutes.
13x faster than backtrader.
installation and setup #
# install vectorbt
pip install vectorbt
# core imports
import vectorbt as vbt
import pandas as pd
import numpy as np
from datetime import datetime
# data handling
import yfinance as yf # or use your data provider
basic strategy implementation #
simple moving average crossover example:
import vectorbt as vbt
import pandas as pd
import numpy as np
class VectorBTStrategy:
def __init__(self, data, fast_window=20, slow_window=50):
"""
Initialize strategy with price data and parameters
Args:
data: DataFrame with OHLCV data
fast_window: Fast MA period
slow_window: Slow MA period
"""
self.data = data
self.fast_window = fast_window
self.slow_window = slow_window
self.close = data['Close']
def generate_signals(self):
"""Generate entry/exit signals using MA crossover"""
# Calculate moving averages (vectorized)
fast_ma = vbt.MA.run(self.close, window=self.fast_window, short_name='fast')
slow_ma = vbt.MA.run(self.close, window=self.slow_window, short_name='slow')
# Generate crossover signals
entries = fast_ma.ma_above(slow_ma, crossed=True)
exits = fast_ma.ma_below(slow_ma, crossed=True)
return entries, exits
def backtest(self, initial_cash=100000, commission=0.001):
"""Run backtest with generated signals"""
entries, exits = self.generate_signals()
# Create portfolio
portfolio = vbt.Portfolio.from_signals(
self.close,
entries=entries,
exits=exits,
init_cash=initial_cash,
fees=commission,
freq='1D'
)
return portfolio
def get_metrics(self, portfolio):
"""Extract key performance metrics"""
return {
'total_return': portfolio.total_return(),
'sharpe_ratio': portfolio.sharpe_ratio(),
'max_drawdown': portfolio.max_drawdown(),
'win_rate': portfolio.trades.win_rate(),
'total_trades': portfolio.trades.count(),
'avg_trade': portfolio.trades.pnl.mean(),
'profit_factor': portfolio.trades.profit_factor()
}
# Example usage
if __name__ == "__main__":
# Load data
data = pd.read_csv('es_futures_daily.csv', index_col='Date', parse_dates=True)
# Run single backtest
strategy = VectorBTStrategy(data, fast_window=20, slow_window=50)
portfolio = strategy.backtest()
metrics = strategy.get_metrics(portfolio)
print(f"Sharpe Ratio: {metrics['sharpe_ratio']:.2f}")
print(f"Total Return: {metrics['total_return']:.2%}")
print(f"Max Drawdown: {metrics['max_drawdown']:.2%}")
parameter optimization at scale #
this is where vectorbt shines:
import vectorbt as vbt
import pandas as pd
import numpy as np
import itertools
class ParameterOptimizer:
def __init__(self, data):
"""Initialize optimizer with price data"""
self.data = data
self.close = data['Close']
self.results = []
def optimize_ma_crossover(
self,
fast_windows=[10, 20, 30, 50],
slow_windows=[50, 100, 150, 200],
initial_cash=100000,
commission=0.001
):
"""
Optimize MA crossover strategy across parameter grid
Uses vectorbt's built-in optimization for speed
"""
# Create parameter combinations
fast_windows = np.array(fast_windows)
slow_windows = np.array(slow_windows)
# Run optimization using vectorbt's native optimization
# This executes all combinations in parallel (vectorized)
fast_ma = vbt.MA.run(
self.close,
window=fast_windows,
short_name='fast'
)
slow_ma = vbt.MA.run(
self.close,
window=slow_windows,
short_name='slow'
)
# Generate entry/exit for all parameter combinations
entries = fast_ma.ma_above(slow_ma, crossed=True)
exits = fast_ma.ma_below(slow_ma, crossed=True)
# Backtest all combinations at once (THIS IS THE MAGIC)
portfolio = vbt.Portfolio.from_signals(
self.close,
entries=entries,
exits=exits,
init_cash=initial_cash,
fees=commission,
freq='1D'
)
return portfolio
def extract_best_params(self, portfolio):
"""Find best performing parameter combination"""
# Get Sharpe ratio for all combinations
sharpe_ratios = portfolio.sharpe_ratio()
# Find maximum Sharpe
best_idx = sharpe_ratios.idxmax()
# Extract metrics for best combination
best_metrics = {
'fast_window': best_idx[0] if isinstance(best_idx, tuple) else best_idx,
'slow_window': best_idx[1] if isinstance(best_idx, tuple) else None,
'sharpe_ratio': sharpe_ratios.max(),
'total_return': portfolio.total_return().loc[best_idx],
'max_drawdown': portfolio.max_drawdown().loc[best_idx],
'win_rate': portfolio.trades.win_rate().loc[best_idx],
'total_trades': portfolio.trades.count().loc[best_idx]
}
return best_metrics
def walk_forward_optimization(
self,
in_sample_days=365,
out_sample_days=90,
step_days=90,
fast_windows=[10, 20, 30],
slow_windows=[50, 100, 150]
):
"""
Walk-forward optimization using vectorbt
Combines walk-forward validation with vectorized backtesting
"""
results = []
# Generate date windows
start_date = self.data.index[0]
end_date = self.data.index[-1]
current_start = start_date
window_num = 0
while current_start + pd.Timedelta(days=in_sample_days + out_sample_days) <= end_date:
in_sample_end = current_start + pd.Timedelta(days=in_sample_days)
out_sample_end = in_sample_end + pd.Timedelta(days=out_sample_days)
# Split data
in_sample_data = self.data.loc[current_start:in_sample_end]
out_sample_data = self.data.loc[in_sample_end:out_sample_end]
# Optimize on in-sample (vectorized)
in_optimizer = ParameterOptimizer(in_sample_data)
in_portfolio = in_optimizer.optimize_ma_crossover(
fast_windows=fast_windows,
slow_windows=slow_windows
)
best_params = in_optimizer.extract_best_params(in_portfolio)
# Test on out-of-sample with best params
out_strategy = VectorBTStrategy(
out_sample_data,
fast_window=best_params['fast_window'],
slow_window=best_params['slow_window']
)
out_portfolio = out_strategy.backtest()
out_metrics = out_strategy.get_metrics(out_portfolio)
# Store results
results.append({
'window': window_num,
'in_sample_period': (current_start, in_sample_end),
'out_sample_period': (in_sample_end, out_sample_end),
'best_fast': best_params['fast_window'],
'best_slow': best_params['slow_window'],
'in_sample_sharpe': best_params['sharpe_ratio'],
'out_sample_sharpe': out_metrics['sharpe_ratio'],
'out_sample_return': out_metrics['total_return'],
'degradation': (best_params['sharpe_ratio'] - out_metrics['sharpe_ratio']) / best_params['sharpe_ratio']
})
window_num += 1
current_start += pd.Timedelta(days=step_days)
return pd.DataFrame(results)
# Example: Run walk-forward optimization
if __name__ == "__main__":
# Load your data
data = pd.read_csv('es_futures_daily.csv', index_col='Date', parse_dates=True)
# Initialize optimizer
optimizer = ParameterOptimizer(data)
# Run walk-forward
results_df = optimizer.walk_forward_optimization(
in_sample_days=365,
out_sample_days=90,
step_days=90,
fast_windows=[10, 20, 30, 50],
slow_windows=[50, 100, 150, 200]
)
# Analyze results
print(f"Average out-of-sample Sharpe: {results_df['out_sample_sharpe'].mean():.2f}")
print(f"Average degradation: {results_df['degradation'].mean():.1%}")
print(f"Windows tested: {len(results_df)}")
performance comparison #
backtrader (event-driven):
4 fast × 4 slow = 16 combinations.
10 walk-forward windows.
total: 160 backtests.
time: 7 minutes.
vectorbt (vectorized):
same 160 backtests.
time: 32 seconds.
13x speedup.
my current workflow #
monthly strategy review:
load 2 years ES futures data.
run walk-forward optimization (4×4×10 = 160 backtests).
vectorbt: <1 minute.
backtrader would take 10+ minutes.
parameter stability check:
test if current parameters still in top quartile.
if degrading: research why, adjust or pause.
rapid iteration:
test new parameter ranges quickly.
experiment with different entry/exit logic.
this is how i stay profitable.
advanced features #
vectorbt has more power i’m exploring:
# Multi-asset portfolio optimization
symbols = ['ES', 'NQ', 'YM']
data = vbt.YFData.download(symbols, start='2023-01-01')
# Run strategy across all symbols simultaneously
portfolio = vbt.Portfolio.from_signals(
data.get('Close'),
entries=entries, # Same signals, applied to all symbols
exits=exits,
init_cash=100000,
fees=0.001,
group_by=True # Treat as single portfolio
)
# Position sizing with Kelly criterion
kelly_sizes = portfolio.get_kelly_sizes()
# Risk metrics
portfolio.get_risk_metrics()
gotchas and limitations #
memory usage:
vectorized = loads everything into RAM.
2 years daily data across 16 params = ~500MB.
not a problem on my server (128GB RAM).
complexity:
event-driven logic (backtrader) easier to understand.
vectorized requires thinking in arrays.
debugging:
harder to debug vectorized code.
trade-off for speed.
when to use vectorbt vs backtrader #
use vectorbt when:
parameter optimization (speed critical).
testing simple signal-based strategies.
walk-forward validation at scale.
use backtrader when:
complex event-driven logic (stop losses, position sizing).
strategy needs order management.
debugging new strategy logic.
i use both:
vectorbt: parameter optimization, validation.
backtrader: strategy development, complex logic.
resources #
vectorbt docs:
thorough examples, API reference.
my learning path:
started with backtrader (2023).
discovered vectorbt (2024).
now use vectorbt for 80% of backtesting.
NexusFi discussions:
learned optimization tricks from quant traders.
tonight (february 5, 2:51am) #
vectorbt = 13x faster than backtrader.
vectorized parameter testing.
same 160 backtests:
backtrader: 7 minutes.
vectorbt: 32 seconds.
rapid iteration enabled.
this is how i stay ahead.
2:51am wednesday. vectorbt backtesting framework. 13x faster than backtrader for parameter optimization (160 backtests: 32 seconds vs 7 minutes). vectorized operations process entire dataset at once using numpy. walk-forward optimization example: 4×4×10 = 160 backtests <1 minute. use vectorbt for parameter optimization, backtrader for complex event-driven logic. learned from NexusFi quant discussions. enables rapid monthly strategy review. this is sustainable algo trading edge.
-AK