Skip to content

Getting started

VectorBT® is a Python package for quantitative analysis that takes a novel approach to backtesting: it operates entirely on pandas and NumPy objects, accelerated by Numba and Rust to analyze any data at speed and scale. This makes it possible to test many thousands of strategies in seconds.

Unlike other backtesters, VectorBT represents complex data as structured NumPy arrays. This enables ultra-fast computation through vectorized operations with NumPy, dynamically compiled operations with Numba, and optional precompiled Rust kernels with Python bindings via PyO3 for the most performance-critical paths.

It also integrates Plotly and Jupyter Widgets to display rich charts and dashboards — akin to Tableau — right in the Jupyter notebook. Thanks to its high performance, VectorBT can process large amounts of data even without a GPU or parallelization, enabling users to interact with data-hungry widgets without noticeable delays.

With VectorBT, you can

  • Simple


    Backtest strategies in a couple of lines of Python code

  • Blazing fast


    Enjoy the best of both worlds: the ecosystem of Python and the speed of C

  • Full control


    Retain full control over execution and your data (as opposed to web-based services such as TradingView)

  • Scalable


    Optimize your trading strategy against many parameters, assets, and periods in one go

  • Insightful


    Uncover hidden patterns in financial markets

  • ML-ready


    Analyze time series and engineer new features for ML models

  • Supercharged


    Supercharge pandas and your favorite tools to run much faster

  • Interactive


    Visualize strategy performance using interactive charts and dashboards (both in Jupyter and browser)

  • Batteries included


    Fetch and process data periodically, send Telegram notifications, and more

  • 💎 VectorBT PRO


    Support us to get access to parallelization, portfolio optimization, pattern recognition, event projections, limit orders, leverage, and 100+ other hot features!

Quick start

pip install -U vectorbt
pip install -U "vectorbt[rust]"
pip install -U "vectorbt[full]"
pip install -U "vectorbt[full,rust]"
>>> import vectorbt as vbt

>>> data = vbt.YFData.download("BTC-USD")
>>> price = data.get("Close")
>>> pf = vbt.Portfolio.from_holding(price, init_cash=100)
>>> print(pf.total_profit())

17089.25554191831

See Installation for Docker and other options, or Usage for more examples.

Why VectorBT?

While there are many great backtesting packages for Python, VectorBT uniquely combines an extremely fast backtester with a data science toolkit: it excels at raw processing performance while offering interactive tools to explore complex phenomena in trading. With it, you can sweep a huge number of strategy configurations, time periods, and instruments in seconds, discover where your strategy performs best, and uncover hidden patterns in data. Having this kind of analytical power at your fingertips can give you a real information advantage in your own trading.

How it works

VectorBT was designed to address common performance shortcomings of backtesting libraries. It builds on the idea that each strategy instance can be represented in a vectorized form, so multiple instances can be packed into a single multi-dimensional array, processed highly efficiently, and compared with ease. This overhauls the traditional OOP approach, where strategies are represented as classes and other data structures that are easier to write and extend, but harder to analyze at scale and much slower without additional optimization effort.

Thanks to the time-series nature of trading data, most aspects of backtesting can be translated into vectors. Instead of processing one element at a time, vectorization lets you avoid naive looping and apply the same operation to all elements at once. The path-dependency problem inherent to vectorization is solved by using compiled backends:

  • Numba compiles Python code on the fly, allowing you to write complex logic in pure Python while still achieving C-like performance.
  • Rust provides precompiled kernels for the most performance-critical paths, which can be enabled with a single argument.

Example

Imagine a complex strategy with many hyperparameters that need to be tuned. Brute-forcing every combination might seem infeasible, but we can still interpolate — and VectorBT makes exactly this possible. It doesn't care whether you have one strategy instance or millions. As long as their vectors can be concatenated into a matrix and you have enough memory, you can analyze them all in one go.

Let's start with fetching the daily price of Bitcoin:

>>> import numpy as np
>>> import pandas as pd
>>> from datetime import datetime

>>> import vectorbt as vbt

>>> # Prepare data
>>> start = '2019-01-01 UTC'  # crypto is in UTC
>>> end = '2020-01-01 UTC'
>>> btc_price = vbt.YFData.download('BTC-USD', start=start, end=end).get('Close')

>>> btc_price
Date
2019-01-01 00:00:00+00:00    3843.520020
2019-01-02 00:00:00+00:00    3943.409424
2019-01-03 00:00:00+00:00    3836.741211
...                                  ...
2019-12-30 00:00:00+00:00    7292.995117
2019-12-31 00:00:00+00:00    7193.599121
2020-01-01 00:00:00+00:00    7200.174316
Freq: D, Name: Close, Length: 366, dtype: float64

We are going to test a simple Dual Moving Average Crossover (DMAC) strategy using the MA class to calculate moving averages and generate signals.

Our first test is straightforward: buy when the 10-day moving average crosses above the 20-day moving average, and sell when the opposite occurs.

>>> fast_ma = vbt.MA.run(btc_price, 10, short_name='fast')
>>> slow_ma = vbt.MA.run(btc_price, 20, short_name='slow')

>>> entries = fast_ma.ma_crossed_above(slow_ma)
>>> entries
Date
2019-01-01 00:00:00+00:00    False
2019-01-02 00:00:00+00:00    False
2019-01-03 00:00:00+00:00    False
...                            ...
2019-12-30 00:00:00+00:00    False
2019-12-31 00:00:00+00:00    False
2020-01-01 00:00:00+00:00    False
Freq: D, Length: 366, dtype: bool

>>> exits = fast_ma.ma_crossed_below(slow_ma)
>>> exits
Date
2019-01-01 00:00:00+00:00    False
2019-01-02 00:00:00+00:00    False
2019-01-03 00:00:00+00:00    False
...                            ...
2019-12-30 00:00:00+00:00    False
2019-12-31 00:00:00+00:00    False
2020-01-01 00:00:00+00:00    False
Freq: D, Length: 366, dtype: bool

>>> pf = vbt.Portfolio.from_signals(btc_price, entries, exits)
>>> pf.total_return()
0.636680693047752

A single DMAC instance produces one column of signals and one performance value.

Adding another strategy instance is as simple as adding another column. Here we pass an array of window sizes instead of a single value. For each window size, VectorBT computes a moving average over the entire price series and stores it in its own column.

>>> # Multiple strategy instances: (10, 30) and (20, 30)
>>> fast_ma = vbt.MA.run(btc_price, [10, 20], short_name='fast')
>>> slow_ma = vbt.MA.run(btc_price, [30, 30], short_name='slow')

>>> entries = fast_ma.ma_crossed_above(slow_ma)
>>> entries
fast_window                   10     20
slow_window                   30     30
Date
2019-01-01 00:00:00+00:00  False  False
2019-01-02 00:00:00+00:00  False  False
2019-01-03 00:00:00+00:00  False  False
...                          ...    ...
2019-12-30 00:00:00+00:00  False  False
2019-12-31 00:00:00+00:00  False  False
2020-01-01 00:00:00+00:00  False  False

[366 rows x 2 columns]

>>> exits = fast_ma.ma_crossed_below(slow_ma)
>>> exits
fast_window                   10     20
slow_window                   30     30
Date
2019-01-01 00:00:00+00:00  False  False
2019-01-02 00:00:00+00:00  False  False
2019-01-03 00:00:00+00:00  False  False
...                          ...    ...
2019-12-30 00:00:00+00:00  False  False
2019-12-31 00:00:00+00:00  False  False
2020-01-01 00:00:00+00:00  False  False

[366 rows x 2 columns]

>>> pf = vbt.Portfolio.from_signals(btc_price, entries, exits)
>>> pf.total_return()
fast_window  slow_window
10           30             0.848840
20           30             0.543411
Name: total_return, dtype: float64

For convenience, VectorBT automatically creates the column levels fast_window and slow_window, making it easy to tell which window size corresponds to which column.

Notice how the signal generation code stays the same across examples — most functions in VectorBT work on time series of any shape. This makes it possible to build analysis pipelines that are universal to the input data.

Representing different features as columns opens up endless possibilities for backtesting. For example, we can go a step further and test the same strategy on Ethereum. To compare both instruments, simply combine the Bitcoin and Ethereum price series into one DataFrame and run the same backtesting pipeline.

>>> # Multiple strategy instances and instruments
>>> eth_price = vbt.YFData.download('ETH-USD', start=start, end=end).get('Close')
>>> comb_price = btc_price.vbt.concat(eth_price,
...     keys=pd.Index(['BTC', 'ETH'], name='symbol'))
>>> comb_price.vbt.drop_levels(-1, inplace=True)
>>> comb_price
symbol                             BTC         ETH
Date
2019-01-01 00:00:00+00:00  3843.520020  140.819412
2019-01-02 00:00:00+00:00  3943.409424  155.047684
2019-01-03 00:00:00+00:00  3836.741211  149.135010
...                                ...         ...
2019-12-30 00:00:00+00:00  7292.995117  132.633484
2019-12-31 00:00:00+00:00  7193.599121  129.610855
2020-01-01 00:00:00+00:00  7200.174316  130.802002

[366 rows x 2 columns]

>>> fast_ma = vbt.MA.run(comb_price, [10, 20], short_name='fast')
>>> slow_ma = vbt.MA.run(comb_price, [30, 30], short_name='slow')

>>> entries = fast_ma.ma_crossed_above(slow_ma)
>>> entries
fast_window                          10            20
slow_window                          30            30
symbol                       BTC    ETH    BTC    ETH
Date
2019-01-01 00:00:00+00:00  False  False  False  False
2019-01-02 00:00:00+00:00  False  False  False  False
2019-01-03 00:00:00+00:00  False  False  False  False
...                          ...    ...    ...    ...
2019-12-30 00:00:00+00:00  False  False  False  False
2019-12-31 00:00:00+00:00  False  False  False  False
2020-01-01 00:00:00+00:00  False  False  False  False

[366 rows x 4 columns]

>>> exits = fast_ma.ma_crossed_below(slow_ma)
>>> exits
fast_window                          10            20
slow_window                          30            30
symbol                       BTC    ETH    BTC    ETH
Date
2019-01-01 00:00:00+00:00  False  False  False  False
2019-01-02 00:00:00+00:00  False  False  False  False
2019-01-03 00:00:00+00:00  False  False  False  False
...                          ...    ...    ...    ...
2019-12-30 00:00:00+00:00  False  False  False  False
2019-12-31 00:00:00+00:00  False  False  False  False
2020-01-01 00:00:00+00:00  False  False  False  False

[366 rows x 4 columns]

>>> pf = vbt.Portfolio.from_signals(comb_price, entries, exits)
>>> pf.total_return()
fast_window  slow_window  symbol
10           30           BTC       0.848840
                          ETH       0.244204
20           30           BTC       0.543411
                          ETH      -0.319102
Name: total_return, dtype: float64

>>> mean_return = pf.total_return().groupby('symbol').mean()
>>> mean_return.vbt.barplot(xaxis_title='Symbol', yaxis_title='Mean total return')

Strategies and instruments aren't the only things that can act as separate features — time can too. If we want to find out when our strategy performs best, we can backtest over multiple time periods. VectorBT can split one time period into many segments of the same length and frequency and represent them as distinct columns. For example, let's split the entire time period into two equal halves and backtest them at once.

>>> # Multiple strategy instances, instruments, and time periods
>>> mult_comb_price, _ = comb_price.vbt.range_split(n=2)
>>> mult_comb_price
split_idx                         0                         1
symbol              BTC         ETH           BTC         ETH
0           3843.520020  140.819412  11961.269531  303.099976
1           3943.409424  155.047684  11215.437500  284.523224
2           3836.741211  149.135010  10978.459961  287.997528
...                 ...         ...           ...         ...
180        10817.155273  290.695984   7292.995117  132.633484
181        10583.134766  293.641113   7193.599121  129.610855
182        10801.677734  291.596436   7200.174316  130.802002

[183 rows x 4 columns]

>>> fast_ma = vbt.MA.run(mult_comb_price, [10, 20], short_name='fast')
>>> slow_ma = vbt.MA.run(mult_comb_price, [30, 30], short_name='slow')

>>> entries = fast_ma.ma_crossed_above(slow_ma)
>>> exits = fast_ma.ma_crossed_below(slow_ma)

>>> pf = vbt.Portfolio.from_signals(mult_comb_price, entries, exits, freq='1D')
>>> pf.total_return()
fast_window  slow_window  split_idx  symbol
10           30           0          BTC       1.632259
                                     ETH       0.946786
                          1          BTC      -0.288720
                                     ETH      -0.308387
20           30           0          BTC       1.721449
                                     ETH       0.343274
                          1          BTC      -0.418280
                                     ETH      -0.257947
Name: total_return, dtype: float64

Notice how the index is no longer datetime-like, since it now captures multiple time periods. That's why we need to pass the frequency freq to the Portfolio class so it can compute performance metrics such as the Sharpe ratio.

The index hierarchy of the resulting performance series can then be used to group results by any feature, such as window pair, symbol, or time period.

>>> mean_return = pf.total_return().groupby(['split_idx', 'symbol']).mean()
>>> mean_return.unstack(level=-1).vbt.barplot(
...     xaxis_title='Split index',
...     yaxis_title='Mean total return',
...     legend_title_text='Symbol')

There is much more to backtesting than stacking columns: VectorBT provides tools for every part of the backtesting pipeline — from building indicators and generating signals, to modeling portfolio performance and visualizing results.

Disclaimer

This software is for educational purposes only. Do not risk money you cannot afford to lose.

Use the software at your own risk. The authors and affiliates assume no responsibility for your trading results.