├── README.md └── supertrend.py /README.md: -------------------------------------------------------------------------------- 1 | # Supertrend Trading Strategy 2 | 3 | This repository contains Python code implementing a Supertrend trading strategy. The Supertrend is a trend-following indicator that helps identify trend direction and potential entry and exit points in the market. 4 | 5 | ## Overview 6 | 7 | The code consists of several functions: 8 | 9 | 1. **fetch_asset_data**: Fetches historical OHLC (Open-High-Low-Close) data for a specified asset from a cryptocurrency exchange. 10 | 2. **supertrend**: Calculates the Supertrend indicator based on the input OHLC data and a specified ATR (Average True Range) multiplier. 11 | 3. **generate_signals**: Generates buy/sell signals based on Supertrend values. 12 | 4. **create_positions**: Creates buy/sell positions based on the generated signals. 13 | 5. **plot_data**: Plots the Supertrend indicator along with buy/sell positions on a candlestick chart. 14 | 6. **strategy_performance**: Evaluates the performance of the trading strategy, including overall profit/loss, maximum drawdown, and other metrics. 15 | 7. **plot_performance_curve**: Plots the performance curve of the strategy against a benchmark. 16 | 17 | ## Usage 18 | 19 | 1. Ensure you have the necessary dependencies installed (`ccxt`, `pandas`, `numpy`, `pandas_ta`, `mplfinance`). 20 | 2. Replace the `symbol`, `start_date`, `interval`, and `exchange` variables with your desired asset, start date, interval, and exchange. 21 | 3. Adjust the `volatility` parameter for the Supertrend calculation. 22 | 4. Run the script to fetch data, apply the Supertrend strategy, evaluate performance, and visualize results. 23 | 24 | ## Dependencies 25 | 26 | - `ccxt`: For fetching cryptocurrency exchange data. 27 | - `pandas`, `numpy`: For data manipulation and analysis. 28 | - `pandas_ta`: For technical analysis indicators. 29 | - `mplfinance`: For plotting candlestick charts. 30 | - `matplotlib`: For additional plotting functionalities. 31 | 32 | ## Disclaimer 33 | 34 | - This code is provided for educational purposes only. Trading involves risk, and past performance is not indicative of future results. Always perform thorough testing and analysis before implementing any trading strategy in live markets. 35 | 36 | ## Contact 37 | 38 | For any questions or inquiries, please contact: Geofrey at ntalegeofrey@gmail.com. 39 | -------------------------------------------------------------------------------- /supertrend.py: -------------------------------------------------------------------------------- 1 | import ccxt 2 | import warnings 3 | from matplotlib.pyplot import fill_between 4 | import pandas as pd 5 | import numpy as np 6 | import pandas_ta as ta 7 | import mplfinance as mpf 8 | import matplotlib.pyplot as plt 9 | warnings.filterwarnings('ignore') 10 | 11 | 12 | def fetch_asset_data(symbol, start_date, interval, exchange): 13 | # Convert start_date to milliseconds timestamp 14 | start_date_ms = exchange.parse8601(start_date) 15 | ohlcv = exchange.fetch_ohlcv(symbol, interval, since=start_date_ms) 16 | header = ["date", "open", "high", "low", "close", "volume"] 17 | df = pd.DataFrame(ohlcv, columns=header) 18 | df['date'] = pd.to_datetime(df['date'], unit='ms') 19 | df.set_index("date", inplace=True) 20 | # Drop the last row containing live data 21 | df.drop(df.index[-1], inplace=True) 22 | return df 23 | 24 | def supertrend(df, atr_multiplier=3): 25 | # Calculate the Upper Band(UB) and the Lower Band(LB) 26 | # Formular: Supertrend =(High+Low)/2 + (Multiplier)∗(ATR) 27 | current_average_high_low = (df['high']+df['low'])/2 28 | df['atr'] = ta.atr(df['high'], df['low'], df['close'], period=15) 29 | df.dropna(inplace=True) 30 | df['basicUpperband'] = current_average_high_low + (atr_multiplier * df['atr']) 31 | df['basicLowerband'] = current_average_high_low - (atr_multiplier * df['atr']) 32 | first_upperBand_value = df['basicUpperband'].iloc[0] 33 | first_lowerBand_value = df['basicLowerband'].iloc[0] 34 | upperBand = [first_upperBand_value] 35 | lowerBand = [first_lowerBand_value] 36 | 37 | for i in range(1, len(df)): 38 | if df['basicUpperband'].iloc[i] < upperBand[i-1] or df['close'].iloc[i-1] > upperBand[i-1]: 39 | upperBand.append(df['basicUpperband'].iloc[i]) 40 | else: 41 | upperBand.append(upperBand[i-1]) 42 | 43 | if df['basicLowerband'].iloc[i] > lowerBand[i-1] or df['close'].iloc[i-1] < lowerBand[i-1]: 44 | lowerBand.append(df['basicLowerband'].iloc[i]) 45 | else: 46 | lowerBand.append(lowerBand[i-1]) 47 | 48 | df['upperband'] = upperBand 49 | df['lowerband'] = lowerBand 50 | df.drop(['basicUpperband', 'basicLowerband',], axis=1, inplace=True) 51 | return df 52 | 53 | def generate_signals(df): 54 | # Intiate a signals list 55 | signals = [0] 56 | 57 | # Loop through the dataframe 58 | for i in range(1 , len(df)): 59 | if df['close'][i] > df['upperband'][i]: 60 | signals.append(1) 61 | elif df['close'][i] < df['lowerband'][i]: 62 | signals.append(-1) 63 | else: 64 | signals.append(signals[i-1]) 65 | 66 | # Add the signals list as a new column in the dataframe 67 | df['signals'] = signals 68 | df['signals'] = df["signals"].shift(1) #Remove look ahead bias 69 | return df 70 | 71 | def create_positions(df): 72 | # We need to shut off (np.nan) data points in the upperband where the signal is not 1 73 | df['upperband'][df['signals'] == 1] = np.nan 74 | # We need to shut off (np.nan) data points in the lowerband where the signal is not -1 75 | df['lowerband'][df['signals'] == -1] = np.nan 76 | 77 | # Create a positions list 78 | buy_positions = [np.nan] 79 | sell_positions = [np.nan] 80 | 81 | # Loop through the dataframe 82 | for i in range(1, len(df)): 83 | # If the current signal is a 1 (Buy) & the it's not equal to the previous signal 84 | # Then that is a trend reversal, so we BUY at that current market price 85 | # We take note of the upperband value 86 | if df['signals'][i] == 1 and df['signals'][i] != df['signals'][i-1]: 87 | buy_positions.append(df['close'][i]) 88 | sell_positions.append(np.nan) 89 | # If the current signal is a -1 (Sell) & the it's not equal to the previous signal 90 | # Then that is a trend reversal, so we SELL at that current market price 91 | elif df['signals'][i] == -1 and df['signals'][i] != df['signals'][i-1]: 92 | sell_positions.append(df['close'][i]) 93 | buy_positions.append(np.nan) 94 | else: 95 | buy_positions.append(np.nan) 96 | sell_positions.append(np.nan) 97 | 98 | # Add the positions list as a new column in the dataframe 99 | df['buy_positions'] = buy_positions 100 | df['sell_positions'] = sell_positions 101 | return df 102 | 103 | def plot_data(df, symbol): 104 | # Define lowerband line plot 105 | lowerband_line = mpf.make_addplot(df['lowerband'], label= "lowerband", color='green') 106 | # Define upperband line plot 107 | upperband_line = mpf.make_addplot(df['upperband'], label= "upperband", color='red') 108 | # Define buy and sell markers 109 | buy_position_makers = mpf.make_addplot(df['buy_positions'], type='scatter', marker='^', label= "Buy", markersize=80, color='#2cf651') 110 | sell_position_makers = mpf.make_addplot(df['sell_positions'], type='scatter', marker='v', label= "Sell", markersize=80, color='#f50100') 111 | # A list of all addplots(apd) 112 | apd = [lowerband_line, upperband_line, buy_position_makers, sell_position_makers] 113 | # Create fill plots 114 | lowerband_fill = dict(y1=df['close'].values, y2=df['lowerband'].values, panel=0, alpha=0.3, color="#CCFFCC") 115 | upperband_fill = dict(y1=df['close'].values, y2=df['upperband'].values, panel=0, alpha=0.3, color="#FFCCCC") 116 | fills = [lowerband_fill, upperband_fill] 117 | # Plot the data 118 | mpf.plot(df, addplot=apd, type='candle', volume=True, style='charles', xrotation=20, title=str(symbol + ' Supertrend Plot'), fill_between=fills) 119 | 120 | def strategy_performance(strategy_df, capital=100, leverage=1): 121 | # Initialize the performance variables 122 | cumulative_balance = capital 123 | investment = capital 124 | pl = 0 125 | max_drawdown = 0 126 | max_drawdown_percentage = 0 127 | 128 | # Lists to store intermediate values for calculating metrics 129 | balance_list = [capital] 130 | pnl_list = [0] 131 | investment_list = [capital] 132 | peak_balance = capital 133 | 134 | # Loop from the second row (index 1) of the DataFrame 135 | for index in range(1, len(strategy_df)): 136 | row = strategy_df.iloc[index] 137 | 138 | # Calculate P/L for each trade signal 139 | if row['signals'] == 1: 140 | pl = ((row['close'] - row['open']) / row['open']) * \ 141 | investment * leverage 142 | elif row['signals'] == -1: 143 | pl = ((row['open'] - row['close']) / row['close']) * \ 144 | investment * leverage 145 | else: 146 | pl = 0 147 | 148 | # Update the investment if there is a signal reversal 149 | if row['signals'] != strategy_df.iloc[index - 1]['signals']: 150 | investment = cumulative_balance 151 | 152 | # Calculate the new balance based on P/L and leverage 153 | cumulative_balance += pl 154 | 155 | # Update the investment list 156 | investment_list.append(investment) 157 | 158 | # Calculate the cumulative balance and add it to the DataFrame 159 | balance_list.append(cumulative_balance) 160 | 161 | # Calculate the overall P/L and add it to the DataFrame 162 | pnl_list.append(pl) 163 | 164 | # Calculate max drawdown 165 | drawdown = cumulative_balance - peak_balance 166 | if drawdown < max_drawdown: 167 | max_drawdown = drawdown 168 | max_drawdown_percentage = (max_drawdown / peak_balance) * 100 169 | 170 | # Update the peak balance 171 | if cumulative_balance > peak_balance: 172 | peak_balance = cumulative_balance 173 | 174 | # Add new columns to the DataFrame 175 | strategy_df['investment'] = investment_list 176 | strategy_df['cumulative_balance'] = balance_list 177 | strategy_df['pl'] = pnl_list 178 | strategy_df['cumPL'] = strategy_df['pl'].cumsum() 179 | 180 | # Calculate other performance metrics (replace with your calculations) 181 | overall_pl_percentage = ( 182 | strategy_df['cumulative_balance'].iloc[-1] - capital) * 100 / capital 183 | overall_pl = strategy_df['cumulative_balance'].iloc[-1] - capital 184 | min_balance = min(strategy_df['cumulative_balance']) 185 | max_balance = max(strategy_df['cumulative_balance']) 186 | 187 | # Print the performance metrics 188 | print("Overall P/L: {:.2f}%".format(overall_pl_percentage)) 189 | print("Overall P/L: {:.2f}".format(overall_pl)) 190 | print("Min balance: {:.2f}".format(min_balance)) 191 | print("Max balance: {:.2f}".format(max_balance)) 192 | print("Maximum Drawdown: {:.2f}".format(max_drawdown)) 193 | print("Maximum Drawdown %: {:.2f}%".format(max_drawdown_percentage)) 194 | 195 | # Return the Strategy DataFrame 196 | return strategy_df 197 | 198 | # Plot the performance curve 199 | def plot_performance_curve(strategy_df): 200 | plt.plot(strategy_df['cumulative_balance'], label='Strategy') 201 | plt.title('Performance Curve') 202 | plt.xlabel('Date') 203 | plt.ylabel('Balance') 204 | plt.xticks(rotation=70) 205 | plt.legend() 206 | plt.show() 207 | 208 | 209 | if __name__ == '__main__': 210 | # Initialize data fetch parameters 211 | symbol = "BTC/USDT" 212 | start_date = "2022-12-1" 213 | interval = '4h' 214 | exchange = ccxt.binance() 215 | 216 | # Fetch historical OHLC data for ETH/USDT 217 | data = fetch_asset_data(symbol=symbol, start_date=start_date, interval=interval, exchange=exchange) 218 | 219 | 220 | volatility = 3 221 | 222 | # Apply supertrend formula 223 | supertrend_data = supertrend(df=data, atr_multiplier=volatility) 224 | 225 | # Generate the Signals 226 | supertrend_positions = generate_signals(supertrend_data) 227 | 228 | # Generate the Positions 229 | supertrend_positions = create_positions(supertrend_positions) 230 | 231 | # Calculate performance 232 | supertrend_df = strategy_performance(supertrend_positions, capital=100, leverage=1) 233 | print(supertrend_df) 234 | 235 | # Plot data 236 | plot_data(supertrend_positions, symbol=symbol) 237 | 238 | # Plot the performance curve 239 | plot_performance_curve(supertrend_df) 240 | --------------------------------------------------------------------------------