""" リンク: https://github.com/freqtrade/freqtrade-strategies/blob/a9a3fc2b/user_data/strategies/Supertrend.py 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 """ from freqtrade.strategy import DecimalParameter, IntParameter, IStrategy import pandas as pd from pandas import DataFrame import talib.abstract as ta import numpy as np class Supertrend(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 INTERFACE_VERSION: int = 3 # Buy hyperspace params: buy_params = { "buy_atr_ratio_max": 0.06, "buy_m1": 4, "buy_m2": 7, "buy_m3": 1, "buy_p1": 8, "buy_p2": 9, "buy_p3": 8, "buy_rsi_min": 52, } # Sell hyperspace params: sell_params = { "sell_m1": 1, "sell_m2": 3, "sell_m3": 6, "sell_p1": 16, "sell_p2": 18, "sell_p3": 18, } # ROI table: minimal_roi = { "0": 0.04, "240": 0.02, "720": 0.01, "1440": 0 } # Stoploss: stoploss = -0.12 # Trailing stop: trailing_stop = True trailing_stop_positive = 0.02 trailing_stop_positive_offset = 0.04 trailing_only_offset_is_reached = True timeframe = '1h' startup_candle_count = 199 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) buy_rsi_min = IntParameter(48, 65, default=52) buy_atr_ratio_max = DecimalParameter(0.03, 0.10, decimals=3, default=0.06) 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) @property def protections(self): return [ { "method": "CooldownPeriod", "stop_duration_candles": 2, }, { "method": "StoplossGuard", "lookback_period_candles": 72, "trade_limit": 2, "stop_duration_candles": 24, "only_per_pair": False, }, { "method": "LowProfitPairs", "lookback_period_candles": 48, "trade_limit": 2, "stop_duration_candles": 12, "required_profit": 0.01, }, { "method": "MaxDrawdown", "lookback_period_candles": 96, "trade_limit": 5, "stop_duration_candles": 48, "max_allowed_drawdown": 0.08, }, ] def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: indicator_columns = { "ema_200": ta.EMA(dataframe, timeperiod=200), "rsi": ta.RSI(dataframe, timeperiod=14), "atr": ta.ATR(dataframe, timeperiod=14), } indicator_columns["atr_ratio"] = indicator_columns["atr"] / dataframe["close"] indicator_specs = [ ('supertrend_1_buy', self.buy_m1.range, self.buy_p1.range), ('supertrend_2_buy', self.buy_m2.range, self.buy_p2.range), ('supertrend_3_buy', self.buy_m3.range, self.buy_p3.range), ('supertrend_1_sell', self.sell_m1.range, self.sell_p1.range), ('supertrend_2_sell', self.sell_m2.range, self.sell_p2.range), ('supertrend_3_sell', self.sell_m3.range, self.sell_p3.range), ] stx_cache = {} for prefix, multiplier_range, period_range in indicator_specs: for multiplier in multiplier_range: for period in period_range: key = (multiplier, period) if key not in stx_cache: stx_cache[key] = self.supertrend(dataframe, multiplier, period)['STX'] indicator_columns[f'{prefix}_{multiplier}_{period}'] = stx_cache[key] return pd.concat([dataframe, DataFrame(indicator_columns, index=dataframe.index)], axis=1) def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( (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['close'] > dataframe['ema_200']) & (dataframe['rsi'] >= self.buy_rsi_min.value) & (dataframe['atr_ratio'] <= self.buy_atr_ratio_max.value) & (dataframe['volume'] > 0) # There is at least some trading volume ), 'enter_long'] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( (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 ), 'exit_long'] = 1 return dataframe """ Supertrend Indicator; adapted for freqtrade from: https://github.com/freqtrade/freqtrade-strategies/issues/30 """ def supertrend(self, dataframe: DataFrame, multiplier, period): price_data = dataframe[['high', 'low', 'close']].apply(pd.to_numeric, errors='coerce') true_range = ta.TRANGE(price_data) average_true_range = ta.SMA(true_range, period) mid_price = (price_data['high'] + price_data['low']) / 2.0 basic_ub = np.asarray(mid_price + multiplier * average_true_range, dtype=float) basic_lb = np.asarray(mid_price - multiplier * average_true_range, dtype=float) close = price_data['close'].to_numpy(dtype=float, copy=True) final_ub = np.zeros(len(dataframe), dtype=float) final_lb = np.zeros(len(dataframe), dtype=float) for i in range(period, len(dataframe)): previous_final_ub = final_ub[i - 1] previous_final_lb = final_lb[i - 1] previous_close = close[i - 1] final_ub[i] = ( basic_ub[i] if basic_ub[i] < previous_final_ub or previous_close > previous_final_ub else previous_final_ub ) final_lb[i] = ( basic_lb[i] if basic_lb[i] > previous_final_lb or previous_close < previous_final_lb else previous_final_lb ) st = np.zeros(len(dataframe), dtype=float) for i in range(period, len(dataframe)): previous_st = st[i - 1] current_close = close[i] if previous_st == final_ub[i - 1]: st[i] = final_ub[i] if current_close <= final_ub[i] else final_lb[i] elif previous_st == final_lb[i - 1]: st[i] = final_lb[i] if current_close >= final_lb[i] else final_ub[i] st_series = pd.Series(st, index=dataframe.index, dtype='float64').fillna(0.0) stx_series = pd.Series( np.where(close < st_series.to_numpy(copy=False), 'down', 'up'), index=dataframe.index, dtype='object', ).where(st_series > 0.0, None) return DataFrame(index=dataframe.index, data={ 'ST': st_series, 'STX': stx_series, })