Tutorials

SuperFast SuperTrend

Build and backtest a streaming, multithreaded SuperTrend indicator

While Python is slower than many compiled languages, it is easy to use and extremely versatile. For many users, especially in the data science field, Python's practicality outweighs concerns about speed. It serves as a Swiss Army knife for programmers and researchers alike.

Unfortunately for quants, Python can become a significant bottleneck when working with large datasets. To address this, an entire ecosystem of scientific packages—such as NumPy and Pandas—exists. These packages are highly optimized for performance, with critical code paths often written in Cython or C. They primarily operate on arrays, providing a consistent interface for efficient data processing.

This approach is especially valuable when creating indicators that can be expressed as a set of vectorized operations, such as OBV. Even for non-vectorized operations like the exponentially weighted moving average (EMA), which is used in many indicators such as MACD, implementations are written in compiled languages and exposed as ready-to-use Python functions. However, some indicators are challenging or even impossible to develop solely with standard array operations because they introduce path dependency, where today's decisions depend on those made yesterday. SuperTrend is one such indicator.

In this example, you will learn how to design and implement a SuperTrend indicator, and step by step optimize it for exceptional performance using TA-Lib and Numba. We will also backtest the new indicator across a range of parameters using VBT (PRO).

Data

The first step is always obtaining the right data. Specifically, we need enough data to benchmark different SuperTrend implementations. Let's pull 2 years of hourly Bitcoin and Ethereum data from Binance using VBT's BinanceData class:

from vectorbtpro import *

data = vbt.BinanceData.pull(
    ['BTCUSDT', 'ETHUSDT'],
    start='2020-01-01 UTC',
    end='2022-01-01 UTC',
    timeframe='1h'
)
Symbol 2/2
Period 18/18

Fetching data for both symbols took about 30 seconds to complete. Since Binance, like any other exchange, will not return all the data at once, VBT first requested the maximum amount of data starting from January 1, 2020, and then gradually collected the remaining data, while respecting Binance's API rate limits. In total, this resulted in 18 requests per symbol. Finally, VBT aligned both symbols in case their indexes or columns were different and made the final index timezone-aware (in UTC).

To avoid repeatedly hitting the Binance servers every time we start a new Python session, we should save the downloaded data locally using either VBT's Data.to_csv or Data.to_hdf:

data.to_hdf('my_data.h5')

We can then easily access the saved data using HDFData:

data = vbt.HDFData.pull('my_data.h5')
Symbol 2/2

We can access any of the symbols in an HDF file using regular path expressions. For example, the same as above: vbt.HDFData.pull(['my_data.h5/BTCUSDT', 'my_data.h5/ETHUSDT']).

Once we have the data, let's take a quick look at what is inside. To get any of the stored DataFrames, use the Data.data dictionary, where each DataFrame is keyed by symbol:

data.data['BTCUSDT'].info()
<class 'pandas.DataFrame'>
DatetimeIndex: 17513 entries, 2020-01-01 00:00:00+00:00 to 2021-12-31 23:00:00+00:00
Data columns (total 9 columns):
 #   Column              Non-Null Count  Dtype
---  ------              --------------  -----
 0   Open                17513 non-null  float64
 1   High                17513 non-null  float64
 2   Low                 17513 non-null  float64
 3   Close               17513 non-null  float64
 4   Volume              17513 non-null  float64
 5   Quote volume        17513 non-null  float64
 6   Trade count         17513 non-null  int64
 7   Taker base volume   17513 non-null  float64
 8   Taker quote volume  17513 non-null  float64
dtypes: float64(8), int64(1)
memory usage: 1.3 MB

We can also get an overview of all the symbols collected:

data.stats()
Start Index             2020-01-01 00:00:00+00:00
End Index               2021-12-31 23:00:00+00:00
Total Duration                  729 days 17:00:00
Total Symbols                                   2
Null Counts: BTCUSDT                            0
Null Counts: ETHUSDT                            0
Name: agg_stats, dtype: object

Each symbol has 17513 data points with no NaNs—perfect!

If you have ever worked with VBT, you know that VBT prefers data with symbols as columns (one per backtest) instead of features as columns. Since SuperTrend depends on the high, low, and close price, let's get those three features as separate DataFrames using Data.get:

✅ Learn how to design and unit test a streaming indicator that processes data in a single pass and updates with each new data point. We will use the streaming SuperTrend indicator as our example, making it the fastest SuperTrend ever built in Python!

✅ Due to the GIL, most Python operations use only one core. Learn how to work around this limitation and build an indicator that uses all available cores!

✅ Learn how to design a proper backtesting pipeline. We will discuss and implement four different designs, ranging from the most modular and flexible to the fastest. As the cherry on top, we will perform 67,200 SuperTrend backtests in just one second!

Topics

Copyright © 2021–2026 Oleg Polakow. All rights reserved.

Site content and documentation are provided for using and evaluating VectorBT PRO and for educational purposes. Any other use, including building or supporting competing products or services, requires prior written consent.