VectorBT provides a robust framework for optimizing trading strategies more efficiently, enabling the selection of the best parameters and fine-tuning of the strategy. In this tutorial, we will explore the process of building a Supertrend using the Talib, Numpy and Numba Python libraries. This approach aims to reduce computation time and enhance the calculation speed of the Supertrend indicator, which is crucial for optimizing strategies with larger parameter datasets.
Platform Used: Google Colab (Version: Python 3.10.12)
Install VectorBT, Numba, and Talib Libraries
pip install vectorbt
pip install numba
url = 'https://anaconda.org/conda-forge/libta-lib/0.4.0/download/linux-64/libta-lib-0.4.0-h166bdaf_1.tar.bz2'
!curl -L $url | tar xj -C /usr/lib/x86_64-linux-gnu/ lib --strip-components=1
url = 'https://anaconda.org/conda-forge/ta-lib/0.4.19/download/linux-64/ta-lib-0.4.19-py310hde88566_4.tar.bz2'
!curl -L $url | tar xj -C /usr/local/lib/python3.10/dist-packages/ lib/python3.10/site-packages/talib --strip-components=3
import talib
Calculate Supertrend with Faster Computational Performance using Talib, Numba and Numpy
Coding Reference : Superfast Supertrend
import vectorbt as vbt
import pandas as pd
import numpy as np
from numba import njit
import talib
def get_basic_bands(med_price, atr, multiplier):
matr = multiplier * atr
upper = med_price + matr
lower = med_price - matr
return upper, lower
@njit
def get_final_bands_nb(close, upper, lower):
trend = np.full(close.shape, np.nan)
dir_ = np.full(close.shape, 1)
long = np.full(close.shape, np.nan)
short = np.full(close.shape, np.nan)
for i in range(1, close.shape[0]):
if close[i] > upper[i - 1]:
dir_[i] = 1
elif close[i] < lower[i - 1]:
dir_[i] = -1
else:
dir_[i] = dir_[i - 1]
if dir_[i] > 0 and lower[i] < lower[i - 1]:
lower[i] = lower[i - 1]
if dir_[i] < 0 and upper[i] > upper[i - 1]:
upper[i] = upper[i - 1]
if dir_[i] > 0:
trend[i] = long[i] = lower[i]
else:
trend[i] = short[i] = upper[i]
return trend, dir_, long, short
def faster_supertrend_talib(high, low, close, period=7, multiplier=3):
avg_price = talib.MEDPRICE(high, low)
atr = talib.ATR(high, low, close, period)
upper, lower = get_basic_bands(avg_price, atr, multiplier)
return get_final_bands_nb(close, upper, lower)
Download the Data
#Download Data from Yahoo Finance
data = vbt.YFData.download("HDFCBANK.NS",start='2010-01-01', end='2023-12-31').get()
data
Output
Open High Low Close Volume Dividends Stock Splits
Date
2010-01-03 18:30:00+00:00 153.474964 156.047932 152.802387 153.989563 3050490 0.0 0.0
2010-01-04 18:30:00+00:00 154.377797 155.731988 153.926399 154.125015 8386600 0.0 0.0
2010-01-05 18:30:00+00:00 154.738888 155.280570 152.400655 154.228821 6639840 0.0 0.0
2010-01-06 18:30:00+00:00 159.650071 159.650071 153.619414 154.630539 6123980 0.0 0.0
2010-01-07 18:30:00+00:00 154.558335 155.587520 153.565270 154.833694 7085900 0.0 0.0
... ... ... ... ... ... ... ...
2023-12-10 18:30:00+00:00 1650.050049 1663.699951 1647.000000 1651.000000 11593810 0.0 0.0
2023-12-11 18:30:00+00:00 1654.199951 1656.250000 1631.849976 1634.599976 18290738 0.0 0.0
2023-12-12 18:30:00+00:00 1632.449951 1636.000000 1615.099976 1630.900024 14673457 0.0 0.0
2023-12-13 18:30:00+00:00 1646.000000 1658.949951 1645.000000 1650.150024 17586183 0.0 0.0
2023-12-14 18:30:00+00:00 1650.000000 1660.000000 1646.599976 1648.849976 7827825 0.0 0.0
Initialize IndicatorFactory to create a basic structure or template for a supertrend indicator and then use a class method such as IndicatorFactory.from_apply_func() to bind a supertrend calculation function to the template
SuperTrend = vbt.IndicatorFactory(
class_name='SuperTrend',
short_name='st',
input_names=['high', 'low', 'close'],
param_names=['period', 'multiplier'],
output_names=['supert', 'superd', 'superl', 'supers']
).from_apply_func(
faster_supertrend_talib,
period=7,
multiplier=3,
to_2d = False
)
Where the ‘supert‘ variable contains the Supertrend indicator value, the ‘superd‘ variable contains the direction of Supertrend indicators. A value of +1 indicates Supertrend in buy mode, and -1 represents Supertrend in sell mode. ‘superl‘ contains the Supertrend long value, and ‘supers‘ contains the Supertrend short values.
Define the Parameter Grid and Compute Supertrend for Multiple Combinations
#Define the Parameter Grid and Compute Supertrend for Multiple Combinations
length = np.arange(10,100,1)
multiplier = np.arange(1,5,0.25)
st = SuperTrend.run(data['High'], data['Low'], data['Close'],
period=length,multiplier=multiplier,param_product = True)
Trading Logic
#Trading Logic - while backtesting vectorbt autmatically removes excessive signals
entries = st.superd==1
exits = st.superd==-1
Run the Backtesting Engine for Multiple Combinational Parameters and Get Porfolio Stats with Selected Metrics
#Run the Backtesting Engine
InitialCapital = 2000000
portfolio = vbt.Portfolio.from_signals(
data['Close'],
entries=entries,
exits=exits,
size = 50,
size_type = 'percent',
fees = 0.001,
init_cash = InitialCapital,
min_size = 1,
size_granularity = 1,
freq = '1D'
)
#get portfolio stats
stats = portfolio.stats([
'total_return',
'total_trades',
'win_rate',
'expectancy',
'profit_factor',
'sharpe_ratio',
'calmar_ratio',
'sortino_ratio'
], agg_func=None)
stats
Output
Total Return [%] Total Trades Win Rate [%] Expectancy Profit Factor Sharpe Ratio Calmar Ratio Sortino Ratio
st_period st_multiplier
10 1.00 110.989987 187 43.548387 11974.996238 1.266833 0.496077 0.268343 0.736359
1.25 132.926824 145 43.750000 15657.745081 1.275541 0.545865 0.285883 0.814798
1.50 177.139321 116 45.217391 26744.656400 1.361542 0.636584 0.319595 0.966773
1.75 131.698549 98 41.237113 23406.261336 1.357480 0.547866 0.275197 0.829829
2.00 147.295566 81 45.000000 31973.680973 1.492471 0.585870 0.269994 0.892889
... ... ... ... ... ... ... ... ... ...
99 3.75 322.449949 30 58.620690 215693.241214 2.834223 0.820413 0.730891 1.247988
4.00 277.027366 28 51.851852 198796.388007 2.501577 0.768041 0.560455 1.167954
4.25 324.198644 25 50.000000 262053.468550 3.489925 0.824143 0.528067 1.263857
4.50 329.173510 23 50.000000 290295.915468 3.442347 0.847463 0.484430 1.303895
4.75 242.965458 23 45.454545 213722.385261 2.626083 0.737534 0.362571 1.121118
Sort the Dataframe with Highest Calmar Ratio
df = stats.sort_values("Calmar Ratio", ascending=False)
df
Output
Total Return [%] Total Trades Win Rate [%] Expectancy Profit Factor Sharpe Ratio Calmar Ratio Sortino Ratio
st_period st_multiplier
26 4.50 460.700937 20 52.631579 471403.429000 5.320774 0.979218 0.821691 1.525197
27 4.50 453.994239 20 52.631579 464507.058568 5.308692 0.972720 0.815387 1.514351
30 4.50 452.994518 20 52.631579 463478.620015 5.009046 0.970652 0.814466 1.510289
29 4.50 450.238778 20 52.631579 460643.564650 5.002865 0.969121 0.811815 1.508008
28 4.50 446.863640 20 52.631579 457172.443112 4.882099 0.965853 0.808654 1.502897
... ... ... ... ... ... ... ... ... ...
20 1.75 67.643363 102 39.603960 10790.430714 1.152556 0.374528 0.119989 0.555169
19 1.75 67.048164 102 39.603960 10681.536544 1.151530 0.372648 0.119144 0.552401
75 1.50 58.058234 126 39.200000 7157.741452 1.123245 0.340693 0.118909 0.499661
76 1.50 58.058234 126 39.200000 7157.741452 1.123245 0.340693 0.118909 0.499661
74 1.50 57.258661 126 39.200000 7040.929102 1.121099 0.338028 0.116740 0.495711
Get the Best Parameters with Max Calamar Ratio
#Get the Parameters with Max Calmar Ratio
max_calmar_index = df['Calmar Ratio'].idxmax()
# Get the corresponding values for st_period and st_multiplier
max_calmar_values = df.loc[max_calmar_index]
st_best_period, st_best_multiplier = max_calmar_index
print("Best st_period:", st_best_period)
print("Best st_multiplier:", st_best_multiplier)
print("Max Calmar Ratio Row:")
print(max_calmar_values)
Output
Best st_period: 26
Best st_multiplier: 4.5
Max Calmar Ratio Row:
Total Return [%] 460.700937
Total Trades 20.000000
Win Rate [%] 52.631579
Expectancy 471403.429000
Profit Factor 5.320774
Sharpe Ratio 0.979218
Calmar Ratio 0.821691
Sortino Ratio 1.525197
Name: (26, 4.5), dtype: float64
Get the Backtesting Metrics for the Best Parameters
#Get the portfolio Backtesting Stats for the Best Parameters
portfolio[(st_best_period,st_best_multiplier)].stats()
Start 2010-01-03 18:30:00+00:00
End 2023-12-14 18:30:00+00:00
Period 3445 days 00:00:00
Start Value 2000000.0
End Value 11214018.739359
Total Return [%] 460.700937
Benchmark Return [%] 970.75437
Max Gross Exposure [%] 99.99951
Total Fees Paid 266511.152822
Max Drawdown [%] 24.389605
Max Drawdown Duration 700 days 00:00:00
Total Trades 20
Total Closed Trades 19
Total Open Trades 1
Open Trade PnL 257353.588354
Win Rate [%] 52.631579
Best Trade [%] 53.038577
Worst Trade [%] -7.480926
Avg Winning Trade [%] 23.408099
Avg Losing Trade [%] -3.146502
Avg Winning Trade Duration 182 days 16:48:00
Avg Losing Trade Duration 61 days 00:00:00
Profit Factor 5.320774
Expectancy 471403.429
Sharpe Ratio 0.979218
Calmar Ratio 0.821691
Omega Ratio 1.184923
Sortino Ratio 1.525197
Name: (26, 4.5), dtype: object
Get the Trade List of Best Parameters
#Get the trade list of best parameters
trades = portfolio[(st_best_period,st_best_multiplier)].trades.records_readable
trades
Exit Trade Id Column Size Entry Timestamp Avg Entry Price Entry Fees Exit Timestamp Avg Exit Price Exit Fees PnL Return Direction Status Position Id
0 0 (26, 4.5) 12974.0 2010-01-03 18:30:00+00:00 153.989563 1997.860590 2010-10-26 18:30:00+00:00 204.823578 2657.381099 6.548653e+05 0.327783 Long Closed 0
1 1 (26, 4.5) 12466.0 2011-03-29 18:30:00+00:00 212.746506 2652.097941 2011-08-07 18:30:00+00:00 211.384262 2635.116211 -2.226894e+04 -0.008397 Long Closed 1
2 2 (26, 4.5) 11930.0 2012-01-17 18:30:00+00:00 220.441605 2629.868343 2012-05-15 18:30:00+00:00 226.525589 2702.450277 6.724961e+04 0.025571 Long Closed 2
3 3 (26, 4.5) 10728.0 2012-06-11 18:30:00+00:00 251.410492 2697.131758 2013-02-05 18:30:00+00:00 294.843872 3163.085060 4.600931e+05 0.170586 Long Closed 3
4 4 (26, 4.5) 10164.0 2013-04-17 18:30:00+00:00 310.565826 3156.591060 2013-06-19 18:30:00+00:00 295.958496 3008.122154 -1.546336e+05 -0.048988 Long Closed 4
5 5 (26, 4.5) 9927.0 2013-09-17 18:30:00+00:00 302.420654 3002.129835 2015-04-29 18:30:00+00:00 463.586273 4602.020934 1.592287e+06 0.530386 Long Closed 5
6 6 (26, 4.5) 9325.0 2015-05-28 18:30:00+00:00 492.537018 4592.907691 2015-08-23 18:30:00+00:00 481.359650 4488.678733 -1.133105e+05 -0.024671 Long Closed 6
7 7 (26, 4.5) 8634.0 2015-10-04 18:30:00+00:00 518.817932 4479.474026 2016-01-19 18:30:00+00:00 481.005371 4153.000374 -3.351061e+05 -0.074809 Long Closed 7
8 8 (26, 4.5) 8392.0 2016-03-20 18:30:00+00:00 493.900818 4144.815664 2016-11-16 18:30:00+00:00 585.868225 4916.606145 7.627291e+05 0.184020 Long Closed 8
9 9 (26, 4.5) 8128.0 2017-01-23 18:30:00+00:00 603.655640 4906.513039 2018-02-06 18:30:00+00:00 897.825317 7297.524180 2.378807e+06 0.484826 Long Closed 9
10 10 (26, 4.5) 7713.0 2018-05-01 18:30:00+00:00 944.183594 7282.488059 2018-09-03 18:30:00+00:00 989.920959 7635.260360 3.378546e+05 0.046393 Long Closed 10
11 11 (26, 4.5) 7560.0 2018-11-27 18:30:00+00:00 1007.989197 7620.398328 2019-07-21 18:30:00+00:00 1115.230713 8431.144189 7.946943e+05 0.104285 Long Closed 11
12 12 (26, 4.5) 7208.0 2019-09-19 18:30:00+00:00 1167.315796 8414.012257 2020-02-02 18:30:00+00:00 1160.698853 8366.317329 -6.447526e+04 -0.007663 Long Closed 12
13 13 (26, 4.5) 8565.0 2020-04-29 18:30:00+00:00 974.839050 8349.496466 2021-04-05 18:30:00+00:00 1401.489258 12003.755493 3.633906e+06 0.435225 Long Closed 13
14 14 (26, 4.5) 7864.0 2021-08-23 18:30:00+00:00 1523.462524 11980.509292 2021-11-09 18:30:00+00:00 1519.944458 11952.843218 -5.159943e+04 -0.004307 Long Closed 14
15 15 (26, 4.5) 8169.0 2022-03-21 18:30:00+00:00 1460.231445 11928.630677 2022-04-17 18:30:00+00:00 1363.771973 11140.653245 -8.110467e+05 -0.067992 Long Closed 15
16 16 (26, 4.5) 7841.0 2022-07-28 18:30:00+00:00 1417.939209 11118.061338 2022-09-26 18:30:00+00:00 1397.819946 10960.306199 -1.798335e+05 -0.016175 Long Closed 16
17 17 (26, 4.5) 7311.0 2022-10-31 18:30:00+00:00 1496.093018 10937.936052 2023-03-13 18:30:00+00:00 1546.613525 11307.291484 3.471102e+05 0.031735 Long Closed 17
18 18 (26, 4.5) 6775.0 2023-04-11 18:30:00+00:00 1665.796875 11285.773828 2023-08-10 18:30:00+00:00 1618.800049 10967.370331 -3.406566e+05 -0.030185 Long Closed 18
19 19 (26, 4.5) 6801.0 2023-12-03 18:30:00+00:00 1609.400024 10945.529566 2023-12-14 18:30:00+00:00 1648.849976 0.000000 2.573536e+05 0.023512 Long Open 19
Get the Sharpe Ratio Heatmap against Supertrend Parameter Combinations
By using a heatmap, you can compare the Sharpe Ratios of various parameter combinations at a glance. This makes it easier to identify regions of parameter space that lead to better trading performance. It aids in making informed decisions about which parameter values are most suitable for your specific trading goals.
Heatmaps allow you to see how sensitive the Sharpe Ratio is to changes in ATR Length and Multiplier. Understanding the sensitivity helps you assess the robustness of the strategy. For example, you might identify regions where the strategy performs well and is less sensitive to parameter changes, indicating a more stable configuration.
import plotly.graph_objects as go
sharpe = portfolio.sharpe_ratio()
calmar = portfolio.calmar_ratio()
# Plot Sharpe Ratio heatmap
sharpe_fig = sharpe.vbt.heatmap(
x_level="st_period",
y_level="st_multiplier",
)
sharpe_fig.update_layout(
title='Sharpe Ratio Heatmap',
xaxis_title="st_length",
yaxis_title="st_multiplier",
width=800, # set the width of the figure
height=600 # set the height of the figure
)
sharpe_fig.show()
Output

Get the Calmar Ratio Heatmap against Supertrend Parameter Combinations
By observing the heatmap, users can compare the performance of the Supertrend strategy under various parameter settings. This facilitates the identification of optimal combinations that result in higher Calmar Ratios, which is a measure of risk-adjusted returns. One can quickly identify regions with higher Calmar Ratios, indicating potentially more robust parameter values for the Supertrend indicator.
# Plot Calmar Ratio heatmap
calmar_fig = calmar.vbt.heatmap(
x_level="st_period",
y_level="st_multiplier",
)
calmar_fig.update_layout(
title='Calmar Ratio Heatmap',
xaxis_title="st_length",
yaxis_title="st_multiplier",
width=800, # set the width of the figure
height=600 # set the height of the figure
)
calmar_fig.show()
Output

I hope this tutorial helps optimize the calculation of Supertrend and refine the simple Supertrend strategy for optimal trading performance. It also covers visualizing the Sharpe ratio and Calmar ratio against the Supertrend parameters.