Learn-skills.dev vectorbt
High-performance vectorized backtesting with parameter optimization, portfolio simulation, and rich performance metrics
git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agiprolabs/claude-trading-skills/vectorbt" ~/.claude/skills/neversight-learn-skills-dev-vectorbt && rm -rf "$T"
data/skills-md/agiprolabs/claude-trading-skills/vectorbt/SKILL.mdVectorized Backtesting with vectorbt
Overview
vectorbt is a Python library for vectorized backtesting — running strategy simulations using NumPy/pandas array operations instead of bar-by-bar loops. This makes it 100–1000x faster than event-driven frameworks (backtrader, zipline), enabling parameter optimization across thousands of combinations in seconds.
Key strengths:
- Blazing speed via NumPy vectorization
- Built-in parameter grid search and optimization
- 50+ built-in performance metrics (Sharpe, Sortino, Calmar, max drawdown, profit factor)
- Rich plotting (equity curves, drawdowns, trade markers, heatmaps)
- Native pandas integration — your data stays in DataFrames throughout
Installation
uv pip install vectorbt pandas numpy
vectorbt pulls in pandas, NumPy, and Plotly automatically. For technical indicators, also install pandas-ta:
uv pip install vectorbt pandas-ta
Core Concepts
1. Signals — Boolean Entry/Exit Arrays
Strategies in vectorbt are expressed as boolean pandas Series (or arrays) indicating where to enter and exit positions:
import vectorbt as vbt import pandas as pd # Entry: buy when fast EMA crosses above slow EMA entries = fast_ema > slow_ema # Exit: sell when fast EMA crosses below slow EMA exits = fast_ema < slow_ema
vectorbt resolves conflicting signals automatically (you can't enter while already in a position).
2. Portfolio — The Backtesting Engine
vbt.Portfolio.from_signals() is the primary backtesting function. It takes price data and entry/exit signals, simulates trades, and computes performance:
pf = vbt.Portfolio.from_signals( close=close_prices, entries=entries, exits=exits, init_cash=10_000, fees=0.003, # 0.3% per trade slippage=0.005, # 0.5% slippage freq="1h", # hourly data )
3. Metrics — Built-in Performance Analysis
# Full stats summary print(pf.stats()) # Individual metrics print(f"Total Return: {pf.total_return():.2%}") print(f"Sharpe Ratio: {pf.sharpe_ratio():.3f}") print(f"Max Drawdown: {pf.max_drawdown():.2%}") print(f"Win Rate: {pf.trades.win_rate():.2%}")
4. Parameter Optimization — Grid Search in Seconds
Pass arrays instead of scalars to test many parameter combos simultaneously:
import numpy as np fast_periods = np.arange(5, 25, 2) # 10 values slow_periods = np.arange(20, 60, 5) # 8 values fast_ma = vbt.MA.run(close, fast_periods, short_name="fast") slow_ma = vbt.MA.run(close, slow_periods, short_name="slow") # This creates 80 parameter combinations automatically entries = fast_ma.ma_crossed_above(slow_ma) exits = fast_ma.ma_crossed_below(slow_ma)
Basic Workflow
Step 1: Load OHLCV Data
import pandas as pd # From CSV df = pd.read_csv("ohlcv.csv", parse_dates=["timestamp"], index_col="timestamp") close = df["close"] # From Yahoo Finance (traditional markets) btc = vbt.YFData.download("BTC-USD", start="2023-01-01", end="2025-01-01") close = btc.get("Close")
For Solana tokens, fetch data via the
birdeye-api skill and load into a DataFrame.
Step 2: Compute Indicators
import pandas_ta as ta # Using pandas-ta (see pandas-ta skill) df.ta.ema(length=12, append=True) df.ta.ema(length=26, append=True) df.ta.rsi(length=14, append=True) df.ta.bbands(length=20, std=2, append=True) # Or using vectorbt built-ins rsi = vbt.RSI.run(close, window=14) bbands = vbt.BBANDS.run(close, window=20, alpha=2)
Step 3: Generate Entry/Exit Signals
# EMA crossover entries = df["EMA_12"] > df["EMA_26"] exits = df["EMA_12"] < df["EMA_26"] # RSI mean reversion entries = rsi.rsi_below(30) exits = rsi.rsi_above(70)
Step 4: Run Backtest
pf = vbt.Portfolio.from_signals( close=close, entries=entries, exits=exits, init_cash=10_000, fees=0.003, slippage=0.005, size=0.95, # use 95% of available cash size_type="percent", freq="1h", )
Step 5: Analyze Results
# Summary statistics print(pf.stats()) # Trade-level analysis trades = pf.trades.records_readable print(f"\nTrade count: {len(trades)}") print(f"Avg holding period: {trades['Duration'].mean()}") # Equity curve pf.plot().show() # Drawdown chart pf.drawdowns.plot().show()
Key Portfolio Parameters
| Parameter | Description | Example |
|---|---|---|
| Price series (pd.Series or DataFrame) | |
| Boolean entry signals | |
| Boolean exit signals | |
| Starting capital | |
| Fee per trade (fraction) | (0.3%) |
| Slippage per trade (fraction) | (0.5%) |
| Position size | |
| How to interpret size | , , |
| Data frequency | , , |
| Trade direction | , , |
| Allow adding to positions | |
| Stop-loss level (fraction) | (5%) |
| Take-profit level (fraction) | (10%) |
Performance Metrics
Returns
— cumulative return over the periodtotal_return()
— annualized compound returnannualized_return()
— Series of daily returnsdaily_returns()
Risk
— maximum peak-to-trough declinemax_drawdown()
— annualized standard deviation of returnsannualized_volatility()
— VaR at specified confidence levelvalue_at_risk()
Risk-Adjusted
— excess return per unit volatilitysharpe_ratio()
— excess return per unit downside deviationsortino_ratio()
— annualized return / max drawdowncalmar_ratio()
— probability-weighted gain/loss ratioomega_ratio()
Trade Statistics
— fraction of profitable tradestrades.win_rate()
— gross profit / gross losstrades.profit_factor()
— average P&L per tradetrades.expectancy()
— mean profit on winnerstrades.avg_winning_trade()
— mean loss on loserstrades.avg_losing_trade()
— total number of completed tradestrades.count()
Parameter Optimization
Grid Search
fast_windows = [5, 8, 12, 15, 20] slow_windows = [20, 26, 30, 40, 50] # Run all 25 combos at once fast_ma = vbt.MA.run(close, fast_windows, short_name="fast") slow_ma = vbt.MA.run(close, slow_windows, short_name="slow") entries = fast_ma.ma_crossed_above(slow_ma) exits = fast_ma.ma_crossed_below(slow_ma) pf = vbt.Portfolio.from_signals(close, entries, exits, fees=0.003) # Find best params by Sharpe sharpe = pf.sharpe_ratio() best_idx = sharpe.idxmax() print(f"Best params: {best_idx}, Sharpe: {sharpe[best_idx]:.3f}")
Walk-Forward Validation
Always validate optimized parameters on out-of-sample data:
# Split: 70% train, 30% test split_idx = int(len(close) * 0.7) train_close = close.iloc[:split_idx] test_close = close.iloc[split_idx:] # Optimize on training data # ... (run grid search on train_close) # Validate best params on test data # ... (run single backtest on test_close with best params)
See
references/optimization_guide.md for detailed walk-forward methodology and overfitting prevention.
Crypto-Specific Considerations
24/7 Markets
Crypto markets never close. Use hourly or minute-based frequencies, not business-day frequencies:
# Correct for crypto pf = vbt.Portfolio.from_signals(close, entries, exits, freq="1h") # Wrong — business days assume market closures # pf = vbt.Portfolio.from_signals(close, entries, exits, freq="1B")
Realistic Fees
DEX swaps on Solana typically cost 0.25–1% including AMM fees. CEX spot fees are 0.05–0.1%.
# Solana DEX (conservative) pf = vbt.Portfolio.from_signals(close, entries, exits, fees=0.005) # CEX spot pf = vbt.Portfolio.from_signals(close, entries, exits, fees=0.001)
Slippage
Low-liquidity tokens can have 1–5% slippage. Always model this:
# High-liquidity (SOL, ETH): 0.1–0.5% pf = vbt.Portfolio.from_signals(close, entries, exits, slippage=0.003) # Low-liquidity memecoins: 1–3% pf = vbt.Portfolio.from_signals(close, entries, exits, slippage=0.02)
Short History
Many tokens have less than 1 year of data. Be cautious about annualizing metrics from short samples.
Common Strategy Patterns
EMA Crossover
fast = vbt.MA.run(close, 12, short_name="fast") slow = vbt.MA.run(close, 26, short_name="slow") entries = fast.ma_crossed_above(slow) exits = fast.ma_crossed_below(slow)
RSI Mean Reversion
rsi = vbt.RSI.run(close, 14) entries = rsi.rsi_crossed_below(30) exits = rsi.rsi_crossed_above(70)
Bollinger Band Breakout
bb = vbt.BBANDS.run(close, window=20, alpha=2) entries = close > bb.upper exits = close < bb.lower
Stop-Loss and Take-Profit
pf = vbt.Portfolio.from_signals( close, entries, exits, sl_stop=0.05, # 5% stop-loss tp_stop=0.10, # 10% take-profit )
Related Skills
- pandas-ta — Technical indicator computation (feeds vectorbt signals)
- birdeye-api — Fetch Solana token OHLCV data for backtesting
- trading-visualization — Advanced chart generation for backtest results
- portfolio-analytics — Deeper portfolio-level risk/return analysis
- position-sizing — Optimal position sizing methodology
- risk-management — Portfolio-level risk guardrails
- regime-detection — Market regime awareness for adaptive strategies
Files
References
— Complete vectorbt API reference for Portfolio, indicators, plotting, and data loadingreferences/api_guide.md
— Grid search, walk-forward validation, overfitting prevention, and optimization best practicesreferences/optimization_guide.md
Scripts
— Three-strategy backtest comparison using synthetic data (EMA crossover, RSI mean reversion, Bollinger breakout)scripts/backtest_example.py
— EMA crossover parameter grid search with walk-forward validationscripts/parameter_sweep.py