/vectorbt

Python library for backtesting and analyzing trading strategies at scale

Primary LanguagePythonGNU General Public License v3.0GPL-3.0

vectorbt

Logo

vectorbt is a backtesting library on steroids - it operates entirely on pandas and NumPy, and is accelerated by Numba to analyze trading strategies at speed and scale 🔥

It follows a unique approach to backtesting that builds upon vectorized matrix calculations and fast iterative processing with Numba. It also integrates plotly.py and ipywidgets to display complex charts and dashboards akin to Tableau right in the Jupyter notebook. Due to its high processing performance, vectorbt is able to process data on the fly and thus enable the user to interact with data-hungry widgets without significant delays.

With vectorbt you can

  • Analyze and engineer features for any time series data
  • Supercharge pandas and your favorite tools to run much faster
  • Test thousands of strategies, configurations, assets, and time ranges in one go
  • Test machine learning models
  • Build interactive charts/dashboards without leaving Jupyter

Example

Here a snippet for testing 4851 window combinations of a dual SMA crossover strategy on the whole Bitcoin history in under 5 seconds (Note: compiling for the first time may take a while):

import vectorbt as vbt
import numpy as np
import yfinance as yf

# Fetch daily price of Bitcoin
close = yf.Ticker("BTC-USD").history(period="max")['Close']

# Compute moving averages for all combinations of fast and slow windows
fast_ma, slow_ma = vbt.MA.run_combs(
    close, window=np.arange(2, 101), r=2, 
    short_names=['fast', 'slow']
)

# Generate crossover signals for each combination
entries = fast_ma.ma_above(slow_ma, crossed=True)
exits = fast_ma.ma_below(slow_ma, crossed=True)

# Run simulation
portfolio = vbt.Portfolio.from_signals(close, entries, exits, fees=0.001, freq='1D')

# Get total return, reshape to symmetric matrix, and plot the whole thing
portfolio.total_return().vbt.heatmap(
    x_level='fast_window', y_level='slow_window', symmetric=True,
    trace_kwargs=dict(colorbar=dict(title='Total return', tickformat='%'))
)

dmac_heatmap.png

Digging into each strategy configuration is as simple as indexing with pandas:

>>> portfolio[(13, 21)].stats()

Start                            2014-09-17 00:00:00
End                              2020-09-13 00:00:00
Duration                          2188 days 00:00:00
Holding Duration [%]                         56.9013
Total Profit                                 12102.4
Total Return [%]                             12102.4
Buy & Hold Return [%]                         2156.3
Max. Drawdown [%]                            47.8405
Avg. Drawdown [%]                             8.3173
Max. Drawdown Duration             510 days 00:00:00
Avg. Drawdown Duration    35 days 01:37:37.627118644
Num. Trades                                       54
Win Rate [%]                                 53.7037
Best Trade [%]                               279.692
Worst Trade [%]                             -23.4948
Avg. Trade [%]                               13.9273
Max. Trade Duration                100 days 00:00:00
Avg. Trade Duration                 23 days 01:20:00
Expectancy                                   224.119
SQN                                          2.25024
Sharpe Ratio                                 1.81674
Sortino Ratio                                2.87812
Calmar Ratio                                 2.56841
Name: (13, 21), dtype: object

Motivation

While there are many other great backtesting packages for Python, vectorbt is more of a data science tool: it excels at processing performance and offers interactive tools to explore complex phenomena in trading. With it you can traverse a huge number of strategy configurations, time periods and instruments in little time, to explore where your strategy performs best and to uncover hidden patterns in data.

Take a simple Dual Moving Average Crossover strategy for example. By calculating the performance of each reasonable window combination and plotting the whole thing as a heatmap (as we do above), you can easily identify how performance depends on window size. If you additionally compute the same heatmap over multiple time periods, you will spot how performance varies with downtrends and uptrends. Finally, by running the same pipeline on other strategies such as holding and trading randomly, you can compare them and decide whether your strategy is worth executing. With vectorbt, this analysis can be done in minutes, and will effectively save you nights of getting the same insights using other libraries.

How it works?

vectorbt combines pandas, NumPy and Numba sauce to obtain orders-of-magnitude speedup over other libraries. It natively works on pandas objects, while performing all computations using NumPy and Numba under the hood. This way, it is often much faster than pandas alone:

>>> import numpy as np
>>> import pandas as pd
>>> import vectorbt as vbt

>>> big_ts = pd.DataFrame(np.random.uniform(size=(1000, 1000)))

# pandas
>>> %timeit big_ts.expanding().max()
48.4 ms ± 557 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

# vectorbt
>>> %timeit big_ts.vbt.expanding_max()
8.82 ms ± 121 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In contrast to most other similar backtesting libraries where backtesting is limited to simple arrays (think of an array for price, an array for signals, etc.), vectorbt is optimized for working with 2-dimensional data: it treats index of a DataFrame as time axis and columns as distinct features that should be backtested, and performs calculations on the entire matrix at once. This way, user can construct huge matrices with thousands of columns and calculate the performance for each one with a single matrix operation, without any Pythonic loops.

To make the library easier to use, vectorbt introduces a namespace (accessor) to pandas objects (see extending pandas). This way, user can easily switch between native pandas functionality and highly-efficient vectorbt methods. Moreover, each vectorbt method is flexible and can work on both Series and DataFrames.

Features

  • Extends pandas using a custom vbt accessor -> Compatible with any library
  • For high performance, most operations are done strictly using NumPy and Numba -> Much faster than comparable operations in pandas
# pandas
>>> %timeit big_ts + 1
242 ms ± 3.58 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# vectorbt
>>> %timeit big_ts.vbt + 1
3.32 ms ± 19.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  • Helper functions for combining, transforming, and indexing NumPy and pandas objects
    • NumPy-like broadcasting for pandas, among other features
# pandas
>>> pd.Series([1, 2, 3]) + pd.DataFrame([[1, 2, 3]])
   0  1  2
0  2  4  6

# vectorbt
>>> pd.Series([1, 2, 3]).vbt + pd.DataFrame([[1, 2, 3]])
   0  1  2
0  2  3  4
1  3  4  5
2  4  5  6
  • Compiled versions of common pandas functions, such as rolling, groupby, and resample
# pandas
>>> %timeit big_ts.rolling(2).apply(np.mean, raw=True)
7.32 s ± 431 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# vectorbt
>>> mean_nb = njit(lambda col, i, x: np.mean(x))
>>> %timeit big_ts.vbt.rolling_apply(2, mean_nb)
86.2 ms ± 7.97 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
  • Drawdown analysis
>>> pd.Series([2, 1, 3, 2]).vbt.drawdowns().plot()

drawdowns.png

  • Functions for working with signals
    • Entry, exit and random signal generation, ranking and distance functions
    • Stop loss, trailing stop and take profit signal generation
>>> pd.Series([False, True, True, True]).vbt.signals.first()
0    False
1     True
2    False
3    False
dtype: bool
  • Functions for working with returns
    • Compiled versions of metrics found in empyrical and more
>>> pd.Series([0.01, -0.01, 0.01]).vbt.returns(freq='1D').sharpe_ratio()
5.515130702591433
  • Class for modeling portfolios
    • Accepts signals, orders, and custom order function
    • Supports individual and multi-asset mixed portfolios
    • Provides metrics and tools for analyzing returns, orders, trades and positions
>>> price = pd.Series([1., 2., 3., 2., 1.])
>>> entries = pd.Series([True, False, True, False, False])
>>> exits = pd.Series([False, True, False, True, False])
>>> portfolio = vbt.Portfolio.from_signals(price, entries, exits, freq='1D')
>>> portfolio.trades().plot()

trades.png

  • Technical indicators with full Numba support
    • Moving average, Bollinger Bands, RSI, Stochastic Oscillator, MACD, and more
    • Each offers methods for generating signals and plotting
    • Each allows arbitrary parameter combinations, from arrays to Cartesian products
>>> vbt.MA.run(pd.Series([1, 2, 3]), window=[2, 3], ewm=[False, True]).ma
ma_window     2         3
ma_ewm    False      True 
0           NaN       NaN
1           1.5       NaN
2           2.5  2.428571
  • Indicator factory for building complex technical indicators in a simplified way
    • Supports TA-Lib indicators out of the box
>>> SMA = vbt.IndicatorFactory.from_talib('SMA')
>>> SMA.run(pd.Series([1., 2., 3.]), timeperiod=[2, 3]).real
sma_timeperiod    2    3
0               NaN  NaN
1               1.5  NaN
2               2.5  2.0
  • Interactive Plotly-based widgets to visualize backtest results
    • Full integration with ipywidgets for displaying interactive dashboards in Jupyter
>>> a = np.random.normal(0, 4, size=10000)
>>> pd.Series(a).vbt.box(horizontal=True, trace_kwargs=dict(boxmean='sd'))

Box.png

Installation

pip install vectorbt

See Jupyter Notebook and JupyterLab Support for Plotly figures.

Example notebooks

Note: you will need to run the notebook to play with widgets.

Dashboards