Timeframe
4h
Direction
Long Only
Stoploss
-26.5%
Trailing Stop
Yes
ROI
0m: 8.7%, 372m: 5.8%, 861m: 2.9%, 2221m: 0.0%
Interface Version
N/A
Startup Candles
18
Indicators
4
freqtrade/freqtrade-strategies
This strategy uses custom_stoploss() to enforce a fixed risk/reward ratio by first calculating a dynamic initial stoploss via ATR - last negative peak
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
freqtrade/freqtrade-strategies
"""
Supertrend strategy:
* Description: Generate a 3 supertrend indicators for 'buy' strategies & 3 supertrend indicators for 'sell' strategies
Buys if the 3 'buy' indicators are 'up'
Sells if the 3 'sell' indicators are 'down'
* Author: @juankysoriano (Juan Carlos Soriano)
* github: https://github.com/juankysoriano/
*** NOTE: This Supertrend strategy is just one of many possible strategies using `Supertrend` as indicator. It should on any case used at your own risk.
It comes with at least a couple of caveats:
1. The implementation for the `supertrend` indicator is based on the following discussion: https://github.com/freqtrade/freqtrade-strategies/issues/30 . Concretelly https://github.com/freqtrade/freqtrade-strategies/issues/30#issuecomment-853042401
2. The implementation for `supertrend` on this strategy is not validated; meaning this that is not proven to match the results by the paper where it was originally introduced or any other trusted academic resources
"""
##[DCD]: This will be some reverse engineering of the code below from my part. I am no Python expert so I might be wrong on some things I say.
## Do your own research when using this code for trading.
import logging
from numpy.lib import math
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.hyper import IntParameter
from pandas import DataFrame
import talib.abstract as ta
import numpy as np
# Hyperopt Libraries
import talib.abstract as ta
import pandas_ta as pta
import numpy as np # noqa
import pandas as pd # noqa
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
from functools import reduce
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,IStrategy, IntParameter)
class SupertrendHopt(IStrategy):
# Buy params, Sell params, ROI, Stoploss and Trailing Stop are values generated by 'freqtrade hyperopt --strategy Supertrend --hyperopt-loss ShortTradeDurHyperOptLoss --timerange=20210101- --timeframe=1h --spaces all'
# It's encourage you find the values that better suites your needs and risk management strategies
#
# [DCD]: Hyperopt command for finding better ROI, Stoploss and trailing stoploss settings
## freqtrade hyperopt --strategy Supertrend --hyperopt-loss ShortTradeDurHyperOptLoss --timeframe=4h --spaces roi stoploss trailing -c user_data/backtest-config.json
## freqtrade hyperopt --strategy Supertrend --hyperopt-loss SharpeHyperOptLoss --timeframe=4h --spaces roi stoploss trailing -c user_data/backtest-config.json
## freqtrade hyperopt --strategy SupertrendHopt --hyperopt-loss ShortTradeDurHyperOptLoss --timeframe=4h --spaces buy sell roi stoploss trailing -c user_data/backtest-config.json --epochs 1000
# Buy hyperspace params:
buy_params = {
"buy_m1": 4,
"buy_m2": 7,
"buy_m3": 1,
"buy_p1": 8,
"buy_p2": 9,
"buy_p3": 8,
}
# Sell hyperspace params:
sell_params = {
"sell_m1": 1,
"sell_m2": 3,
"sell_m3": 6,
"sell_p1": 16,
"sell_p2": 18,
"sell_p3": 18,
}
## [DCD]: Takeprofit levels, based on default strategy parameters.
# ROI table:
minimal_roi = {
"0": 0.087,
"372": 0.058,
"861": 0.029,
"2221": 0
}
## [DCD]: Stoploss level
# Stoploss:
stoploss = -0.265
## [DCD]: Apparently this strategy also makes use of trailing stoploss.
## Should be in hyperopt funtion then...
# Trailing stop:
trailing_stop = True
trailing_stop_positive = 0.05
trailing_stop_positive_offset = 0.144
trailing_only_offset_is_reached = False
## [DCD]: timeframe set by the original author. My backtest session proves that 4hour has better results.
timeframe = '4h'
## [DCD]: start using the strategy after this amount of candles to prevent unexpected strategy inconsistencies
## See: https://www.freqtrade.io/en/stable/strategy-customization/#strategy-startup-period
startup_candle_count = 18
## [DCD]: These settings look like hyperopt spaces to use for determining the best supertrend parameter settings
buy_m1 = IntParameter(1, 7, default=4)
buy_m2 = IntParameter(1, 7, default=4)
buy_m3 = IntParameter(1, 7, default=4)
buy_p1 = IntParameter(7, 21, default=14)
buy_p2 = IntParameter(7, 21, default=14)
buy_p3 = IntParameter(7, 21, default=14)
sell_m1 = IntParameter(1, 7, default=4)
sell_m2 = IntParameter(1, 7, default=4)
sell_m3 = IntParameter(1, 7, default=4)
sell_p1 = IntParameter(7, 21, default=14)
sell_p2 = IntParameter(7, 21, default=14)
sell_p3 = IntParameter(7, 21, default=14)
# --- Plotting ---
# Use this section if you want to plot the indicators on a chart after backtesting
## [DCD]: There is no plotting possible because the output of the Supertrend function only contains up/down indicators
## So there are no values to plot of Supertrend indicators
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
## [DCD]: This is exactly what I thought was correct to find out the correct buy space setting for hyperopt
## For each multiplier value in the buy_m1 range
for multiplier in self.buy_m1.range:
## And then for each period value in the buy_p1 ranve
for period in self.buy_p1.range:
## Make a dataframe that contains the multiplier and period and use the .self.supertrend indirator with corresponding
## mult and period (but what is STX?) - Well STX is the output of the supertrend function below and indicates the direction (up/down).
dataframe[f'supertrend_1_buy_{multiplier}_{period}'] = self.supertrend(dataframe, multiplier, period)['STX']
## Since hyperopt is not used, I guess that the default intparameter for m1 (4) and p1 (14) will be used for eacht of these for in loops
## And on and on...
for multiplier in self.buy_m2.range:
for period in self.buy_p2.range:
dataframe[f'supertrend_2_buy_{multiplier}_{period}'] = self.supertrend(dataframe, multiplier, period)['STX']
for multiplier in self.buy_m3.range:
for period in self.buy_p3.range:
dataframe[f'supertrend_3_buy_{multiplier}_{period}'] = self.supertrend(dataframe, multiplier, period)['STX']
for multiplier in self.sell_m1.range:
for period in self.sell_p1.range:
dataframe[f'supertrend_1_sell_{multiplier}_{period}'] = self.supertrend(dataframe, multiplier, period)['STX']
for multiplier in self.sell_m2.range:
for period in self.sell_p2.range:
dataframe[f'supertrend_2_sell_{multiplier}_{period}'] = self.supertrend(dataframe, multiplier, period)['STX']
for multiplier in self.sell_m3.range:
for period in self.sell_p3.range:
dataframe[f'supertrend_3_sell_{multiplier}_{period}'] = self.supertrend(dataframe, multiplier, period)['STX']
##[DCD]: This strategy actually uses 6 supertrends. Three ST's for buy positions and 3 ST's for sell signals
print(dataframe)
return dataframe
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions = []
conditions.append(
## [DCD]: The output of the indicator apparantly has a 'up' value because for all used supertrends these have to be equal == to the 'up' value
(dataframe[f'supertrend_1_buy_{self.buy_m1.value}_{self.buy_p1.value}'] == 'up') &
(dataframe[f'supertrend_2_buy_{self.buy_m2.value}_{self.buy_p2.value}'] == 'up') &
(dataframe[f'supertrend_3_buy_{self.buy_m3.value}_{self.buy_p3.value}'] == 'up') & # The three indicators are 'up' for the current candle
(dataframe['volume'] > 0) # There is at least some trading volume
)
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions = []
conditions.append(
##[DCD]: Same goes for the 'down' value. Apparantly this is output from the indicator...
(dataframe[f'supertrend_1_sell_{self.sell_m1.value}_{self.sell_p1.value}'] == 'down') &
(dataframe[f'supertrend_2_sell_{self.sell_m2.value}_{self.sell_p2.value}'] == 'down') &
(dataframe[f'supertrend_3_sell_{self.sell_m3.value}_{self.sell_p3.value}'] == 'down') & # The three indicators are 'down' for the current candle
(dataframe['volume'] > 0) # There is at least some trading volume
)
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
return dataframe
## ==========================================================
##[DCD]: This is the section where the indicator is programmed. It seems that this is a custom indicator that is used in the above code.
## I want to put this in a jupyter notebook to see what actually happens and why...
"""
Supertrend Indicator; adapted for freqtrade
from: https://github.com/freqtrade/freqtrade-strategies/issues/30
"""
def supertrend(self, dataframe: DataFrame, multiplier, period):
##[DCD]: make a copy of the entire dataframe for use in this function
df = dataframe.copy()
##[DCD]: What is the TRANGE indicator in the talib library?
df['TR'] = ta.TRANGE(df)
##[DCD]: ATR value is made from the TRANGE. Period is a value that this function gets from the strategy above
## for period in self.buy_p1.range:
df['ATR'] = ta.SMA(df['TR'], period)
##[DCD]: creation of two additional variables that seem to be strings
st = 'ST_' + str(period) + '_' + str(multiplier)
stx = 'STX_' + str(period) + '_' + str(multiplier)
# Compute basic upper and lower bands
##[DCD]: create two new dataframe columns that are calculations of the candle hig, candle low and the df atr that was calculated above...
## These bands move with the price and are different each day and look like keltner channel or other band
df['basic_ub'] = (df['high'] + df['low']) / 2 + multiplier * df['ATR']
df['basic_lb'] = (df['high'] + df['low']) / 2 - multiplier * df['ATR']
# Compute final upper and lower bands
##[DCD]: More calculations of the atr bands before making a supertrend indicator of it
df['final_ub'] = 0.00
df['final_lb'] = 0.00
for i in range(period, len(df)):
##[DCD]: the iat function is explained in: https://www.youtube.com/watch?v=UfC-jjoAauY&ab_channel=DataIndependent
## Here comparisons are being made between the upper/lower band cells and the final_ub is the final comparison
## iat is a index position reference that comes from i in the range
df['final_ub'].iat[i] = df['basic_ub'].iat[i] if df['basic_ub'].iat[i] < df['final_ub'].iat[i - 1] or df['close'].iat[i - 1] > df['final_ub'].iat[i - 1] else df['final_ub'].iat[i - 1]
df['final_lb'].iat[i] = df['basic_lb'].iat[i] if df['basic_lb'].iat[i] > df['final_lb'].iat[i - 1] or df['close'].iat[i - 1] < df['final_lb'].iat[i - 1] else df['final_lb'].iat[i - 1]
# Set the Supertrend value
##[DCD]: the supertrend is the final result of the upper and lower band comparisons
## If the df['close'] price is above the upper band, then we know which side of the supertrend we are (bullish)
df[st] = 0.00
for i in range(period, len(df)):
df[st].iat[i] = df['final_ub'].iat[i] if df[st].iat[i - 1] == df['final_ub'].iat[i - 1] and df['close'].iat[i] <= df['final_ub'].iat[i] else \
df['final_lb'].iat[i] if df[st].iat[i - 1] == df['final_ub'].iat[i - 1] and df['close'].iat[i] > df['final_ub'].iat[i] else \
df['final_lb'].iat[i] if df[st].iat[i - 1] == df['final_lb'].iat[i - 1] and df['close'].iat[i] >= df['final_lb'].iat[i] else \
df['final_ub'].iat[i] if df[st].iat[i - 1] == df['final_lb'].iat[i - 1] and df['close'].iat[i] < df['final_lb'].iat[i] else 0.00
# Mark the trend direction up/down
##[DCD]: This is the output of the function where STX apparentrly signals if we are up or down
df[stx] = np.where((df[st] > 0.00), np.where((df['close'] < df[st]), 'down', 'up'), np.NaN)
# Remove basic and final bands from the columns
##[DCD]: Removes all the calculations out of the dataframe
df.drop(['basic_ub', 'basic_lb', 'final_ub', 'final_lb'], inplace=True, axis=1)
##[DCD]:Value to use to fill holes (e.g. 0), alternately a dict/Series/DataFrame of values specifying which value to use for each
## index (for a Series) or column (for a DataFrame). Values not in the dict/Series/DataFrame will not be filled.
df.fillna(0, inplace=True)
##[DCD]: Return the dataframe with the supertrend (ST) and supertrend direction (STX) to the strategy.
return DataFrame(index=df.index, data={
'ST' : df[st],
'STX' : df[stx]
})
##[DCD]: Question, Can I Hyperopt this file??
"""
2022-01-23 12:19:35,330 - freqtrade.optimize.backtesting - INFO - Running backtesting for Strategy Supertrend
date open high low close volume supertrend_1_buy_4_8 supertrend_2_buy_7_9 supertrend_3_buy_1_8 supertrend_1_sell_1_16 supertrend_2_sell_3_18 supertrend_3_sell_6_18
0 2017-08-17 00:00:00+00:00 4261.48 4485.39 4200.74 4285.08 795.150377 nan nan nan nan nan nan
1 2017-08-18 00:00:00+00:00 4285.08 4371.52 3938.77 4108.37 1199.888264 nan nan nan nan nan nan
2 2017-08-19 00:00:00+00:00 4108.37 4184.69 3850.00 4139.98 381.309763 nan nan nan nan nan nan
3 2017-08-20 00:00:00+00:00 4120.98 4211.08 4032.62 4086.29 467.083022 nan nan nan nan nan nan
4 2017-08-21 00:00:00+00:00 4069.13 4119.62 3911.79 4016.00 691.743060 nan nan nan nan nan nan
... ... ... ... ... ... ... ... ... ... ... ... ...
1592 2021-12-26 00:00:00+00:00 50399.67 51280.00 49412.00 50775.49 22569.889140 down down up up down down
1593 2021-12-27 00:00:00+00:00 50775.48 52088.00 50449.00 50701.44 28779.582120 down down up up down down
1594 2021-12-28 00:00:00+00:00 50701.44 50704.05 47313.01 47543.74 45853.339240 down down down down down down
1595 2021-12-29 00:00:00+00:00 47543.74 48139.08 46096.99 46464.66 39498.870000 down down down down down down
1596 2021-12-30 00:00:00+00:00 46464.66 47900.00 45900.00 47120.87 30352.295690 down down down down down down
"""