Skip to main content

Python library for backtesting and analyzing trading strategies at scale

Project description

Build Status Code Coverage Website PyPi Downloads License Gitter

vectorbt

Logo

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

In contrast to conventional libraries, vectorbt represents trading data as nd-arrays. This enables superfast computation using vectorized operations with NumPy and non-vectorized but compiled operations 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 high performance, vectorbt is able to process large amounts of data even without GPU and parallelization (both are work in progress), and enable the user to interact with data-hungry widgets without significant delays.

With vectorbt you can

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

Installation

pip install vectorbt

See Jupyter Notebook and JupyterLab Support for Plotly figures.

Examples

You can start backtesting with just a couple of lines.

Here is how much profit we would have made if we invested $100 into Bitcoin in 2014 and held (Note: first time compiling with Numba may take a while):

import vectorbt as vbt

price = vbt.utils.data.download('BTC-USD', period='max')['Close']

portfolio = vbt.Portfolio.from_holding(price, init_cash=100)
portfolio.total_profit()
8412.436065824717

The crossover of 10-day SMA and 50-day SMA under the same conditions:

fast_ma = vbt.MA.run(price, 10)
slow_ma = vbt.MA.run(price, 50)
entries = fast_ma.ma_above(slow_ma, crossover=True)
exits = fast_ma.ma_below(slow_ma, crossover=True)

portfolio = vbt.Portfolio.from_signals(price, entries, exits, init_cash=100)
portfolio.total_profit()
12642.617149066731

Quickly assessing the performance of 1000 random strategies on BTC and ETH:

import numpy as np

symbols = ["BTC-USD", "ETH-USD"]
price_by_symbol = vbt.utils.data.download(symbols, period='max', cols='Close')
price = vbt.utils.data.concat_symbols(price_by_symbol, treat_missing='drop')

n = np.random.randint(10, 101, size=1000).tolist()
portfolio = vbt.Portfolio.from_random(price, n=n, init_cash=100, seed=42)

mean_expectancy = portfolio.trades.expectancy().groupby(['rand_n', 'symbol']).mean()
fig = mean_expectancy.unstack().vbt.scatterplot(xaxis_title='rand_n', yaxis_title='mean_expectancy')
fig.show()

rand_scatter.png

For fans of hyperparameter optimization, here is a snippet for testing 10000 window combinations of a dual SMA crossover strategy on BTC, USD and XRP:

symbols = ["BTC-USD", "ETH-USD", "LTC-USD"]
price_by_symbol = vbt.utils.data.download(symbols, period='max', cols='Close')
price = vbt.utils.data.concat_symbols(price_by_symbol, treat_missing='drop')

windows = np.arange(2, 101)
fast_ma, slow_ma = vbt.MA.run_combs(price, window=windows, r=2, short_names=['fast', 'slow'])
entries = fast_ma.ma_above(slow_ma, crossover=True)
exits = fast_ma.ma_below(slow_ma, crossover=True)

portfolio_kwargs = dict(size=np.inf, fees=0.001, freq='1D')
portfolio = vbt.Portfolio.from_signals(price, entries, exits, **portfolio_kwargs)

fig = portfolio.total_return().vbt.heatmap(
    x_level='fast_window', y_level='slow_window', slider_level='symbol', symmetric=True,
    trace_kwargs=dict(colorbar=dict(title='Total return', tickformat='%')))
fig.show()

dmac_heatmap.gif

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

portfolio[(10, 20, 'ETH-USD')].stats()
Start                            2015-08-07 00:00:00
End                              2021-02-09 00:00:00
Duration                          2010 days 00:00:00
Init. Cash                                       100
Total Profit                                  852058
Total Return [%]                              852058
Benchmark Return [%]                         62649.5
Position Coverage [%]                        55.4229
Max. Drawdown [%]                             70.735
Avg. Drawdown [%]                            11.8845
Max. Drawdown Duration             760 days 00:00:00
Avg. Drawdown Duration    29 days 07:23:04.615384615
Num. Trades                                       48
Win Rate [%]                                 54.1667
Best Trade [%]                                1075.8
Worst Trade [%]                             -29.5934
Avg. Trade [%]                               47.5687
Max. Trade Duration                 80 days 00:00:00
Avg. Trade Duration                 22 days 02:00:00
Expectancy                                   6687.62
SQN                                          1.90991
Gross Exposure                              0.554229
Sharpe Ratio                                 2.23221
Sortino Ratio                                3.95263
Calmar Ratio                                 5.89963
Name: (10, 20, ETH-USD), dtype: object
portfolio[(10, 20, 'ETH-USD')].plot().show()

dmac_portfolio.png

It's not all about backtesting - vectorbt can be used to facilitate financial data analysis and visualization. Let's generate a GIF for comparing %B and bandwidth of Bollinger Bands for different symbols:

import imageio
from tqdm import tqdm

symbols = ["BTC-USD", "ETH-USD", "ADA-USD"]
gif_delta, gif_step, gif_fps = 90, 3, 3
gif_fname = 'bbands.gif'

price_by_symbol = vbt.utils.data.download(symbols, period='6mo', cols='Close')
price = vbt.utils.data.concat_symbols(price_by_symbol, treat_missing='drop')

bbands = vbt.BBANDS.run(price)

def plot(bbands):
    fig = vbt.make_subplots(
            rows=5, cols=1, shared_xaxes=True, 
            row_heights=[*[0.5 / 3] * len(symbols), 0.25, 0.25], vertical_spacing=0.05,
            subplot_titles=(*symbols, '%B', 'Bandwidth'))
    fig.update_layout(template=vbt.settings.dark_template, showlegend=False, width=750, height=650)
    for i, symbol in enumerate(symbols):
        bbands.close[symbol].vbt.lineplot(add_trace_kwargs=dict(row=i + 1, col=1), fig=fig)
    bbands.percent_b.iloc[:, ::-1].vbt.heatmap(
        trace_kwargs=dict(zmin=0, zmid=0.5, zmax=1, colorscale='Spectral', colorbar=dict(
            y=(fig.layout.yaxis4.domain[0] + fig.layout.yaxis4.domain[1]) / 2, len=0.2
        )), add_trace_kwargs=dict(row=4, col=1), horizontal=True, fig=fig)
    bbands.bandwidth.iloc[:, ::-1].vbt.heatmap(
        trace_kwargs=dict(colorbar=dict(
            y=(fig.layout.yaxis5.domain[0] + fig.layout.yaxis5.domain[1]) / 2, len=0.2
        )), add_trace_kwargs=dict(row=5, col=1), horizontal=True, fig=fig)
    return fig

with imageio.get_writer(gif_fname, fps=gif_fps) as writer:
    for i in tqdm(range(0, len(bbands.wrapper.index) - gif_delta, gif_step)):
        fig = plot(bbands.iloc[i:i + gif_delta])
        fig_np = imageio.imread(fig.to_image(format="png"))
        writer.append_data(fig_np)
100%|██████████| 31/31 [00:21<00:00,  1.21it/s]

bbands.gif

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 as example. By calculating the performance of each reasonable window combination and plotting the whole thing as a heatmap (as we do above), we can analyze how performance depends upon window size. If we additionally compute the same heatmap over multiple time periods, we may observe how performance varies with downtrends and uptrends. Finally, by running the same pipeline over other strategies such as holding and trading randomly, we can compare them and decide whether our strategy is worth executing. With vectorbt, this analysis can be done in minutes and save time and cost of getting the same insights elsewhere.

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 (price, signals, etc.), vectorbt is optimized for working with multi-dimensional data: it treats index of a DataFrame as time axis and columns as distinct features that should be backtest, and performs computations on the entire matrix at once, without slow Python 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 pandas and vectorbt functionality. Moreover, each vectorbt method is flexible towards inputs 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().show()

drawdowns.png

  • Functions for working with signals
    • Entry, exit and random signal generation
    • Ranking and distance functions
>>> pd.Series([False, True, True, True]).vbt.signals.first()
0    False
1     True
2    False
3    False
dtype: bool
  • Signal factory for building iterative signal generators
    • Also includes a range of basic generators such for random signals
>>> rand = vbt.RAND.run(n=[0, 1, 2], input_shape=(6,), seed=42)
>>> rand.entries
rand_n      0      1      2
0       False   True   True
1       False  False  False
2       False  False  False
3       False  False   True
4       False  False  False
5       False  False  False
>>> rand.exits
rand_n      0      1      2
0       False  False  False
1       False  False   True
2       False  False  False
3       False   True  False
4       False  False   True
5       False  False  False
  • Functions for working with returns
    • Compiled versions of metrics found in empyrical
>>> 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 long and short positions
    • Supports individual and multi-asset mixed portfolios
    • Provides metrics and tools for analyzing returns, orders, trades and positions
>>> price = [1., 2., 3., 2., 1.]
>>> entries = [True, False, True, False, False]
>>> exits = [False, True, False, True, False]
>>> portfolio = vbt.Portfolio.from_signals(price, entries, exits, freq='1D')
>>> portfolio.trades.plot().show()

trades.png

  • A range of basic technical indicators with full Numba support
    • Moving average, Bollinger Bands, RSI, Stochastic, MACD, and more
    • Each offers methods for generating signals and plotting
    • Each allows arbitrary parameter combinations, from arrays to Cartesian products
>>> vbt.MA.run([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 with ease
    • Supports TA-Lib indicators out of the box
>>> SMA = vbt.IndicatorFactory.from_talib('SMA')
>>> SMA.run([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

Resources

Head over to the documentation to get started.

Notebooks

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

Dashboards

Articles

How to contribute

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

First, you need to install vectorbt from the repository:

pip uninstall vectorbt
git clone https://github.com/polakowo/vectorbt.git
cd vectorbt
pip install -e .

After making changes, make sure you did not break any functionality:

pytest

Please make sure to update tests as appropriate.

Disclaimer

This software is for educational purposes only. Do not risk money which you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vectorbt-0.16.5.tar.gz (271.6 kB view details)

Uploaded Source

File details

Details for the file vectorbt-0.16.5.tar.gz.

File metadata

  • Download URL: vectorbt-0.16.5.tar.gz
  • Upload date:
  • Size: 271.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/53.0.0 requests-toolbelt/0.9.1 tqdm/4.56.0 CPython/3.6.7

File hashes

Hashes for vectorbt-0.16.5.tar.gz
Algorithm Hash digest
SHA256 f1033ae3f8d92dbf36708c7024ee1c3f9e11fc1ac7a769bccebadefe6940a4f0
MD5 29db97947d8f7d529014efde5316f749
BLAKE2b-256 ac5ab2319c39f32ce9fb5ade174fe21aabe452aa7258bf5162799101b8e7a16a

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page