Rajandran R Creator of OpenAlgo - OpenSource Algo Trading framework for Indian Traders. Telecom Engineer turned Full-time Derivative Trader. Mostly Trading Nifty, Banknifty, High Liquid Stock Derivatives. Trading the Markets Since 2006 onwards. Using Market Profile and Orderflow for more than a decade. Designed and published 100+ open source trading systems on various trading tools. Strongly believe that market understanding and robust trading frameworks are the key to the trading success. Building Algo Platforms, Writing about Markets, Trading System Design, Market Sentiment, Trading Softwares & Trading Nuances since 2007 onwards. Author of Marketcalls.in

Mastering VectorBT – Superfast Supertrend Grid Optimization – Part 3 – Python Tutorial

5 min read

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.

Rajandran R Creator of OpenAlgo - OpenSource Algo Trading framework for Indian Traders. Telecom Engineer turned Full-time Derivative Trader. Mostly Trading Nifty, Banknifty, High Liquid Stock Derivatives. Trading the Markets Since 2006 onwards. Using Market Profile and Orderflow for more than a decade. Designed and published 100+ open source trading systems on various trading tools. Strongly believe that market understanding and robust trading frameworks are the key to the trading success. Building Algo Platforms, Writing about Markets, Trading System Design, Market Sentiment, Trading Softwares & Trading Nuances since 2007 onwards. Author of Marketcalls.in

[Course] Building Stock Market Based Telegram Bots using Python

Learn how to build powerful Telegram bots for the stock market using Python. This hands-on course guides you through creating bots that fetch real-time...
Rajandran R
1 min read

Understanding Object-Oriented Programming (OOP) Concepts in Python for Traders…

For traders and investors, having well-structured code can greatly improve the efficiency and manageability of trading applications. Object-Oriented Programming (OOP) in Python offers a...
Rajandran R
3 min read

How I Built a Trader-Friendly Stock Market Dashboard using…

As both a developer and an avid market watcher, I set out to create a tool that could give traders and investors a clear,...
Rajandran R
2 min read

Leave a Reply

Get Notifications, Alerts on Market Updates, Trading Tools, Automation & More