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'
)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')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 MBWe 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: objectEach 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.