class Ewt2(IStrategy): minimal_roi = {"0": 0.5} stoploss = -0.1 timeframe = '5m' informative_timeframe = '15m' sell_profit_only = False process_only_new_candles = True startup_candle_count: int = 100 # trailing_stop = True # trailing_stop_positive = 0.02 # trailing_stop_positive_offset = 0.075 # trailing_only_offset_is_reached = True buy_EWO_val = DecimalParameter(0, 3.00, default=0.75, space='buy', optimize=True, load=True) sell_EWO_val = DecimalParameter(-3.00, 0, default=-2, space='sell', optimize=True, load=True) use_custom_stoploss = False custom_trade_info = {} def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: if self.config['runmode'].value in ('live', 'dry_run'): dataframe, last_updated = self.dp.get_analyzed_dataframe( pair=pair, timeframe=self.timeframe) EWO_BUY = dataframe['EWO_BUY'].iat[-1] EWO_SELL = dataframe['EWO_SELL'].iat[-1] # If in backtest or hyperopt, get the indicator values out of the trades dict (Thanks @JoeSchr!) else: EWO_BUY = self.custom_trade_info[ trade.pair]['EWO_BUY'].loc[current_time]['EWO_BUY'] EWO_SELL = self.custom_trade_info[ trade.pair]['EWO_SELL'].loc[current_time]['EWO_SELL'] if (current_time - timedelta(minutes=600) > trade.open_date_utc) & (current_profit < 0): if EWO_BUY < 0: return 0.01 return 0.5 def informative_pairs(self): pairs = self.dp.current_whitelist() informative_pairs = [(pair, self.informative_timeframe) for pair in pairs] informative_pairs.append(('BTC/USDT', self.informative_timeframe)) return informative_pairs @staticmethod def coock_indicators(dataframe: DataFrame, metadata: dict): dataframe['ema32'] = fta.EMA(dataframe, period=32) dataframe['EWO_BUY'] = EWO(dataframe, 5, 35) dataframe['EWO_SELL'] = EWO(dataframe, 5, 35) dataframe['mfi'] = fta.MFI(dataframe) return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if not metadata['pair'] in self.custom_trade_info: self.custom_trade_info[metadata['pair']] = {} if self.timeframe == self.informative_timeframe: dataframe = self.coock_indicators(dataframe, metadata) else: assert self.dp, "DataProvider is required for multiple timeframes." informative = self.dp.get_pair_dataframe( pair=metadata['pair'], timeframe=self.informative_timeframe) informative = self.coock_indicators(informative.copy(), metadata) dataframe = merge_informative_pair(dataframe, informative, self.timeframe, self.informative_timeframe, ffill=True) # don't overwrite the base dataframe's OHLCV information skip_columns = [ (s + "_" + self.informative_timeframe) for s in ['date', 'open', 'high', 'low', 'close', 'volume'] ] dataframe.rename(columns=lambda s: s.replace( "_{}".format(self.informative_timeframe), "") if (not s in skip_columns) else s, inplace=True) informative_btc = self.dp.get_pair_dataframe( pair='BTC/USDT', timeframe=self.informative_timeframe) informative_btc['EWO_BTC'] = EWO(informative_btc, 10, 40) informative_btc['ema32_btc'] = fta.EMA(informative_btc, period=32) informative_btc['btc_open'] = informative_btc['open'] dataframe = merge_informative_pair(dataframe, informative_btc, self.timeframe, self.informative_timeframe, ffill=True) # don't overwrite the base dataframe's OHLCV information skip_columns = [ (s + "_" + self.informative_timeframe) for s in ['date', 'open', 'high', 'low', 'close', 'volume'] ] dataframe.rename(columns=lambda s: s.replace( "_{}".format(self.informative_timeframe), "") if (not s in skip_columns) else s, inplace=True) dataframe['4h_high'] = dataframe['close'].rolling(48).max() dataframe['8h_high'] = dataframe['close'].rolling(96).max() if self.dp.runmode.value in ('backtest', 'hyperopt'): self.custom_trade_info[metadata['pair']]['EWO_BUY'] = dataframe[[ 'date', 'EWO_BUY' ]].copy().set_index('date') self.custom_trade_info[metadata['pair']]['EWO_SELL'] = dataframe[[ 'date', 'EWO_SELL' ]].copy().set_index('date') return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[(( # (dataframe['8h_high'] > dataframe['close']) & ((dataframe['EWO_BUY'].shift(1) < 0.00) | (dataframe['EWO_BUY'].shift(2) < 0.00) | (dataframe['EWO_BUY'].shift(3) < 0.00)) & (dataframe['EWO_BUY'] > self.buy_EWO_val.value) # & (dataframe['EWO_BTC'] > 0.00) & (dataframe['volume'] > 0) & (dataframe['open'] > dataframe['ema32']) # & (dataframe['btc_open'] > dataframe['ema32_btc']) ) | (dataframe['EWO_BUY'] < -7.00)), 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[( (dataframe['EWO_SELL'] <= self.sell_EWO_val.value) # & (dataframe['EWO_SELL'] > dataframe['EWO_SELL'].shift(1)) & # (dataframe['EWO_SELL'].shift(1) > dataframe['EWO_SELL'].shift(2)) & & (dataframe['volume'] > 0)), 'sell'] = 1 # dataframe.loc[:, 'sell'] = 0 return dataframe def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, sell_reason: str, **kwargs) -> bool: # activate sell signal only when profit is above 1.5% and below -1.5% if sell_reason == 'sell_signal': if 0.02 > trade.calc_profit_ratio(rate): return False else: return True return True
class wtc(IStrategy): ################################ SETTINGS ################################ # 61 trades. 16/0/45 Wins/Draws/Losses. # * Avg profit: 132.53%. # Median profit: -12.97%. # Total profit: 0.80921449 BTC ( 809.21Σ%). # Avg duration 4 days, 7:47:00 min. # Objective: -15.73417 # Config: # "max_open_trades": 10, # "stake_currency": "BTC", # "stake_amount": 0.01, # "tradable_balance_ratio": 0.99, # "timeframe": "30m", # "dry_run_wallet": 0.1, # Buy hyperspace params: buy_params = { "buy_max": 0.9609, "buy_max0": 0.8633, "buy_max1": 0.9133, "buy_min": 0.0019, "buy_min0": 0.0102, "buy_min1": 0.6864, } # Sell hyperspace params: sell_params = { "sell_max": -0.7979, "sell_max0": 0.82, "sell_max1": 0.9821, "sell_min": -0.5377, "sell_min0": 0.0628, "sell_min1": 0.4461, } minimal_roi = {"0": 0.30873, "569": 0.16689, "3211": 0.06473, "7617": 0} stoploss = -0.128 ############################## END SETTINGS ############################## timeframe = '30m' buy_max = DecimalParameter(-1, 1, decimals=4, default=0.4393, space='buy') buy_min = DecimalParameter(-1, 1, decimals=4, default=-0.4676, space='buy') sell_max = DecimalParameter(-1, 1, decimals=4, default=-0.9512, space='sell') sell_min = DecimalParameter(-1, 1, decimals=4, default=0.6519, space='sell') buy_max0 = DecimalParameter(0, 1, decimals=4, default=0.4393, space='buy') buy_min0 = DecimalParameter(0, 1, decimals=4, default=-0.4676, space='buy') sell_max0 = DecimalParameter(0, 1, decimals=4, default=-0.9512, space='sell') sell_min0 = DecimalParameter(0, 1, decimals=4, default=0.6519, space='sell') buy_max1 = DecimalParameter(0, 1, decimals=4, default=0.4393, space='buy') buy_min1 = DecimalParameter(0, 1, decimals=4, default=-0.4676, space='buy') sell_max1 = DecimalParameter(0, 1, decimals=4, default=-0.9512, space='sell') sell_min1 = DecimalParameter(0, 1, decimals=4, default=0.6519, space='sell') def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # WAVETREND try: ap = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3 esa = ta.EMA(ap, 10) d = ta.EMA((ap - esa).abs(), 10) ci = (ap - esa).div(0.0015 * d) tci = ta.EMA(ci, 21) wt1 = tci wt2 = ta.SMA(np.nan_to_num(wt1), 4) dataframe['wt1'], dataframe['wt2'] = wt1, wt2 stoch = ta.STOCH(dataframe, 14) slowk = stoch['slowk'] dataframe['slowk'] = slowk # print(dataframe.iloc[:, 6:].keys()) x = dataframe.iloc[:, 6:].values # returns a numpy array min_max_scaler = preprocessing.MinMaxScaler() x_scaled = min_max_scaler.fit_transform(x) dataframe.iloc[:, 6:] = pd.DataFrame(x_scaled) # print('wt:\t', dataframe['wt'].min(), dataframe['wt'].max()) # print('stoch:\t', dataframe['stoch'].min(), dataframe['stoch'].max()) dataframe['def'] = dataframe['slowk'] - dataframe['wt1'] # print('def:\t', dataframe['def'].min(), "\t", dataframe['def'].max()) except: dataframe['wt1'], dataframe['wt2'], dataframe['def'], dataframe[ 'slowk'] = 0, 10, 100, 1000 return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[( (qtpylib.crossed_above(dataframe['wt1'], dataframe['wt2'])) & (dataframe['wt1'].between(self.buy_min0.value, self.buy_max0.value) ) & (dataframe['slowk'].between(self.buy_min1.value, self.buy_max1. value)) & (dataframe['def'].between(self.buy_min.value, self.buy_max.value)) ), 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # print(dataframe['slowk']/dataframe['wt1']) dataframe.loc[( (qtpylib.crossed_below(dataframe['wt1'], dataframe['wt2'])) & (dataframe['wt1'].between(self.sell_min0.value, self.sell_max0. value)) & (dataframe['slowk'].between(self.sell_min1.value, self.sell_max1. value)) & (dataframe['def'].between(self.sell_min.value, self.sell_max.value) )), 'sell'] = 1 return dataframe
class MIKUZEMA_v1(IStrategy): # Optimal timeframe for the strategy timeframe = '5m' # generate signals from the 1h timeframe informative_timeframe = '1h' # WARNING: ichimoku is a long indicator, if you remove or use a # shorter startup_candle_count your backtest results will be unreliable startup_candle_count = 500 # NOTE: this strat only uses candle information, so processing between # new candles is a waste of resources as nothing will change process_only_new_candles = True # ROI table: minimal_roi = { "0": 0.078, "40": 0.062, "99": 0.039, "218": 0 } stoploss = -0.294 # Buy hyperspace params: buy_params = { 'low_offset': 0.964, 'zema_len_buy': 51 } # Sell hyperspace params: sell_params = { 'high_offset': 1.004, 'zema_len_sell': 72 } low_offset = DecimalParameter(0.80, 1.20, default=1.004, space='buy', optimize=True) high_offset = DecimalParameter(0.80, 1.20, default=0.964, space='sell', optimize=True) zema_len_buy = IntParameter(30, 90, default=72, space='buy', optimize=True) zema_len_sell = IntParameter(30, 90, default=51, space='sell', optimize=True) def informative_pairs(self): pairs = self.dp.current_whitelist() informative_pairs = [(pair, self.informative_timeframe) for pair in pairs] return informative_pairs def slow_tf_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: displacement = 30 ichimoku = ftt.ichimoku(dataframe, conversion_line_period=20, base_line_periods=60, laggin_span=120, displacement=displacement ) dataframe['chikou_span'] = ichimoku['chikou_span'] # cross indicators dataframe['tenkan_sen'] = ichimoku['tenkan_sen'] dataframe['kijun_sen'] = ichimoku['kijun_sen'] # cloud, green a > b, red a < b dataframe['senkou_a'] = ichimoku['senkou_span_a'] dataframe['senkou_b'] = ichimoku['senkou_span_b'] dataframe['leading_senkou_span_a'] = ichimoku['leading_senkou_span_a'] dataframe['leading_senkou_span_b'] = ichimoku['leading_senkou_span_b'] dataframe['cloud_green'] = ichimoku['cloud_green'] * 1 dataframe['cloud_red'] = ichimoku['cloud_red'] * -1 dataframe.loc[:, 'cloud_top'] = dataframe.loc[:, ['senkou_a', 'senkou_b']].max(axis=1) dataframe.loc[:, 'cloud_bottom'] = dataframe.loc[:, ['senkou_a', 'senkou_b']].min(axis=1) # DANGER ZONE START # NOTE: Not actually the future, present data that is normally shifted forward for display as the cloud dataframe['future_green'] = (dataframe['leading_senkou_span_a'] > dataframe['leading_senkou_span_b']).astype('int') * 2 dataframe['future_red'] = (dataframe['leading_senkou_span_a'] < dataframe['leading_senkou_span_b']).astype('int') * 2 # The chikou_span is shifted into the past, so we need to be careful not to read the # current value. But if we shift it forward again by displacement it should be safe to use. # We're effectively "looking back" at where it normally appears on the chart. dataframe['chikou_high'] = ( (dataframe['chikou_span'] > dataframe['cloud_top']) ).shift(displacement).fillna(0).astype('int') dataframe['chikou_low'] = ( (dataframe['chikou_span'] < dataframe['cloud_bottom']) ).shift(displacement).fillna(0).astype('int') # DANGER ZONE END dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) ssl_down, ssl_up = ssl_atr(dataframe, 10) dataframe['ssl_down'] = ssl_down dataframe['ssl_up'] = ssl_up dataframe['ssl_ok'] = ( (ssl_up > ssl_down) ).astype('int') * 3 dataframe['ssl_bear'] = ( (ssl_up < ssl_down) ).astype('int') * 3 dataframe['ichimoku_ok'] = ( (dataframe['tenkan_sen'] > dataframe['kijun_sen']) & (dataframe['close'] > dataframe['cloud_top']) & (dataframe['future_green'] > 0) & (dataframe['chikou_high'] > 0) ).astype('int') * 4 dataframe['ichimoku_bear'] = ( (dataframe['tenkan_sen'] < dataframe['kijun_sen']) & (dataframe['close'] < dataframe['cloud_bottom']) & (dataframe['future_red'] > 0) & (dataframe['chikou_low'] > 0) ).astype('int') * 4 dataframe['ichimoku_valid'] = ( (dataframe['leading_senkou_span_b'] == dataframe['leading_senkou_span_b']) # not NaN ).astype('int') * 1 dataframe['trend_pulse'] = ( (dataframe['ichimoku_ok'] > 0) & (dataframe['ssl_ok'] > 0) ).astype('int') * 2 dataframe['bear_trend_pulse'] = ( (dataframe['ichimoku_bear'] > 0) & (dataframe['ssl_bear'] > 0) ).astype('int') * 2 dataframe['trend_over'] = ( (dataframe['ssl_ok'] == 0) | (dataframe['close'] < dataframe['cloud_top']) ).astype('int') * 1 dataframe['bear_trend_over'] = ( (dataframe['ssl_bear'] == 0) | (dataframe['close'] > dataframe['cloud_bottom']) ).astype('int') * 1 dataframe.loc[ (dataframe['trend_pulse'] > 0), 'trending'] = 3 dataframe.loc[ (dataframe['trend_over'] > 0) , 'trending'] = 0 dataframe['trending'].fillna(method='ffill', inplace=True) dataframe.loc[ (dataframe['bear_trend_pulse'] > 0), 'bear_trending'] = 3 dataframe.loc[ (dataframe['bear_trend_over'] > 0) , 'bear_trending'] = 0 dataframe['bear_trending'].fillna(method='ffill', inplace=True) return dataframe def fast_tf_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if self.config['runmode'].value == 'hyperopt': for len in range(30, 91): dataframe[f'zema_{len}'] = ftt.zema(dataframe, period=len) else: dataframe[f'zema_{self.zema_len_buy.value}'] = ftt.zema(dataframe, period=self.zema_len_buy.value) dataframe[f'zema_{self.zema_len_sell.value}'] = ftt.zema(dataframe, period=self.zema_len_sell.value) dataframe[f'zema_buy'] = ftt.zema(dataframe, period=self.zema_len_buy.value) * self.low_offset.value dataframe[f'zema_sell'] = ftt.zema(dataframe, period=self.zema_len_sell.value) * self.high_offset.value return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: assert (timeframe_to_minutes(self.timeframe) == 5), "Run this strategy at 5m." if self.timeframe == self.informative_timeframe: dataframe = self.slow_tf_indicators(dataframe, metadata) else: assert self.dp, "DataProvider is required for multiple timeframes." informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.informative_timeframe) informative = self.slow_tf_indicators(informative.copy(), metadata) dataframe = merge_informative_pair(dataframe, informative, self.timeframe, self.informative_timeframe, ffill=True) # don't overwrite the base dataframe's OHLCV information skip_columns = [(s + "_" + self.informative_timeframe) for s in ['date', 'open', 'high', 'low', 'close', 'volume']] dataframe.rename(columns=lambda s: s.replace("_{}".format(self.informative_timeframe), "") if (not s in skip_columns) else s, inplace=True) dataframe = self.fast_tf_indicators(dataframe, metadata) return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: zema = f'zema_{self.zema_len_buy.value}' dataframe.loc[ (dataframe['ichimoku_valid'] > 0) & (dataframe['bear_trending'] == 0) & (dataframe['close'] < (dataframe[zema] * self.low_offset.value)) , 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: zema = f'zema_{self.zema_len_sell.value}' dataframe.loc[ ( (dataframe['close'] > (dataframe[zema] * self.high_offset.value)) ) , 'sell'] = 1 return dataframe def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float, rate: float, time_in_force: str, sell_reason: str, current_time: 'datetime', **kwargs) -> bool: if sell_reason in ('roi',): dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) current_candle = dataframe.iloc[-1] if current_candle is not None: current_candle = current_candle.squeeze() # don't sell during ichimoku uptrend if current_candle['trending'] > 0: return False return True plot_config = { # Main plot indicators (Moving averages, ...) 'main_plot': { 'senkou_a': { 'color': 'green', 'fill_to': 'senkou_b', 'fill_label': 'Ichimoku Cloud', 'fill_color': 'rgba(0,0,0,0.2)', }, # plot senkou_b, too. Not only the area to it. 'senkou_b': { 'color': 'red', }, 'tenkan_sen': { 'color': 'blue' }, 'kijun_sen': { 'color': 'orange' }, # 'chikou_span': { 'color': 'lightgreen' }, 'ssl_up': { 'color': 'green' }, # 'ssl_down': { 'color': 'red' }, # 'ema50': { 'color': 'violet' }, # 'ema200': { 'color': 'magenta' }, 'zema_buy': { 'color': 'blue' }, 'zema_sell': { 'color': 'orange' }, }, 'subplots': { "Trend": { 'trending': {'color': 'green'}, 'bear_trending': {'color': 'red'}, }, "Bull": { 'trend_pulse': {'color': 'blue'}, 'trending': {'color': 'orange'}, 'trend_over': {'color': 'red'}, }, "Bull Signals": { 'ichimoku_ok': {'color': 'green'}, 'ssl_ok': {'color': 'red'}, }, "Bear": { 'bear_trend_pulse': {'color': 'blue'}, 'bear_trending': {'color': 'orange'}, 'bear_trend_over': {'color': 'red'}, }, "Bear Signals": { 'ichimoku_bear': {'color': 'green'}, 'ssl_bear': {'color': 'red'}, }, "Misc": { 'ichimoku_valid': {'color': 'green'}, }, } }
class HyperoptableStrategy(IStrategy): """ Default Strategy provided by freqtrade bot. Please do not modify this strategy, it's intended for internal use only. Please look at the SampleStrategy in the user_data/strategy directory or strategy repository https://github.com/freqtrade/freqtrade-strategies for samples and inspiration. """ INTERFACE_VERSION = 2 # Minimal ROI designed for the strategy minimal_roi = {"40": 0.0, "30": 0.01, "20": 0.02, "0": 0.04} # Optimal stoploss designed for the strategy stoploss = -0.10 # Optimal ticker interval for the strategy timeframe = '5m' # Optional order type mapping order_types = { 'buy': 'limit', 'sell': 'limit', 'stoploss': 'limit', 'stoploss_on_exchange': False } # Number of candles the strategy requires before producing valid signals startup_candle_count: int = 20 # Optional time in force for orders order_time_in_force = { 'buy': 'gtc', 'sell': 'gtc', } buy_params = { 'buy_rsi': 35, # Intentionally not specified, so "default" is tested # 'buy_plusdi': 0.4 } sell_params = {'sell_rsi': 74, 'sell_minusdi': 0.4} buy_rsi = IntParameter([0, 50], default=30, space='buy') buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy') sell_rsi = IntParameter(low=50, high=100, default=70, space='sell') sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell', load=False) def informative_pairs(self): """ Define additional, informative pair/interval combinations to be cached from the exchange. These pair/interval combinations are non-tradeable, unless they are part of the whitelist as well. For more information, please consult the documentation :return: List of tuples in the format (pair, interval) Sample: return [("ETH/USDT", "5m"), ("BTC/USDT", "15m"), ] """ return [] def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Adds several different TA indicators to the given DataFrame Performance Note: For the best performance be frugal on the number of indicators you are using. Let uncomment only the indicator you are using in your strategies or your hyperopt configuration, otherwise you will waste your memory and CPU usage. :param dataframe: Dataframe with data from the exchange :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ # Momentum Indicator # ------------------------------------ # ADX dataframe['adx'] = ta.ADX(dataframe) # MACD macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] dataframe['macdsignal'] = macd['macdsignal'] dataframe['macdhist'] = macd['macdhist'] # Minus Directional Indicator / Movement dataframe['minus_di'] = ta.MINUS_DI(dataframe) # Plus Directional Indicator / Movement dataframe['plus_di'] = ta.PLUS_DI(dataframe) # RSI dataframe['rsi'] = ta.RSI(dataframe) # Stoch fast stoch_fast = ta.STOCHF(dataframe) dataframe['fastd'] = stoch_fast['fastd'] dataframe['fastk'] = stoch_fast['fastk'] # Bollinger bands bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] # EMA - Exponential Moving Average dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame :param metadata: Additional information, like the currently traded pair :return: DataFrame with buy column """ dataframe.loc[((dataframe['rsi'] < self.buy_rsi.value) & (dataframe['fastd'] < 35) & (dataframe['adx'] > 30) & (dataframe['plus_di'] > self.buy_plusdi.value)) | ((dataframe['adx'] > 65) & (dataframe['plus_di'] > self.buy_plusdi.value)), 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame :param metadata: Additional information, like the currently traded pair :return: DataFrame with buy column """ dataframe.loc[( ((qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) | (qtpylib.crossed_above(dataframe['fastd'], 70))) & (dataframe['adx'] > 10) & (dataframe['minus_di'] > 0)) | ((dataframe['adx'] > 70) & (dataframe['minus_di'] > self.sell_minusdi.value)), 'sell'] = 1 return dataframe
class StrategyTestV3(IStrategy): """ Strategy used by tests freqtrade bot. Please do not modify this strategy, it's intended for internal use only. Please look at the SampleStrategy in the user_data/strategy directory or strategy repository https://github.com/freqtrade/freqtrade-strategies for samples and inspiration. """ INTERFACE_VERSION = 3 # Minimal ROI designed for the strategy minimal_roi = {"40": 0.0, "30": 0.01, "20": 0.02, "0": 0.04} # Optimal stoploss designed for the strategy stoploss = -0.10 # Optimal timeframe for the strategy timeframe = '5m' # Optional order type mapping order_types = { 'entry': 'limit', 'exit': 'limit', 'stoploss': 'limit', 'stoploss_on_exchange': False } # Number of candles the strategy requires before producing valid signals startup_candle_count: int = 20 # Optional time in force for orders order_time_in_force = { 'entry': 'gtc', 'exit': 'gtc', } buy_params = { 'buy_rsi': 35, # Intentionally not specified, so "default" is tested # 'buy_plusdi': 0.4 } sell_params = {'sell_rsi': 74, 'sell_minusdi': 0.4} buy_rsi = IntParameter([0, 50], default=30, space='buy') buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy') sell_rsi = IntParameter(low=50, high=100, default=70, space='sell') sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell', load=False) protection_enabled = BooleanParameter(default=True) protection_cooldown_lookback = IntParameter([0, 50], default=30) # TODO: Can this work with protection tests? (replace HyperoptableStrategy implicitly ... ) # @property # def protections(self): # prot = [] # if self.protection_enabled.value: # prot.append({ # "method": "CooldownPeriod", # "stop_duration_candles": self.protection_cooldown_lookback.value # }) # return prot def informative_pairs(self): return [] def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # Momentum Indicator # ------------------------------------ # ADX dataframe['adx'] = ta.ADX(dataframe) # MACD macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] dataframe['macdsignal'] = macd['macdsignal'] dataframe['macdhist'] = macd['macdhist'] # Minus Directional Indicator / Movement dataframe['minus_di'] = ta.MINUS_DI(dataframe) # Plus Directional Indicator / Movement dataframe['plus_di'] = ta.PLUS_DI(dataframe) # RSI dataframe['rsi'] = ta.RSI(dataframe) # Stoch fast stoch_fast = ta.STOCHF(dataframe) dataframe['fastd'] = stoch_fast['fastd'] dataframe['fastk'] = stoch_fast['fastk'] # Bollinger bands bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] # EMA - Exponential Moving Average dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[((dataframe['rsi'] < self.buy_rsi.value) & (dataframe['fastd'] < 35) & (dataframe['adx'] > 30) & (dataframe['plus_di'] > self.buy_plusdi.value)) | ((dataframe['adx'] > 65) & (dataframe['plus_di'] > self.buy_plusdi.value)), 'enter_long'] = 1 dataframe.loc[( qtpylib.crossed_below(dataframe['rsi'], self.sell_rsi.value)), 'enter_short'] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[( ((qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) | (qtpylib.crossed_above(dataframe['fastd'], 70))) & (dataframe['adx'] > 10) & (dataframe['minus_di'] > 0)) | ((dataframe['adx'] > 70) & (dataframe['minus_di'] > self.sell_minusdi.value)), 'exit_long'] = 1 dataframe.loc[( qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)), 'exit_short'] = 1 return dataframe def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float, max_leverage: float, side: str, **kwargs) -> float: # Return 3.0 in all cases. # Bot-logic must make sure it's an allowed leverage and eventually adjust accordingly. return 3.0 def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, min_stake: float, max_stake: float, **kwargs): if current_profit < -0.0075: orders = trade.select_filled_orders(trade.entry_side) return round(orders[0].cost, 0) return None
class HyperoptableStrategy(StrategyTestV2): """ Default Strategy provided by freqtrade bot. Please do not modify this strategy, it's intended for internal use only. Please look at the SampleStrategy in the user_data/strategy directory or strategy repository https://github.com/freqtrade/freqtrade-strategies for samples and inspiration. """ buy_params = { 'buy_rsi': 35, # Intentionally not specified, so "default" is tested # 'buy_plusdi': 0.4 } sell_params = { 'sell_rsi': 74, 'sell_minusdi': 0.4 } buy_rsi = IntParameter([0, 50], default=30, space='buy') buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy') sell_rsi = IntParameter(low=50, high=100, default=70, space='sell') sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell', load=False) protection_enabled = BooleanParameter(default=True) protection_cooldown_lookback = IntParameter([0, 50], default=30) @property def protections(self): prot = [] if self.protection_enabled.value: prot.append({ "method": "CooldownPeriod", "stop_duration_candles": self.protection_cooldown_lookback.value }) return prot def informative_pairs(self): """ Define additional, informative pair/interval combinations to be cached from the exchange. These pair/interval combinations are non-tradeable, unless they are part of the whitelist as well. For more information, please consult the documentation :return: List of tuples in the format (pair, interval) Sample: return [("ETH/USDT", "5m"), ("BTC/USDT", "15m"), ] """ return [] def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame :param metadata: Additional information, like the currently traded pair :return: DataFrame with buy column """ dataframe.loc[ ( (dataframe['rsi'] < self.buy_rsi.value) & (dataframe['fastd'] < 35) & (dataframe['adx'] > 30) & (dataframe['plus_di'] > self.buy_plusdi.value) ) | ( (dataframe['adx'] > 65) & (dataframe['plus_di'] > self.buy_plusdi.value) ), 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame :param metadata: Additional information, like the currently traded pair :return: DataFrame with buy column """ dataframe.loc[ ( ( (qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) | (qtpylib.crossed_above(dataframe['fastd'], 70)) ) & (dataframe['adx'] > 10) & (dataframe['minus_di'] > 0) ) | ( (dataframe['adx'] > 70) & (dataframe['minus_di'] > self.sell_minusdi.value) ), 'sell'] = 1 return dataframe
class SMAOG(IStrategy): INTERFACE_VERSION = 2 buy_params = { "base_nb_candles_buy": 26, "buy_trigger": "SMA", "low_offset": 0.968, "pair_is_bad_0_threshold": 0.555, "pair_is_bad_1_threshold": 0.172, "pair_is_bad_2_threshold": 0.198, } sell_params = { "base_nb_candles_sell": 28, "high_offset": 0.985, "sell_trigger": "EMA", } base_nb_candles_buy = IntParameter( 16, 45, default=buy_params['base_nb_candles_buy'], space='buy', optimize=False, load=True) base_nb_candles_sell = IntParameter( 16, 45, default=sell_params['base_nb_candles_sell'], space='sell', optimize=False, load=True) low_offset = DecimalParameter(0.8, 0.99, default=buy_params['low_offset'], space='buy', optimize=False, load=True) high_offset = DecimalParameter(0.8, 1.1, default=sell_params['high_offset'], space='sell', optimize=False, load=True) buy_trigger = CategoricalParameter(ma_types.keys(), default=buy_params['buy_trigger'], space='buy', optimize=False, load=True) sell_trigger = CategoricalParameter(ma_types.keys(), default=sell_params['sell_trigger'], space='sell', optimize=False, load=True) pair_is_bad_0_threshold = DecimalParameter(0.0, 0.600, default=0.220, space='buy', optimize=True, load=True) pair_is_bad_1_threshold = DecimalParameter(0.0, 0.350, default=0.090, space='buy', optimize=True, load=True) pair_is_bad_2_threshold = DecimalParameter(0.0, 0.200, default=0.060, space='buy', optimize=True, load=True) timeframe = '5m' stoploss = -0.23 minimal_roi = { "0": 10, } trailing_stop = True trailing_only_offset_is_reached = True trailing_stop_positive = 0.01 trailing_stop_positive_offset = 0.02 use_sell_signal = True sell_profit_only = False ignore_roi_if_buy_signal = False process_only_new_candles = True startup_candle_count = 400 def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if not self.config['runmode'].value == 'hyperopt': dataframe['ma_offset_buy'] = ma_types[self.buy_trigger.value]( dataframe, int( self.base_nb_candles_buy.value)) * self.low_offset.value dataframe['ma_offset_sell'] = ma_types[self.sell_trigger.value]( dataframe, int( self.base_nb_candles_sell.value)) * self.high_offset.value dataframe['pair_is_bad'] = ( (((dataframe['open'].rolling(144).min() - dataframe['close']) / dataframe['close']) >= self.pair_is_bad_0_threshold.value) | (((dataframe['open'].rolling(12).min() - dataframe['close']) / dataframe['close']) >= self.pair_is_bad_1_threshold.value) | (((dataframe['open'].rolling(2).min() - dataframe['close']) / dataframe['close']) >= self.pair_is_bad_2_threshold.value) ).astype('int') dataframe['ema_50'] = ta.EMA(dataframe, timeperiod=50) dataframe['ema_200'] = ta.EMA(dataframe, timeperiod=200) dataframe['rsi_exit'] = ta.RSI(dataframe, timeperiod=2) return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if self.config['runmode'].value == 'hyperopt': dataframe['ma_offset_buy'] = ma_types[self.buy_trigger.value]( dataframe, int( self.base_nb_candles_buy.value)) * self.low_offset.value dataframe['pair_is_bad'] = ( (((dataframe['open'].rolling(144).min() - dataframe['close']) / dataframe['close']) >= self.pair_is_bad_0_threshold.value) | (((dataframe['open'].rolling(12).min() - dataframe['close']) / dataframe['close']) >= self.pair_is_bad_1_threshold.value) | (((dataframe['open'].rolling(2).min() - dataframe['close']) / dataframe['close']) >= self.pair_is_bad_2_threshold.value) ).astype('int') dataframe['ema_50'] = ta.EMA(dataframe, timeperiod=50) dataframe['ema_200'] = ta.EMA(dataframe, timeperiod=200) dataframe.loc[((dataframe['ema_50'] > dataframe['ema_200']) & (dataframe['close'] > dataframe['ema_200']) & (dataframe['pair_is_bad'] < 1) & (dataframe['close'] < dataframe['ma_offset_buy']) & (dataframe['volume'] > 0)), 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if self.config['runmode'].value == 'hyperopt': dataframe['ma_offset_sell'] = ta.EMA( dataframe, int( self.base_nb_candles_sell.value)) * self.high_offset.value dataframe.loc[((dataframe['close'] > dataframe['ma_offset_sell']) & ( (dataframe['open'] < dataframe['open'].shift(1)) | (dataframe['rsi_exit'] < 50) | (dataframe['rsi_exit'] < dataframe['rsi_exit'].shift(1))) & (dataframe['volume'] > 0)), 'sell'] = 1 return dataframe
class BigZ04_TSL4(IStrategy): INTERFACE_VERSION = 2 minimal_roi = {"0": 100.0} stoploss = -0.99 # effectively disabled. timeframe = '5m' inf_1h = '1h' # Sell signal use_sell_signal = True sell_profit_only = False sell_profit_offset = 0.001 # it doesn't meant anything, just to guarantee there is a minimal profit. ignore_roi_if_buy_signal = False # Trailing stoploss trailing_stop = False trailing_only_offset_is_reached = False trailing_stop_positive = 0.01 trailing_stop_positive_offset = 0.025 # Custom stoploss use_custom_stoploss = True # Run "populate_indicators()" only for new candle. process_only_new_candles = True # Number of candles the strategy requires before producing valid signals startup_candle_count: int = 400 # Optional order type mapping. order_types = { 'buy': 'market', 'sell': 'market', 'stoploss': 'market', 'stoploss_on_exchange': False } buy_params = { ############# # Enable/Disable conditions "buy_condition_0_enable": True, "buy_condition_1_enable": True, "buy_condition_2_enable": True, "buy_condition_3_enable": True, "buy_condition_4_enable": True, "buy_condition_5_enable": True, "buy_condition_6_enable": True, "buy_condition_7_enable": True, "buy_condition_8_enable": True, "buy_condition_9_enable": True, "buy_condition_10_enable": True, "buy_condition_11_enable": True, "buy_condition_12_enable": True, "buy_condition_13_enable": False, } # V1 original # Sell hyperspace params: sell_params = { "base_nb_candles_sell": 49, "high_offset": 1.006, "pHSL": -0.08, "pPF_1": 0.016, "pSL_1": 0.011, "pPF_2": 0.080, "pSL_2": 0.040, } ############################################################################ # Buy buy_condition_0_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_1_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_2_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_3_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_4_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_5_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_6_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_7_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_8_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_9_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_10_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_11_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_12_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_condition_13_enable = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True) buy_bb20_close_bblowerband_safe_1 = DecimalParameter(0.950, 1.050, default=0.989, decimals=3, space='buy', optimize=False, load=True) buy_bb20_close_bblowerband_safe_2 = DecimalParameter(0.700, 1.100, default=0.982, decimals=2, space='buy', optimize=False, load=True) buy_volume_pump_1 = DecimalParameter(0.1, 0.9, default=0.4, space='buy', decimals=1, optimize=False, load=True) buy_volume_drop_1 = DecimalParameter(1, 10, default=3.8, space='buy', decimals=1, optimize=False, load=True) buy_volume_drop_2 = DecimalParameter(1, 10, default=3, space='buy', decimals=1, optimize=False, load=True) buy_volume_drop_3 = DecimalParameter(1, 10, default=2.7, space='buy', decimals=1, optimize=False, load=True) buy_rsi_1h_0 = DecimalParameter(55.0, 85.0, default=71.0, space='buy', decimals=1, optimize=False, load=True) buy_rsi_1h_1a = DecimalParameter(65.0, 78.0, default=69.0, space='buy', decimals=1, optimize=False, load=True) buy_rsi_1h_1 = DecimalParameter(10.0, 40.0, default=16.5, space='buy', decimals=1, optimize=False, load=True) buy_rsi_1h_2 = DecimalParameter(10.0, 40.0, default=15.0, space='buy', decimals=1, optimize=False, load=True) buy_rsi_1h_3 = DecimalParameter(10.0, 40.0, default=20.0, space='buy', decimals=1, optimize=True, load=True) buy_rsi_1h_4 = DecimalParameter(10.0, 40.0, default=35.0, space='buy', decimals=1, optimize=False, load=True) buy_rsi_1h_5 = DecimalParameter(10.0, 60.0, default=39.0, space='buy', decimals=1, optimize=False, load=True) buy_rsi_0 = DecimalParameter(10.0, 40.0, default=30.0, space='buy', decimals=1, optimize=False, load=True) buy_rsi_1 = DecimalParameter(10.0, 40.0, default=28.0, space='buy', decimals=1, optimize=True, load=True) buy_rsi_2 = DecimalParameter(7.0, 40.0, default=10.0, space='buy', decimals=1, optimize=False, load=True) buy_rsi_3 = DecimalParameter(7.0, 40.0, default=14.2, space='buy', decimals=1, optimize=False, load=True) buy_macd_1 = DecimalParameter(0.01, 0.09, default=0.02, space='buy', decimals=2, optimize=False, load=True) buy_macd_2 = DecimalParameter(0.01, 0.09, default=0.03, space='buy', decimals=2, optimize=False, load=True) buy_dip_0 = DecimalParameter(1.015, 1.040, default=1.024, space='buy', decimals=3, optimize=False, load=True) # hyperopt parameters for custom_stoploss() trade_time = IntParameter(25, 65, default=35, space='sell', optimize=False, load=True) rsi_1h_val = IntParameter(25, 45, default=32, space='sell', optimize=False, load=True) narrow_stop = DecimalParameter(1.005, 1.030, default=1.020, space='sell', decimals=3, optimize=False, load=True) wide_stop = DecimalParameter(1.010, 1.045, default=1.035, space='sell', decimals=3, optimize=False, load=True) # hyperopt parameters for SMAOffsetProtectOptV1 sell signal base_nb_candles_sell = IntParameter(5, 80, default=49, space='sell', optimize=False, load=True) high_offset = DecimalParameter(0.99, 1.1, default=1.006, space='sell', optimize=False, load=True) # trailing stoploss hyperopt parameters # hard stoploss profit pHSL = DecimalParameter(-0.200, -0.040, default=-0.08, decimals=3, space='sell', optimize=False, load=True) # profit threshold 1, trigger point, SL_1 is used pPF_1 = DecimalParameter(0.008, 0.020, default=0.016, decimals=3, space='sell', optimize=False, load=True) pSL_1 = DecimalParameter(0.008, 0.020, default=0.011, decimals=3, space='sell', optimize=False, load=True) # profit threshold 2, SL_2 is used pPF_2 = DecimalParameter(0.040, 0.100, default=0.080, decimals=3, space='sell', optimize=False, load=True) pSL_2 = DecimalParameter(0.020, 0.070, default=0.040, decimals=3, space='sell', optimize=False, load=True) def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, sell_reason: str, **kwargs) -> bool: return True def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, current_profit: float, **kwargs): return False # new custom stoploss, both hard and trailing functions. Trailing stoploss first rises at a slower # rate than the current rate until a profit threshold is reached, after which it rises at a constant # percentage as per a normal trailing stoploss. This allows more margin for pull-backs during a rise. def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: # hard stoploss profit HSL = self.pHSL.value PF_1 = self.pPF_1.value SL_1 = self.pSL_1.value PF_2 = self.pPF_2.value SL_2 = self.pSL_2.value # For profits between PF_1 and PF_2 the stoploss (sl_profit) used is linearly interpolated # between the values of SL_1 and SL_2. For all profits above PL_2 the sl_profit value # rises linearly with current profit, for profits below PF_1 the hard stoploss profit is used. if (current_profit > PF_2): sl_profit = SL_2 + (current_profit - PF_2) elif (current_profit > PF_1): sl_profit = SL_1 + ((current_profit - PF_1) * (SL_2 - SL_1) / (PF_2 - PF_1)) else: sl_profit = HSL return stoploss_from_open(sl_profit, current_profit) def informative_pairs(self): pairs = self.dp.current_whitelist() informative_pairs = [(pair, '1h') for pair in pairs] return informative_pairs def informative_1h_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: assert self.dp, "DataProvider is required for multiple timeframes." # Get the informative pair informative_1h = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.inf_1h) # EMA informative_1h['ema_50'] = ta.SMA(informative_1h, timeperiod=50) informative_1h['ema_200'] = ta.SMA(informative_1h, timeperiod=200) # RSI informative_1h['rsi'] = ta.RSI(informative_1h, timeperiod=14) bollinger = qtpylib.bollinger_bands( qtpylib.typical_price(informative_1h), window=20, stds=2) informative_1h['bb_lowerband'] = bollinger['lower'] informative_1h['bb_middleband'] = bollinger['mid'] informative_1h['bb_upperband'] = bollinger['upper'] return informative_1h def normal_tf_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] dataframe['volume_mean_slow'] = dataframe['volume'].rolling( window=48).mean() # EMA dataframe['ema_200'] = ta.SMA(dataframe, timeperiod=200) dataframe['ema_26'] = ta.EMA(dataframe, timeperiod=26) dataframe['ema_12'] = ta.EMA(dataframe, timeperiod=12) # MACD dataframe['macd'], dataframe['signal'], dataframe['hist'] = ta.MACD( dataframe['close'], fastperiod=12, slowperiod=26, signalperiod=9) # SMA dataframe['sma_5'] = ta.EMA(dataframe, timeperiod=5) # RSI dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) # ------ ATR stuff dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) # Calculate all ma_sell values for val in self.base_nb_candles_sell.range: dataframe[f'ma_sell_{val}'] = ta.EMA(dataframe, timeperiod=val) return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # The indicators for the 1h informative timeframe informative_1h = self.informative_1h_indicators(dataframe, metadata) dataframe = merge_informative_pair(dataframe, informative_1h, self.timeframe, self.inf_1h, ffill=True) # The indicators for the normal (5m) timeframe dataframe = self.normal_tf_indicators(dataframe, metadata) return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] conditions.append( (self.buy_condition_12_enable.value & (dataframe['close'] > dataframe['ema_200']) & (dataframe['close'] > dataframe['ema_200_1h']) & (dataframe['close'] < dataframe['bb_lowerband'] * 0.993) & (dataframe['low'] < dataframe['bb_lowerband'] * 0.985) & (dataframe['close'].shift() > dataframe['bb_lowerband']) & (dataframe['rsi_1h'] < 72.8) & (dataframe['open'] > dataframe['close']) & (dataframe['volume_mean_slow'] > dataframe['volume_mean_slow'].shift(48) * self.buy_volume_pump_1.value) & (dataframe['volume_mean_slow'] * self.buy_volume_pump_1.value < dataframe['volume_mean_slow'].shift(48)) & (dataframe['volume'] < (dataframe['volume'].shift() * self.buy_volume_drop_1.value)) & ((dataframe['open'] - dataframe['close']) < dataframe['bb_upperband'].shift(2) - dataframe['bb_lowerband'].shift(2)) & (dataframe['volume'] > 0))) conditions.append(( self.buy_condition_11_enable.value & (dataframe['close'] > dataframe['ema_200']) & (dataframe['hist'] > 0) & (dataframe['hist'].shift() > 0) & (dataframe['hist'].shift(2) > 0) & (dataframe['hist'].shift(3) > 0) & (dataframe['hist'].shift(5) > 0) & (dataframe['bb_middleband'] - dataframe['bb_middleband'].shift(5) > dataframe['close'] / 200) & (dataframe['bb_middleband'] - dataframe['bb_middleband'].shift(10) > dataframe['close'] / 100) & ((dataframe['bb_upperband'] - dataframe['bb_lowerband']) < (dataframe['close'] * 0.1)) & ((dataframe['open'].shift() - dataframe['close'].shift()) < (dataframe['close'] * 0.018)) & (dataframe['rsi'] > 51) & (dataframe['open'] < dataframe['close']) & (dataframe['open'].shift() > dataframe['close'].shift()) & (dataframe['close'] > dataframe['bb_middleband']) & (dataframe['close'].shift() < dataframe['bb_middleband'].shift()) & (dataframe['low'].shift(2) > dataframe['bb_middleband'].shift(2)) & (dataframe['volume'] > 0) # Make sure Volume is not 0 )) conditions.append(( self.buy_condition_0_enable.value & (dataframe['close'] > dataframe['ema_200']) & (dataframe['rsi'] < self.buy_rsi_0.value) & ((dataframe['close'] * self.buy_dip_0.value < dataframe['open'].shift(3)) | (dataframe['close'] * self.buy_dip_0.value < dataframe['open'].shift(2)) | (dataframe['close'] * self.buy_dip_0.value < dataframe['open'].shift(1))) & (dataframe['rsi_1h'] < self.buy_rsi_1h_0.value) & (dataframe['volume_mean_slow'] > dataframe['volume_mean_slow'].shift(48) * self.buy_volume_pump_1.value) & (dataframe['volume_mean_slow'] * self.buy_volume_pump_1.value < dataframe['volume_mean_slow'].shift(48)) & (dataframe['volume'] > 0) # Make sure Volume is not 0 )) conditions.append( (self.buy_condition_1_enable.value & (dataframe['close'] > dataframe['ema_200']) & (dataframe['close'] > dataframe['ema_200_1h']) & (dataframe['close'] < dataframe['bb_lowerband'] * self.buy_bb20_close_bblowerband_safe_1.value) & (dataframe['rsi_1h'] < self.buy_rsi_1h_1a.value) & (dataframe['open'] > dataframe['close']) & (dataframe['volume_mean_slow'] > dataframe['volume_mean_slow'].shift(48) * self.buy_volume_pump_1.value) & (dataframe['volume_mean_slow'] * self.buy_volume_pump_1.value < dataframe['volume_mean_slow'].shift(48)) & (dataframe['volume'] < (dataframe['volume'].shift() * self.buy_volume_drop_1.value)) & ((dataframe['open'] - dataframe['close']) < dataframe['bb_upperband'].shift(2) - dataframe['bb_lowerband'].shift(2)) & (dataframe['volume'] > 0))) conditions.append( (self.buy_condition_2_enable.value & (dataframe['close'] > dataframe['ema_200']) & (dataframe['close'] < dataframe['bb_lowerband'] * self.buy_bb20_close_bblowerband_safe_2.value) & (dataframe['volume_mean_slow'] > dataframe['volume_mean_slow'].shift(48) * self.buy_volume_pump_1.value) & (dataframe['volume_mean_slow'] * self.buy_volume_pump_1.value < dataframe['volume_mean_slow'].shift(48)) & (dataframe['volume'] < (dataframe['volume'].shift() * self.buy_volume_drop_1.value)) & (dataframe['open'] - dataframe['close'] < dataframe['bb_upperband'].shift(2) - dataframe['bb_lowerband'].shift(2)) & (dataframe['volume'] > 0))) conditions.append( (self.buy_condition_3_enable.value & (dataframe['close'] > dataframe['ema_200_1h']) & (dataframe['close'] < dataframe['bb_lowerband']) & (dataframe['rsi'] < self.buy_rsi_3.value) & (dataframe['volume_mean_slow'] > dataframe['volume_mean_slow'].shift(48) * self.buy_volume_pump_1.value) & (dataframe['volume_mean_slow'] * self.buy_volume_pump_1.value < dataframe['volume_mean_slow'].shift(48)) & (dataframe['volume'] < (dataframe['volume'].shift() * self.buy_volume_drop_3.value)) & (dataframe['volume'] > 0))) conditions.append( (self.buy_condition_4_enable.value & (dataframe['rsi_1h'] < self.buy_rsi_1h_1.value) & (dataframe['close'] < dataframe['bb_lowerband']) & (dataframe['volume_mean_slow'] > dataframe['volume_mean_slow'].shift(48) * self.buy_volume_pump_1.value) & (dataframe['volume_mean_slow'] * self.buy_volume_pump_1.value < dataframe['volume_mean_slow'].shift(48)) & (dataframe['volume'] < (dataframe['volume'].shift() * self.buy_volume_drop_1.value)) & (dataframe['volume'] > 0))) conditions.append(( self.buy_condition_5_enable.value & (dataframe['close'] > dataframe['ema_200']) & (dataframe['close'] > dataframe['ema_200_1h']) & (dataframe['ema_26'] > dataframe['ema_12']) & ((dataframe['ema_26'] - dataframe['ema_12']) > (dataframe['open'] * self.buy_macd_1.value)) & ((dataframe['ema_26'].shift() - dataframe['ema_12'].shift()) > (dataframe['open'] / 100)) & (dataframe['close'] < (dataframe['bb_lowerband'])) & (dataframe['volume'] < (dataframe['volume'].shift() * self.buy_volume_drop_1.value)) & (dataframe['volume_mean_slow'] > dataframe['volume_mean_slow'].shift(48) * self.buy_volume_pump_1.value) & (dataframe['volume_mean_slow'] * self.buy_volume_pump_1.value < dataframe['volume_mean_slow'].shift(48)) & (dataframe['volume'] > 0) # Make sure Volume is not 0 )) conditions.append( (self.buy_condition_6_enable.value & (dataframe['rsi_1h'] < self.buy_rsi_1h_5.value) & (dataframe['ema_26'] > dataframe['ema_12']) & ((dataframe['ema_26'] - dataframe['ema_12']) > (dataframe['open'] * self.buy_macd_2.value)) & ((dataframe['ema_26'].shift() - dataframe['ema_12'].shift()) > (dataframe['open'] / 100)) & (dataframe['close'] < (dataframe['bb_lowerband'])) & (dataframe['volume_mean_slow'] > dataframe['volume_mean_slow'].shift(48) * self.buy_volume_pump_1.value) & (dataframe['volume_mean_slow'] * self.buy_volume_pump_1.value < dataframe['volume_mean_slow'].shift(48)) & (dataframe['volume'] < (dataframe['volume'].shift() * self.buy_volume_drop_1.value)) & (dataframe['volume'] > 0))) conditions.append( (self.buy_condition_7_enable.value & (dataframe['rsi_1h'] < self.buy_rsi_1h_2.value) & (dataframe['ema_26'] > dataframe['ema_12']) & ((dataframe['ema_26'] - dataframe['ema_12']) > (dataframe['open'] * self.buy_macd_1.value)) & ((dataframe['ema_26'].shift() - dataframe['ema_12'].shift()) > (dataframe['open'] / 100)) & (dataframe['volume'] < (dataframe['volume'].shift() * self.buy_volume_drop_1.value)) & (dataframe['volume_mean_slow'] > dataframe['volume_mean_slow'].shift(48) * self.buy_volume_pump_1.value) & (dataframe['volume_mean_slow'] * self.buy_volume_pump_1.value < dataframe['volume_mean_slow'].shift(48)) & (dataframe['volume'] > 0))) conditions.append( (self.buy_condition_8_enable.value & (dataframe['rsi_1h'] < self.buy_rsi_1h_3.value) & (dataframe['rsi'] < self.buy_rsi_1.value) & (dataframe['volume'] < (dataframe['volume'].shift() * self.buy_volume_drop_1.value)) & (dataframe['volume'] > 0))) conditions.append( (self.buy_condition_9_enable.value & (dataframe['rsi_1h'] < self.buy_rsi_1h_4.value) & (dataframe['rsi'] < self.buy_rsi_2.value) & (dataframe['volume'] < (dataframe['volume'].shift() * self.buy_volume_drop_1.value)) & (dataframe['volume_mean_slow'] > dataframe['volume_mean_slow'].shift(48) * self.buy_volume_pump_1.value) & (dataframe['volume_mean_slow'] * self.buy_volume_pump_1.value < dataframe['volume_mean_slow'].shift(48)) & (dataframe['volume'] > 0))) conditions.append( (self.buy_condition_10_enable.value & (dataframe['rsi_1h'] < self.buy_rsi_1h_4.value) & (dataframe['close_1h'] < dataframe['bb_lowerband_1h']) & (dataframe['hist'] > 0) & (dataframe['hist'].shift(2) < 0) & (dataframe['rsi'] < 40.5) & (dataframe['hist'] > dataframe['close'] * 0.0012) & (dataframe['open'] < dataframe['close']) & (dataframe['volume'] > 0))) 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: dataframe.loc[((dataframe['close'] > dataframe['bb_middleband'] * 1.01) & # Don't be gready, sell fast (dataframe['volume'] > 0) # Make sure Volume is not 0 ), 'sell'] = 0 return dataframe
class Solipsis4(IStrategy): ## Buy Space Hyperopt Variables # Base Pair Params base_mp = IntParameter(10, 50, default=30, space='buy') base_rmi_max = IntParameter(30, 60, default=50, space='buy') base_rmi_min = IntParameter(0, 30, default=20, space='buy') base_ma_streak = IntParameter(1, 4, default=1, space='buy') base_rmi_streak = IntParameter(3, 8, default=3, space='buy') base_trigger = CategoricalParameter(['pcc', 'rmi', 'none'], default='rmi', space='buy', optimize=False) inf_pct_adr = DecimalParameter(0.70, 0.99, default=0.80, space='buy') # BTC Informative xbtc_guard = CategoricalParameter(['strict', 'lazy', 'none'], default='lazy', space='buy', optimize=True) xbtc_base_rmi = IntParameter(20, 70, default=40, space='buy') # BTC / ETH Stake Parameters xtra_base_stake_rmi = IntParameter(10, 50, default=50, space='buy') xtra_base_fiat_rmi = IntParameter(30, 70, default=50, space='buy') ## Sell Space Params are being "hijacked" for custom_stoploss and dynamic_roi # Dynamic ROI droi_trend_type = CategoricalParameter(['rmi', 'ssl', 'candle', 'any'], default='any', space='sell', optimize=True) droi_pullback = CategoricalParameter([True, False], default=True, space='sell', optimize=True) droi_pullback_amount = DecimalParameter(0.005, 0.02, default=0.005, space='sell') droi_pullback_respect_table = CategoricalParameter([True, False], default=False, space='sell', optimize=True) # Custom Stoploss cstp_threshold = DecimalParameter(-0.05, 0, default=-0.03, space='sell') cstp_bail_how = CategoricalParameter(['roc', 'time', 'any'], default='roc', space='sell', optimize=True) cstp_bail_roc = DecimalParameter(-0.05, -0.01, default=-0.03, space='sell') cstp_bail_time = IntParameter(720, 1440, default=720, space='sell') timeframe = '5m' inf_timeframe = '1h' buy_params = { 'base_ma_streak': 1, 'base_mp': 12, 'base_rmi_max': 50, 'base_rmi_min': 20, 'base_rmi_streak': 3, 'inf_pct_adr': 950, 'xbtc_base_rmi': 20, 'xbtc_guard': 'none', 'xtra_base_fiat_rmi': 45, 'xtra_base_stake_rmi': 13 } sell_params = { 'droi_pullback': True, 'droi_pullback_amount': 0.006, 'droi_pullback_respect_table': False, 'droi_trend_type': 'any' } minimal_roi = {"0": 0.01, "1440": 0} # Enable or disable these as desired # Must be enabled when hyperopting the respective spaces use_dynamic_roi = True use_custom_stoploss = True stoploss = -0.10 # Recommended use_sell_signal = False sell_profit_only = False ignore_roi_if_buy_signal = False # Required startup_candle_count: int = 233 process_only_new_candles = False # Strategy Specific Variable Storage custom_trade_info = {} custom_fiat = "USD" # Only relevant if stake is BTC or ETH custom_btc_inf = False # Don't change this. """ Informative Pair Definitions """ def informative_pairs(self): # add all whitelisted pairs on informative timeframe pairs = self.dp.current_whitelist() informative_pairs = [(pair, self.inf_timeframe) for pair in pairs] # add extra informative pairs if the stake is BTC or ETH if self.config['stake_currency'] in ('BTC', 'ETH'): for pair in pairs: coin, stake = pair.split('/') coin_fiat = f"{coin}/{self.custom_fiat}" informative_pairs += [(coin_fiat, self.timeframe)] stake_fiat = f"{self.config['stake_currency']}/{self.custom_fiat}" informative_pairs += [(stake_fiat, self.timeframe)] # if BTC/STAKE is not in whitelist, add it as an informative pair on both timeframes else: btc_stake = f"BTC/{self.config['stake_currency']}" if not btc_stake in pairs: informative_pairs += [(btc_stake, self.timeframe)] return informative_pairs """ Indicator Definitions """ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if not metadata['pair'] in self.custom_trade_info: self.custom_trade_info[metadata['pair']] = {} ## Base Timeframe / Pair dataframe['kama'] = ta.KAMA(dataframe, length=233) # RMI: https://www.tradingview.com/script/kwIt9OgQ-Relative-Momentum-Index/ dataframe['rmi'] = cta.RMI(dataframe, length=24, mom=5) # Momentum Pinball: https://www.tradingview.com/script/fBpVB1ez-Momentum-Pinball-Indicator/ dataframe['roc-mp'] = ta.ROC(dataframe, timeperiod=1) dataframe['mp'] = ta.RSI(dataframe['roc-mp'], timeperiod=3) # MA Streak: https://www.tradingview.com/script/Yq1z7cIv-MA-Streak-Can-Show-When-a-Run-Is-Getting-Long-in-the-Tooth/ dataframe['mastreak'] = cta.mastreak(dataframe, period=4) # Percent Change Channel: https://www.tradingview.com/script/6wwAWXA1-MA-Streak-Change-Channel/ upper, mid, lower = cta.pcc(dataframe, period=40, mult=3) dataframe['pcc-lowerband'] = lower dataframe['pcc-upperband'] = upper lookup_idxs = dataframe.index.values - ( abs(dataframe['mastreak'].values) + 1) valid_lookups = lookup_idxs >= 0 dataframe['sbc'] = np.nan dataframe.loc[valid_lookups, 'sbc'] = dataframe['close'].to_numpy()[ lookup_idxs[valid_lookups].astype(int)] dataframe['streak-roc'] = 100 * (dataframe['close'] - dataframe['sbc']) / dataframe['sbc'] # Trends, Peaks and Crosses dataframe['candle-up'] = np.where( dataframe['close'] >= dataframe['close'].shift(), 1, 0) dataframe['candle-up-trend'] = np.where( dataframe['candle-up'].rolling(5).sum() >= 3, 1, 0) dataframe['rmi-up'] = np.where( dataframe['rmi'] >= dataframe['rmi'].shift(), 1, 0) dataframe['rmi-up-trend'] = np.where( dataframe['rmi-up'].rolling(5).sum() >= 3, 1, 0) dataframe['rmi-dn'] = np.where( dataframe['rmi'] <= dataframe['rmi'].shift(), 1, 0) dataframe['rmi-dn-count'] = dataframe['rmi-dn'].rolling(8).sum() dataframe['streak-bo'] = np.where( dataframe['streak-roc'] < dataframe['pcc-lowerband'], 1, 0) dataframe['streak-bo-count'] = dataframe['streak-bo'].rolling(8).sum() # Indicators used only for ROI and Custom Stoploss ssldown, sslup = cta.SSLChannels_ATR(dataframe, length=21) dataframe['sroc'] = cta.SROC(dataframe, roclen=21, emalen=13, smooth=21) dataframe['ssl-dir'] = np.where(sslup > ssldown, 'up', 'down') # Base pair informative timeframe indicators informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.inf_timeframe) # Get the "average day range" between the 1d high and 1d low to set up guards informative['1d-high'] = informative['close'].rolling(24).max() informative['1d-low'] = informative['close'].rolling(24).min() informative['adr'] = informative['1d-high'] - informative['1d-low'] dataframe = merge_informative_pair(dataframe, informative, self.timeframe, self.inf_timeframe, ffill=True) # Other stake specific informative indicators # e.g if stake is BTC and current coin is XLM (pair: XLM/BTC) if self.config['stake_currency'] in ('BTC', 'ETH'): coin, stake = metadata['pair'].split('/') fiat = self.custom_fiat coin_fiat = f"{coin}/{fiat}" stake_fiat = f"{stake}/{fiat}" # Informative COIN/FIAT e.g. XLM/USD - Base Timeframe coin_fiat_tf = self.dp.get_pair_dataframe(pair=coin_fiat, timeframe=self.timeframe) dataframe[f"{fiat}_rmi"] = cta.RMI(coin_fiat_tf, length=55, mom=5) # Informative STAKE/FIAT e.g. BTC/USD - Base Timeframe stake_fiat_tf = self.dp.get_pair_dataframe( pair=stake_fiat, timeframe=self.timeframe) dataframe[f"{stake}_rmi"] = cta.RMI(stake_fiat_tf, length=55, mom=5) # Informatives for BTC/STAKE if not in whitelist else: pairs = self.dp.current_whitelist() btc_stake = f"BTC/{self.config['stake_currency']}" if not btc_stake in pairs: self.custom_btc_inf = True # BTC/STAKE - Base Timeframe btc_stake_tf = self.dp.get_pair_dataframe( pair=btc_stake, timeframe=self.timeframe) dataframe['BTC_rmi'] = cta.RMI(btc_stake_tf, length=55, mom=5) dataframe['BTC_close'] = btc_stake_tf['close'] dataframe['BTC_kama'] = ta.KAMA(btc_stake_tf, length=144) # Slam some indicators into the trade_info dict so we can dynamic roi and custom stoploss in backtest if self.dp.runmode.value in ('backtest', 'hyperopt'): self.custom_trade_info[metadata['pair']]['sroc'] = dataframe[[ 'date', 'sroc' ]].copy().set_index('date') self.custom_trade_info[metadata['pair']]['ssl-dir'] = dataframe[[ 'date', 'ssl-dir' ]].copy().set_index('date') self.custom_trade_info[ metadata['pair']]['rmi-up-trend'] = dataframe[[ 'date', 'rmi-up-trend' ]].copy().set_index('date') self.custom_trade_info[ metadata['pair']]['candle-up-trend'] = dataframe[[ 'date', 'candle-up-trend' ]].copy().set_index('date') return dataframe """ Buy Signal """ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] # Informative Timeframe Guards conditions.append(( dataframe['close'] <= dataframe[f"1d-low_{self.inf_timeframe}"] + (self.inf_pct_adr.value * dataframe[f"adr_{self.inf_timeframe}"]))) # Base Timeframe Guards conditions.append( (dataframe['rmi-dn-count'] >= self.base_rmi_streak.value) & (dataframe['streak-bo-count'] >= self.base_ma_streak.value) & (dataframe['rmi'] <= self.base_rmi_max.value) & (dataframe['rmi'] >= self.base_rmi_min.value) & (dataframe['mp'] <= self.base_mp.value)) # Base Timeframe Trigger if self.base_trigger.value == 'pcc': conditions.append( qtpylib.crossed_above(dataframe['streak-roc'], dataframe['pcc-lowerband'])) if self.base_trigger.value == 'rmi': conditions.append(dataframe['rmi-up-trend'] == 1) # Extra conditions for */BTC and */ETH stakes on additional informative pairs if self.config['stake_currency'] in ('BTC', 'ETH'): conditions.append(( dataframe[f"{self.custom_fiat}_rmi"] > self.xtra_base_fiat_rmi. value) | (dataframe[f"{self.config['stake_currency']}_rmi"] < self.xtra_base_stake_rmi.value)) # Extra conditions for BTC/STAKE if not in whitelist else: if self.custom_btc_inf: if self.xbtc_guard.value == 'strict': conditions.append( ((dataframe['BTC_rmi'] > self.xbtc_base_rmi.value) & (dataframe['BTC_close'] > dataframe['BTC_kama']))) if self.xbtc_guard.value == 'lazy': conditions.append( (dataframe['close'] > dataframe['kama']) | ((dataframe['BTC_rmi'] > self.xbtc_base_rmi.value) & (dataframe['BTC_close'] > dataframe['BTC_kama']))) conditions.append(dataframe['volume'].gt(0)) if conditions: dataframe.loc[reduce(lambda x, y: x & y, conditions), 'buy'] = 1 return dataframe """ Sell Signal """ def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['sell'] = 0 return dataframe """ Custom Stoploss """ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: trade_dur = int( (current_time.timestamp() - trade.open_date_utc.timestamp()) // 60) if self.config['runmode'].value in ('live', 'dry_run'): dataframe, last_updated = self.dp.get_analyzed_dataframe( pair=pair, timeframe=self.timeframe) sroc = dataframe['sroc'].iat[-1] # If in backtest or hyperopt, get the indicator values out of the trades dict (Thanks @JoeSchr!) else: sroc = self.custom_trade_info[ trade.pair]['sroc'].loc[current_time]['sroc'] if current_profit < self.cstp_threshold.value: if self.cstp_bail_how.value == 'roc' or self.cstp_bail_how.value == 'any': # Dynamic bailout based on rate of change if (sroc / 100) <= self.cstp_bail_roc.value: return 0.001 if self.cstp_bail_how.value == 'time' or self.cstp_bail_how.value == 'any': # Dynamic bailout based on time if trade_dur > self.cstp_bail_time.value: return 0.001 return 1 """ Freqtrade ROI Overload for dynamic ROI functionality """ def min_roi_reached_dynamic( self, trade: Trade, current_profit: float, current_time: datetime, trade_dur: int) -> Tuple[Optional[int], Optional[float]]: minimal_roi = self.minimal_roi _, table_roi = self.min_roi_reached_entry(trade_dur) # see if we have the data we need to do this, otherwise fall back to the standard table if self.custom_trade_info and trade and trade.pair in self.custom_trade_info: if self.config['runmode'].value in ('live', 'dry_run'): dataframe, last_updated = self.dp.get_analyzed_dataframe( pair=trade.pair, timeframe=self.timeframe) rmi_trend = dataframe['rmi-up-trend'].iat[-1] candle_trend = dataframe['candle-up-trend'].iat[-1] ssl_dir = dataframe['ssl-dir'].iat[-1] # If in backtest or hyperopt, get the indicator values out of the trades dict (Thanks @JoeSchr!) else: rmi_trend = self.custom_trade_info[trade.pair][ 'rmi-up-trend'].loc[current_time]['rmi-up-trend'] candle_trend = self.custom_trade_info[trade.pair][ 'candle-up-trend'].loc[current_time]['candle-up-trend'] ssl_dir = self.custom_trade_info[ trade.pair]['ssl-dir'].loc[current_time]['ssl-dir'] min_roi = table_roi max_profit = trade.calc_profit_ratio(trade.max_rate) pullback_value = (max_profit - self.droi_pullback_amount.value) in_trend = False if self.droi_trend_type.value == 'rmi' or self.droi_trend_type.value == 'any': if rmi_trend == 1: in_trend = True if self.droi_trend_type.value == 'ssl' or self.droi_trend_type.value == 'any': if ssl_dir == 'up': in_trend = True if self.droi_trend_type.value == 'candle' or self.droi_trend_type.value == 'any': if candle_trend == 1: in_trend = True # Force the ROI value high if in trend if (in_trend == True): min_roi = 100 # If pullback is enabled, allow to sell if a pullback from peak has happened regardless of trend if self.droi_pullback.value == True and (current_profit < pullback_value): if self.droi_pullback_respect_table.value == True: min_roi = table_roi else: min_roi = current_profit / 2 else: min_roi = table_roi return trade_dur, min_roi # Change here to allow loading of the dynamic_roi settings def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: trade_dur = int( (current_time.timestamp() - trade.open_date_utc.timestamp()) // 60) if self.use_dynamic_roi: _, roi = self.min_roi_reached_dynamic(trade, current_profit, current_time, trade_dur) else: _, roi = self.min_roi_reached_entry(trade_dur) if roi is None: return False else: return current_profit > roi """ Trade Timeout Overloads """ def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: bid_strategy = self.config.get('bid_strategy', {}) ob = self.dp.orderbook(pair, 1) current_price = ob[f"{bid_strategy['price_side']}s"][0][0] if current_price > order['price'] * 1.01: return True return False def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: ask_strategy = self.config.get('ask_strategy', {}) ob = self.dp.orderbook(pair, 1) current_price = ob[f"{ask_strategy['price_side']}s"][0][0] if current_price < order['price'] * 0.99: return True return False
class CryptoFrog(IStrategy): # ROI table - this strat REALLY benefits from roi and trailing hyperopt: minimal_roi = {"0": 0.213, "39": 0.103, "96": 0.037, "166": 0} # Stoploss: stoploss = -0.085 # Trailing stop: trailing_stop = True trailing_stop_positive = 0.01 trailing_stop_positive_offset = 0.047 trailing_only_offset_is_reached = False use_custom_stoploss = True custom_stop = { # Linear Decay Parameters 'decay-time': 166, # minutes to reach end, I find it works well to match this to the final ROI value - default 1080 'decay-delay': 0, # minutes to wait before decay starts 'decay-start': -0.085, # -0.32118, # -0.07163, # starting value: should be the same or smaller than initial stoploss - default -0.30 'decay-end': -0.02, # ending value - default -0.03 # Profit and TA 'cur-min-diff': 0.03, # diff between current and minimum profit to move stoploss up to min profit point 'cur-threshold': -0.02, # how far negative should current profit be before we consider moving it up based on cur/min or roc 'roc-bail': -0.03, # value for roc to use for dynamic bailout 'rmi-trend': 50, # rmi-slow value to pause stoploss decay 'bail-how': 'immediate', # set the stoploss to the atr offset below current price, or immediate # Positive Trailing 'pos-trail': True, # enable trailing once positive 'pos-threshold': 0.005, # trail after how far positive 'pos-trail-dist': 0.015 # how far behind to place the trail } # Dynamic ROI droi_trend_type = CategoricalParameter(['rmi', 'ssl', 'candle', 'any'], default='any', space='sell', optimize=True) droi_pullback = CategoricalParameter([True, False], default=True, space='sell', optimize=True) droi_pullback_amount = DecimalParameter(0.005, 0.02, default=0.005, space='sell') droi_pullback_respect_table = CategoricalParameter([True, False], default=False, space='sell', optimize=True) # Custom Stoploss cstp_threshold = DecimalParameter(-0.05, 0, default=-0.03, space='sell') cstp_bail_how = CategoricalParameter(['roc', 'time', 'any'], default='roc', space='sell', optimize=True) cstp_bail_roc = DecimalParameter(-0.05, -0.01, default=-0.03, space='sell') cstp_bail_time = IntParameter(720, 1440, default=720, space='sell') stoploss = custom_stop['decay-start'] custom_trade_info = {} custom_current_price_cache: TTLCache = TTLCache(maxsize=100, ttl=300) # 5 minutes # run "populate_indicators" only for new candle process_only_new_candles = False # Experimental settings (configuration will overide these if set) use_sell_signal = True sell_profit_only = False ignore_roi_if_buy_signal = False use_dynamic_roi = True timeframe = '5m' informative_timeframe = '1h' # Optional order type mapping order_types = { 'buy': 'limit', 'sell': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': False } plot_config = { 'main_plot': { 'Smooth_HA_H': { 'color': 'orange' }, 'Smooth_HA_L': { 'color': 'yellow' }, }, 'subplots': { "StochRSI": { 'srsi_k': { 'color': 'blue' }, 'srsi_d': { 'color': 'red' }, }, "MFI": { 'mfi': { 'color': 'green' }, }, "BBEXP": { 'bbw_expansion': { 'color': 'orange' }, }, "FAST": { 'fastd': { 'color': 'red' }, 'fastk': { 'color': 'blue' }, }, "SQZMI": { 'sqzmi': { 'color': 'lightgreen' }, }, "VFI": { 'vfi': { 'color': 'lightblue' }, }, "DMI": { 'dmi_plus': { 'color': 'orange' }, 'dmi_minus': { 'color': 'yellow' }, }, "EMACO": { 'emac_1h': { 'color': 'red' }, 'emao_1h': { 'color': 'blue' }, }, } } def informative_pairs(self): pairs = self.dp.current_whitelist() #pairs.append("BTC/USDT") #pairs.append("ETH/USDT") informative_pairs = [(pair, self.informative_timeframe) for pair in pairs] return informative_pairs ## smoothed Heiken Ashi def HA(self, dataframe, smoothing=None): df = dataframe.copy() df['HA_Close'] = (df['open'] + df['high'] + df['low'] + df['close']) / 4 df.reset_index(inplace=True) ha_open = [(df['open'][0] + df['close'][0]) / 2] [ ha_open.append((ha_open[i] + df['HA_Close'].values[i]) / 2) for i in range(0, len(df) - 1) ] df['HA_Open'] = ha_open df.set_index('index', inplace=True) df['HA_High'] = df[['HA_Open', 'HA_Close', 'high']].max(axis=1) df['HA_Low'] = df[['HA_Open', 'HA_Close', 'low']].min(axis=1) if smoothing is not None: sml = abs(int(smoothing)) if sml > 0: df['Smooth_HA_O'] = ta.EMA(df['HA_Open'], sml) df['Smooth_HA_C'] = ta.EMA(df['HA_Close'], sml) df['Smooth_HA_H'] = ta.EMA(df['HA_High'], sml) df['Smooth_HA_L'] = ta.EMA(df['HA_Low'], sml) return df def hansen_HA(self, informative_df, period=6): dataframe = informative_df.copy() dataframe['hhclose'] = (dataframe['open'] + dataframe['high'] + dataframe['low'] + dataframe['close']) / 4 dataframe['hhopen'] = ( (dataframe['open'].shift(2) + dataframe['close'].shift(2)) / 2 ) #it is not the same as real heikin ashi since I found that this is better. dataframe['hhhigh'] = dataframe[['open', 'close', 'high']].max(axis=1) dataframe['hhlow'] = dataframe[['open', 'close', 'low']].min(axis=1) dataframe['emac'] = ta.SMA( dataframe['hhclose'], timeperiod=period) #to smooth out the data and thus less noise. dataframe['emao'] = ta.SMA(dataframe['hhopen'], timeperiod=period) return {'emac': dataframe['emac'], 'emao': dataframe['emao']} ## detect BB width expansion to indicate possible volatility def bbw_expansion(self, bbw_rolling, mult=1.1): bbw = list(bbw_rolling) m = 0.0 for i in range(len(bbw) - 1): if bbw[i] > m: m = bbw[i] if (bbw[-1] > (m * mult)): return 1 return 0 ## do_indicator style a la Obelisk strategies def do_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # Stoch fast - mainly due to 5m timeframes stoch_fast = ta.STOCHF(dataframe) dataframe['fastd'] = stoch_fast['fastd'] dataframe['fastk'] = stoch_fast['fastk'] #StochRSI for double checking things period = 14 smoothD = 3 SmoothK = 3 dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) stochrsi = (dataframe['rsi'] - dataframe['rsi'].rolling(period).min() ) / (dataframe['rsi'].rolling(period).max() - dataframe['rsi'].rolling(period).min()) dataframe['srsi_k'] = stochrsi.rolling(SmoothK).mean() * 100 dataframe['srsi_d'] = dataframe['srsi_k'].rolling(smoothD).mean() # Bollinger Bands because obviously bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=1) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] # SAR Parabol - probably don't need this dataframe['sar'] = ta.SAR(dataframe) ## confirm wideboi variance signal with bbw expansion dataframe["bb_width"] = ( (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["bb_middleband"]) dataframe['bbw_expansion'] = dataframe['bb_width'].rolling( window=4).apply(self.bbw_expansion) # confirm entry and exit on smoothed HA dataframe = self.HA(dataframe, 4) # thanks to Hansen_Khornelius for this idea that I apply to the 1hr informative # https://github.com/hansen1015/freqtrade_strategy hansencalc = self.hansen_HA(dataframe, 6) dataframe['emac'] = hansencalc['emac'] dataframe['emao'] = hansencalc['emao'] # money flow index (MFI) for in/outflow of money, like RSI adjusted for vol dataframe['mfi'] = fta.MFI(dataframe) ## sqzmi to detect quiet periods dataframe['sqzmi'] = fta.SQZMI(dataframe) #, MA=hansencalc['emac']) # Volume Flow Indicator (MFI) for volume based on the direction of price movement dataframe['vfi'] = fta.VFI(dataframe, period=14) dmi = fta.DMI(dataframe, period=14) dataframe['dmi_plus'] = dmi['DI+'] dataframe['dmi_minus'] = dmi['DI-'] dataframe['adx'] = fta.ADX(dataframe, period=14) ## for stoploss - all from Solipsis4 ## simple ATR and ROC for stoploss dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) dataframe['roc'] = ta.ROC(dataframe, timeperiod=9) dataframe['rmi'] = RMI(dataframe, length=24, mom=5) ssldown, sslup = SSLChannels_ATR(dataframe, length=21) dataframe['sroc'] = SROC(dataframe, roclen=21, emalen=13, smooth=21) dataframe['ssl-dir'] = np.where(sslup > ssldown, 'up', 'down') dataframe['rmi-up'] = np.where( dataframe['rmi'] >= dataframe['rmi'].shift(), 1, 0) dataframe['rmi-up-trend'] = np.where( dataframe['rmi-up'].rolling(5).sum() >= 3, 1, 0) dataframe['candle-up'] = np.where( dataframe['close'] >= dataframe['close'].shift(), 1, 0) dataframe['candle-up-trend'] = np.where( dataframe['candle-up'].rolling(5).sum() >= 3, 1, 0) return dataframe ## stolen from Obelisk's Ichi strat code and backtest blog post, and Solipsis4 def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # Populate/update the trade data if there is any, set trades to false if not live/dry self.custom_trade_info[metadata['pair']] = self.populate_trades( metadata['pair']) if self.config['runmode'].value in ('backtest', 'hyperopt'): assert (timeframe_to_minutes(self.timeframe) <= 30), "Backtest this strategy in 5m or 1m timeframe." if self.timeframe == self.informative_timeframe: dataframe = self.do_indicators(dataframe, metadata) else: if not self.dp: return dataframe informative = self.dp.get_pair_dataframe( pair=metadata['pair'], timeframe=self.informative_timeframe) informative = self.do_indicators(informative.copy(), metadata) dataframe = merge_informative_pair(dataframe, informative, self.timeframe, self.informative_timeframe, ffill=True) skip_columns = [(s + "_" + self.informative_timeframe) for s in [ 'date', 'open', 'high', 'low', 'close', 'volume', 'emac', 'emao' ]] dataframe.rename(columns=lambda s: s.replace( "_{}".format(self.informative_timeframe), "") if (not s in skip_columns) else s, inplace=True) # Slam some indicators into the trade_info dict so we can dynamic roi and custom stoploss in backtest if self.dp.runmode.value in ('backtest', 'hyperopt'): self.custom_trade_info[metadata['pair']]['roc'] = dataframe[[ 'date', 'roc' ]].copy().set_index('date') self.custom_trade_info[metadata['pair']]['atr'] = dataframe[[ 'date', 'atr' ]].copy().set_index('date') self.custom_trade_info[metadata['pair']]['sroc'] = dataframe[[ 'date', 'sroc' ]].copy().set_index('date') self.custom_trade_info[metadata['pair']]['ssl-dir'] = dataframe[[ 'date', 'ssl-dir' ]].copy().set_index('date') self.custom_trade_info[ metadata['pair']]['rmi-up-trend'] = dataframe[[ 'date', 'rmi-up-trend' ]].copy().set_index('date') self.custom_trade_info[ metadata['pair']]['candle-up-trend'] = dataframe[[ 'date', 'candle-up-trend' ]].copy().set_index('date') return dataframe ## cryptofrog signals def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[( ( ## close ALWAYS needs to be lower than the heiken low at 5m (dataframe['close'] < dataframe['Smooth_HA_L']) & ## Hansen's HA EMA at informative timeframe (dataframe['emac_1h'] < dataframe['emao_1h'])) & (( ## potential uptick incoming so buy (dataframe['bbw_expansion'] == 1) & (dataframe['sqzmi'] == False) & ((dataframe['mfi'] < 20) | (dataframe['dmi_minus'] > 30))) | ( # this tries to find extra buys in undersold regions (dataframe['close'] < dataframe['sar']) & ((dataframe['srsi_d'] >= dataframe['srsi_k']) & (dataframe['srsi_d'] < 30)) & ((dataframe['fastd'] > dataframe['fastk']) & (dataframe['fastd'] < 23)) & (dataframe['mfi'] < 30)) | ( # find smaller temporary dips in sideways (((dataframe['dmi_minus'] > 30) & qtpylib.crossed_above( dataframe['dmi_minus'], dataframe['dmi_plus'])) & (dataframe['close'] < dataframe['bb_lowerband'])) | ( ## if nothing else is making a buy signal ## just throw in any old SQZMI shit based fastd ## this needs work! (dataframe['sqzmi'] == True) & ((dataframe['fastd'] > dataframe['fastk']) & (dataframe['fastd'] < 20)))) ## volume sanity checks & (dataframe['vfi'] < 0.0) & (dataframe['volume'] > 0))), 'buy'] = 1 return dataframe ## more going on here def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[( ( ## close ALWAYS needs to be higher than the heiken high at 5m (dataframe['close'] > dataframe['Smooth_HA_H']) & ## Hansen's HA EMA at informative timeframe (dataframe['emac_1h'] > dataframe['emao_1h'])) & ( ## try to find oversold regions with a corresponding BB expansion ((dataframe['bbw_expansion'] == 1) & ((dataframe['mfi'] > 80) | (dataframe['dmi_plus'] > 30))) ## volume sanity checks & (dataframe['vfi'] > 0.0) & (dataframe['volume'] > 0))), 'sell'] = 1 return dataframe """ Everything from here completely stolen from the godly work of @werkkrew Custom Stoploss """ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: trade_dur = int( (current_time.timestamp() - trade.open_date_utc.timestamp()) // 60) if self.config['runmode'].value in ('live', 'dry_run'): dataframe, last_updated = self.dp.get_analyzed_dataframe( pair=pair, timeframe=self.timeframe) sroc = dataframe['sroc'].iat[-1] # If in backtest or hyperopt, get the indicator values out of the trades dict (Thanks @JoeSchr!) else: sroc = self.custom_trade_info[ trade.pair]['sroc'].loc[current_time]['sroc'] if current_profit < self.cstp_threshold.value: if self.cstp_bail_how.value == 'roc' or self.cstp_bail_how.value == 'any': # Dynamic bailout based on rate of change if (sroc / 100) <= self.cstp_bail_roc.value: return 0.001 if self.cstp_bail_how.value == 'time' or self.cstp_bail_how.value == 'any': # Dynamic bailout based on time if trade_dur > self.cstp_bail_time.value: return 0.001 return 1 """ Freqtrade ROI Overload for dynamic ROI functionality """ def min_roi_reached_dynamic( self, trade: Trade, current_profit: float, current_time: datetime, trade_dur: int) -> Tuple[Optional[int], Optional[float]]: minimal_roi = self.minimal_roi _, table_roi = self.min_roi_reached_entry(trade_dur) # see if we have the data we need to do this, otherwise fall back to the standard table if self.custom_trade_info and trade and trade.pair in self.custom_trade_info: if self.config['runmode'].value in ('live', 'dry_run'): dataframe, last_updated = self.dp.get_analyzed_dataframe( pair=trade.pair, timeframe=self.timeframe) rmi_trend = dataframe['rmi-up-trend'].iat[-1] candle_trend = dataframe['candle-up-trend'].iat[-1] ssl_dir = dataframe['ssl-dir'].iat[-1] # If in backtest or hyperopt, get the indicator values out of the trades dict (Thanks @JoeSchr!) else: rmi_trend = self.custom_trade_info[trade.pair][ 'rmi-up-trend'].loc[current_time]['rmi-up-trend'] candle_trend = self.custom_trade_info[trade.pair][ 'candle-up-trend'].loc[current_time]['candle-up-trend'] ssl_dir = self.custom_trade_info[ trade.pair]['ssl-dir'].loc[current_time]['ssl-dir'] min_roi = table_roi max_profit = trade.calc_profit_ratio(trade.max_rate) pullback_value = (max_profit - self.droi_pullback_amount.value) in_trend = False if self.droi_trend_type.value == 'rmi' or self.droi_trend_type.value == 'any': if rmi_trend == 1: in_trend = True if self.droi_trend_type.value == 'ssl' or self.droi_trend_type.value == 'any': if ssl_dir == 'up': in_trend = True if self.droi_trend_type.value == 'candle' or self.droi_trend_type.value == 'any': if candle_trend == 1: in_trend = True # Force the ROI value high if in trend if (in_trend == True): min_roi = 100 # If pullback is enabled, allow to sell if a pullback from peak has happened regardless of trend if self.droi_pullback.value == True and (current_profit < pullback_value): if self.droi_pullback_respect_table.value == True: min_roi = table_roi else: min_roi = current_profit / 2 else: min_roi = table_roi return trade_dur, min_roi # Change here to allow loading of the dynamic_roi settings def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: trade_dur = int( (current_time.timestamp() - trade.open_date_utc.timestamp()) // 60) if self.use_dynamic_roi: _, roi = self.min_roi_reached_dynamic(trade, current_profit, current_time, trade_dur) else: _, roi = self.min_roi_reached_entry(trade_dur) if roi is None: return False else: return current_profit > roi # Get the current price from the exchange (or local cache) def get_current_price(self, pair: str, refresh: bool) -> float: if not refresh: rate = self.custom_current_price_cache.get(pair) # Check if cache has been invalidated if rate: return rate ask_strategy = self.config.get('ask_strategy', {}) if ask_strategy.get('use_order_book', False): ob = self.dp.orderbook(pair, 1) rate = ob[f"{ask_strategy['price_side']}s"][0][0] else: ticker = self.dp.ticker(pair) rate = ticker['last'] self.custom_current_price_cache[pair] = rate return rate """ Stripped down version from Schism, meant only to update the price data a bit more frequently than the default instead of getting all sorts of trade information """ def populate_trades(self, pair: str) -> dict: # Initialize the trades dict if it doesn't exist, persist it otherwise if not pair in self.custom_trade_info: self.custom_trade_info[pair] = {} # init the temp dicts and set the trade stuff to false trade_data = {} trade_data['active_trade'] = False # active trade stuff only works in live and dry, not backtest if self.config['runmode'].value in ('live', 'dry_run'): # find out if we have an open trade for this pair active_trade = Trade.get_trades([ Trade.pair == pair, Trade.is_open.is_(True), ]).all() # if so, get some information if active_trade: # get current price and update the min/max rate current_rate = self.get_current_price(pair, True) active_trade[0].adjust_min_max_rates(current_rate) return trade_data # nested hyperopt class class HyperOpt: # defining as dummy, so that no error is thrown about missing # sell indicator space when hyperopting for all spaces @staticmethod def indicator_space() -> List[Dimension]: return []
class Solipsis5(IStrategy): ## Buy Space Hyperopt Variables # Base Pair Params base_mp = IntParameter(10, 50, default=30, space='buy', load=True, optimize=True) base_rmi_max = IntParameter(30, 60, default=50, space='buy', load=True, optimize=True) base_rmi_min = IntParameter(0, 30, default=20, space='buy', load=True, optimize=True) base_ma_streak = IntParameter(1, 4, default=1, space='buy', load=True, optimize=True) base_rmi_streak = IntParameter(3, 8, default=3, space='buy', load=True, optimize=True) base_trigger = CategoricalParameter(['pcc', 'rmi', 'none'], default='rmi', space='buy', load=True, optimize=True) inf_pct_adr = DecimalParameter(0.70, 0.99, default=0.80, space='buy', load=True, optimize=True) # BTC Informative xbtc_guard = CategoricalParameter(['strict', 'lazy', 'none'], default='lazy', space='buy', optimize=True) xbtc_base_rmi = IntParameter(20, 70, default=40, space='buy', load=True, optimize=True) # BTC / ETH Stake Parameters xtra_base_stake_rmi = IntParameter(10, 50, default=50, space='buy', load=True, optimize=True) xtra_base_fiat_rmi = IntParameter(30, 70, default=50, space='buy', load=True, optimize=True) ## Sell Space Params are being used for both custom_stoploss and custom_sell # Custom Sell Profit (formerly Dynamic ROI) csell_roi_type = CategoricalParameter(['static', 'decay', 'step'], default='step', space='sell', load=True, optimize=True) csell_roi_time = IntParameter(720, 1440, default=720, space='sell', load=True, optimize=True) csell_roi_start = DecimalParameter(0.01, 0.05, default=0.01, space='sell', load=True, optimize=True) csell_roi_end = DecimalParameter(0.0, 0.01, default=0, space='sell', load=True, optimize=True) csell_trend_type = CategoricalParameter(['rmi', 'ssl', 'candle', 'any', 'none'], default='any', space='sell', load=True, optimize=True) csell_pullback = CategoricalParameter([True, False], default=True, space='sell', load=True, optimize=True) csell_pullback_amount = DecimalParameter(0.005, 0.03, default=0.01, space='sell', load=True, optimize=True) csell_pullback_respect_roi = CategoricalParameter([True, False], default=False, space='sell', load=True, optimize=True) csell_endtrend_respect_roi = CategoricalParameter([True, False], default=False, space='sell', load=True, optimize=True) # Custom Stoploss cstop_loss_threshold = DecimalParameter(-0.05, -0.01, default=-0.03, space='sell', load=True, optimize=True) cstop_bail_how = CategoricalParameter(['roc', 'time', 'any', 'none'], default='none', space='sell', load=True, optimize=True) cstop_bail_roc = DecimalParameter(-5.0, -1.0, default=-3.0, space='sell', load=True, optimize=True) cstop_bail_time = IntParameter(60, 1440, default=720, space='sell', load=True, optimize=True) cstop_bail_time_trend = CategoricalParameter([True, False], default=True, space='sell', load=True, optimize=True) timeframe = '5m' inf_timeframe = '1h' buy_params = {} sell_params = {} minimal_roi = { "0": 100 } stoploss = -0.99 use_custom_stoploss = True # Recommended use_sell_signal = True sell_profit_only = True ignore_roi_if_buy_signal = True # Required startup_candle_count: int = 233 process_only_new_candles = False # Strategy Specific Variable Storage custom_trade_info = {} custom_fiat = "USD" # Only relevant if stake is BTC or ETH custom_btc_inf = False # Don't change this. """ Informative Pair Definitions """ def informative_pairs(self): # add all whitelisted pairs on informative timeframe pairs = self.dp.current_whitelist() informative_pairs = [(pair, self.inf_timeframe) for pair in pairs] # add extra informative pairs if the stake is BTC or ETH if self.config['stake_currency'] in ('BTC', 'ETH'): for pair in pairs: coin, stake = pair.split('/') coin_fiat = f"{coin}/{self.custom_fiat}" informative_pairs += [(coin_fiat, self.timeframe)] stake_fiat = f"{self.config['stake_currency']}/{self.custom_fiat}" informative_pairs += [(stake_fiat, self.timeframe)] # if BTC/STAKE is not in whitelist, add it as an informative pair on both timeframes else: btc_stake = f"BTC/{self.config['stake_currency']}" if not btc_stake in pairs: informative_pairs += [(btc_stake, self.timeframe)] return informative_pairs """ Indicator Definitions """ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if not metadata['pair'] in self.custom_trade_info: self.custom_trade_info[metadata['pair']] = {} if not 'had-trend' in self.custom_trade_info[metadata["pair"]]: self.custom_trade_info[metadata['pair']]['had-trend'] = False ## Base Timeframe / Pair # Kaufmann Adaptive Moving Average dataframe['kama'] = ta.KAMA(dataframe, length=233) # RMI: https://www.tradingview.com/script/kwIt9OgQ-Relative-Momentum-Index/ dataframe['rmi'] = cta.RMI(dataframe, length=24, mom=5) # Momentum Pinball: https://www.tradingview.com/script/fBpVB1ez-Momentum-Pinball-Indicator/ dataframe['roc-mp'] = ta.ROC(dataframe, timeperiod=1) dataframe['mp'] = ta.RSI(dataframe['roc-mp'], timeperiod=3) # MA Streak: https://www.tradingview.com/script/Yq1z7cIv-MA-Streak-Can-Show-When-a-Run-Is-Getting-Long-in-the-Tooth/ dataframe['mastreak'] = cta.mastreak(dataframe, period=4) # Percent Change Channel: https://www.tradingview.com/script/6wwAWXA1-MA-Streak-Change-Channel/ upper, mid, lower = cta.pcc(dataframe, period=40, mult=3) dataframe['pcc-lowerband'] = lower dataframe['pcc-upperband'] = upper lookup_idxs = dataframe.index.values - (abs(dataframe['mastreak'].values) + 1) valid_lookups = lookup_idxs >= 0 dataframe['sbc'] = np.nan dataframe.loc[valid_lookups, 'sbc'] = dataframe['close'].to_numpy()[lookup_idxs[valid_lookups].astype(int)] dataframe['streak-roc'] = 100 * (dataframe['close'] - dataframe['sbc']) / dataframe['sbc'] # Trends, Peaks and Crosses dataframe['candle-up'] = np.where(dataframe['close'] >= dataframe['open'],1,0) dataframe['candle-up-trend'] = np.where(dataframe['candle-up'].rolling(5).sum() >= 3,1,0) dataframe['rmi-up'] = np.where(dataframe['rmi'] >= dataframe['rmi'].shift(),1,0) dataframe['rmi-up-trend'] = np.where(dataframe['rmi-up'].rolling(5).sum() >= 3,1,0) dataframe['rmi-dn'] = np.where(dataframe['rmi'] <= dataframe['rmi'].shift(),1,0) dataframe['rmi-dn-count'] = dataframe['rmi-dn'].rolling(8).sum() dataframe['streak-bo'] = np.where(dataframe['streak-roc'] < dataframe['pcc-lowerband'],1,0) dataframe['streak-bo-count'] = dataframe['streak-bo'].rolling(8).sum() # Indicators used only for ROI and Custom Stoploss ssldown, sslup = cta.SSLChannels_ATR(dataframe, length=21) dataframe['sroc'] = cta.SROC(dataframe, roclen=21, emalen=13, smooth=21) dataframe['ssl-dir'] = np.where(sslup > ssldown,'up','down') # Base pair informative timeframe indicators informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.inf_timeframe) # Get the "average day range" between the 1d high and 1d low to set up guards informative['1d-high'] = informative['close'].rolling(24).max() informative['1d-low'] = informative['close'].rolling(24).min() informative['adr'] = informative['1d-high'] - informative['1d-low'] dataframe = merge_informative_pair(dataframe, informative, self.timeframe, self.inf_timeframe, ffill=True) # Other stake specific informative indicators # e.g if stake is BTC and current coin is XLM (pair: XLM/BTC) if self.config['stake_currency'] in ('BTC', 'ETH'): coin, stake = metadata['pair'].split('/') fiat = self.custom_fiat coin_fiat = f"{coin}/{fiat}" stake_fiat = f"{stake}/{fiat}" # Informative COIN/FIAT e.g. XLM/USD - Base Timeframe coin_fiat_tf = self.dp.get_pair_dataframe(pair=coin_fiat, timeframe=self.timeframe) dataframe[f"{fiat}_rmi"] = cta.RMI(coin_fiat_tf, length=55, mom=5) # Informative STAKE/FIAT e.g. BTC/USD - Base Timeframe stake_fiat_tf = self.dp.get_pair_dataframe(pair=stake_fiat, timeframe=self.timeframe) dataframe[f"{stake}_rmi"] = cta.RMI(stake_fiat_tf, length=55, mom=5) # Informatives for BTC/STAKE if not in whitelist else: pairs = self.dp.current_whitelist() btc_stake = f"BTC/{self.config['stake_currency']}" if not btc_stake in pairs: self.custom_btc_inf = True # BTC/STAKE - Base Timeframe btc_stake_tf = self.dp.get_pair_dataframe(pair=btc_stake, timeframe=self.timeframe) dataframe['BTC_rmi'] = cta.RMI(btc_stake_tf, length=55, mom=5) dataframe['BTC_close'] = btc_stake_tf['close'] dataframe['BTC_kama'] = ta.KAMA(btc_stake_tf, length=144) return dataframe """ Buy Signal """ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] # Informative Timeframe Guards conditions.append( (dataframe['close'] <= dataframe[f"1d-low_{self.inf_timeframe}"] + (self.inf_pct_adr.value * dataframe[f"adr_{self.inf_timeframe}"])) ) # Base Timeframe Guards conditions.append( (dataframe['rmi-dn-count'] >= self.base_rmi_streak.value) & (dataframe['streak-bo-count'] >= self.base_ma_streak.value) & (dataframe['rmi'] <= self.base_rmi_max.value) & (dataframe['rmi'] >= self.base_rmi_min.value) & (dataframe['mp'] <= self.base_mp.value) ) # Base Timeframe Trigger if self.base_trigger.value == 'pcc': conditions.append(qtpylib.crossed_above(dataframe['streak-roc'], dataframe['pcc-lowerband'])) if self.base_trigger.value == 'rmi': conditions.append(dataframe['rmi-up-trend'] == 1) # Extra conditions for */BTC and */ETH stakes on additional informative pairs if self.config['stake_currency'] in ('BTC', 'ETH'): conditions.append( (dataframe[f"{self.custom_fiat}_rmi"] > self.xtra_base_fiat_rmi.value) | (dataframe[f"{self.config['stake_currency']}_rmi"] < self.xtra_base_stake_rmi.value) ) # Extra conditions for BTC/STAKE if not in whitelist else: if self.custom_btc_inf: if self.xbtc_guard.value == 'strict': conditions.append( ( (dataframe['BTC_rmi'] > self.xbtc_base_rmi.value) & (dataframe['BTC_close'] > dataframe['BTC_kama']) ) ) if self.xbtc_guard.value == 'lazy': conditions.append( (dataframe['close'] > dataframe['kama']) | ( (dataframe['BTC_rmi'] > self.xbtc_base_rmi.value) & (dataframe['BTC_close'] > dataframe['BTC_kama']) ) ) conditions.append(dataframe['volume'].gt(0)) if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), 'buy'] = 1 return dataframe """ Sell Signal """ def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['sell'] = 0 return dataframe """ Custom Stoploss """ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) last_candle = dataframe.iloc[-1].squeeze() trade_dur = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60) in_trend = self.custom_trade_info[trade.pair]['had-trend'] # Determine how we sell when we are in a loss if current_profit < self.cstop_loss_threshold.value: if self.cstop_bail_how.value == 'roc' or self.cstop_bail_how.value == 'any': # Dynamic bailout based on rate of change if last_candle['sroc'] <= self.cstop_bail_roc.value: return 0.01 if self.cstop_bail_how.value == 'time' or self.cstop_bail_how.value == 'any': # Dynamic bailout based on time, unless time_trend is true and there is a potential reversal if trade_dur > self.cstop_bail_time.value: if self.cstop_bail_time_trend.value == True and in_trend == True: return 1 else: return 0.01 return 1 """ Custom Sell """ def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, current_profit: float, **kwargs): dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) last_candle = dataframe.iloc[-1].squeeze() trade_dur = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60) max_profit = max(0, trade.calc_profit_ratio(trade.max_rate)) pullback_value = max(0, (max_profit - self.csell_pullback_amount.value)) in_trend = False # Determine our current ROI point based on the defined type if self.csell_roi_type.value == 'static': min_roi = self.csell_roi_start.value elif self.csell_roi_type.value == 'decay': min_roi = cta.linear_decay(self.csell_roi_start.value, self.csell_roi_end.value, 0, self.csell_roi_time.value, trade_dur) elif self.csell_roi_type.value == 'step': if trade_dur < self.csell_roi_time.value: min_roi = self.csell_roi_start.value else: min_roi = self.csell_roi_end.value # Determine if there is a trend if self.csell_trend_type.value == 'rmi' or self.csell_trend_type.value == 'any': if last_candle['rmi-up-trend'] == 1: in_trend = True if self.csell_trend_type.value == 'ssl' or self.csell_trend_type.value == 'any': if last_candle['ssl-dir'] == 'up': in_trend = True if self.csell_trend_type.value == 'candle' or self.csell_trend_type.value == 'any': if last_candle['candle-up-trend'] == 1: in_trend = True # Don't sell if we are in a trend unless the pullback threshold is met if in_trend == True and current_profit > 0: # Record that we were in a trend for this trade/pair for a more useful sell message later self.custom_trade_info[trade.pair]['had-trend'] = True # If pullback is enabled and profit has pulled back allow a sell, maybe if self.csell_pullback.value == True and (current_profit <= pullback_value): if self.csell_pullback_respect_roi.value == True and current_profit > min_roi: return 'intrend_pullback_roi' elif self.csell_pullback_respect_roi.value == False: if current_profit > min_roi: return 'intrend_pullback_roi' else: return 'intrend_pullback_noroi' # We are in a trend and pullback is disabled or has not happened or various criteria were not met, hold return None # If we are not in a trend, just use the roi value elif in_trend == False: if self.custom_trade_info[trade.pair]['had-trend']: if current_profit > min_roi: self.custom_trade_info[trade.pair]['had-trend'] = False return 'trend_roi' elif self.csell_endtrend_respect_roi.value == False: self.custom_trade_info[trade.pair]['had-trend'] = False return 'trend_noroi' elif current_profit > min_roi: return 'notrend_roi' else: return None
class NostalgiaForInfinityV2(IStrategy): INTERFACE_VERSION = 2 minimal_roi = {"0": 10} stoploss = -1.0 timeframe = '5m' inf_1h = '1h' custom_info = {} # Sell signal use_sell_signal = True sell_profit_only = False sell_profit_offset = 0.001 # it doesn't meant anything, just to guarantee there is a minimal profit. ignore_roi_if_buy_signal = True # Trailing stoploss trailing_stop = False trailing_only_offset_is_reached = True trailing_stop_positive = 0.02 trailing_stop_positive_offset = 0.04 # Custom stoploss use_custom_stoploss = False # Run "populate_indicators()" only for new candle. process_only_new_candles = True # Number of candles the strategy requires before producing valid signals startup_candle_count: int = 200 # Optional order type mapping. order_types = { 'buy': 'limit', 'sell': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': False } # Buy params buy_params = { "buy_bb40_bbdelta_close": 0.017, "buy_bb40_closedelta_close": 0.013, "buy_bb40_tail_bbdelta": 0.445, "buy_bb20_close_bblowerband": 0.992, "buy_bb20_volume": 27, "buy_rsi_diff": 52.438, } # Sell params sell_params = { "sell_rsi_bb": 79.706, "sell_rsi_main": 85.023, "sell_rsi_2": 87.545, "sell_rsi_diff": 0.873, "sell_ema_relative": 0.03, } buy_bb40_bbdelta_close = DecimalParameter(0.005, 0.05, default=0.049, space='buy', optimize=True, load=True) buy_bb40_closedelta_close = DecimalParameter(0.01, 0.03, default=0.023, space='buy', optimize=True, load=True) buy_bb40_tail_bbdelta = DecimalParameter(0.15, 0.45, default=0.287, space='buy', optimize=True, load=True) buy_bb20_close_bblowerband = DecimalParameter(0.8, 1.1, default=0.991, space='buy', optimize=True, load=True) buy_bb20_volume = IntParameter(18, 34, default=20, space='buy', optimize=True, load=True) buy_rsi_diff = DecimalParameter(36.0, 54.0, default=37.0, space='buy', optimize=True, load=True) sell_rsi_bb = DecimalParameter(60.0, 80.0, default=70, space='sell', optimize=True, load=True) sell_rsi_main = DecimalParameter(72.0, 90.0, default=78.0, space='sell', optimize=True, load=True) sell_rsi_2 = DecimalParameter(72.0, 90.0, default=60.0, space='sell', optimize=True, load=True) sell_ema_relative = DecimalParameter(0.005, 0.1, default=0.03, space='sell', optimize=False, load=True) sell_rsi_diff = DecimalParameter(0.0, 5.0, default=5.0, space='sell', optimize=True, load=True) def informative_pairs(self): pairs = self.dp.current_whitelist() informative_pairs = [(pair, '1h') for pair in pairs] return informative_pairs def informative_1h_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: assert self.dp, "DataProvider is required for multiple timeframes." # Get the informative pair informative_1h = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.inf_1h) # EMA informative_1h['ema_20'] = ta.EMA(informative_1h, timeperiod=20) informative_1h['ema_50'] = ta.EMA(informative_1h, timeperiod=50) informative_1h['ema_200'] = ta.EMA(informative_1h, timeperiod=200) # RSI informative_1h['rsi'] = ta.RSI(informative_1h, timeperiod=14) # SSL Channels ssl_down_1h, ssl_up_1h = SSLChannels(informative_1h, 20) informative_1h['ssl_down'] = ssl_down_1h informative_1h['ssl_up'] = ssl_up_1h return informative_1h def normal_tf_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: bb_40 = qtpylib.bollinger_bands(dataframe['close'], window=40, stds=2) dataframe['lower'] = bb_40['lower'] dataframe['mid'] = bb_40['mid'] dataframe['bbdelta'] = (bb_40['mid'] - dataframe['lower']).abs() dataframe['closedelta'] = (dataframe['close'] - dataframe['close'].shift()).abs() dataframe['tail'] = (dataframe['close'] - dataframe['low']).abs() bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=50) dataframe['volume_mean_slow'] = dataframe['volume'].rolling( window=30).mean() # EMA dataframe['ema_50'] = ta.EMA(dataframe, timeperiod=50) dataframe['ema_200'] = ta.EMA(dataframe, timeperiod=200) # SMA dataframe['sma_5'] = ta.SMA(dataframe, timeperiod=5) dataframe['sma_9'] = ta.SMA(dataframe, timeperiod=9) # RSI dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # The indicators for the 1h informative timeframe informative_1h = self.informative_1h_indicators(dataframe, metadata) dataframe = merge_informative_pair(dataframe, informative_1h, self.timeframe, self.inf_1h, ffill=True) # The indicators for the normal (5m) timeframe dataframe = self.normal_tf_indicators(dataframe, metadata) return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ((dataframe['close'] < dataframe['sma_9']) & (dataframe['close'] > dataframe['ema_200_1h']) & (dataframe['ema_50'] > dataframe['ema_200']) & (dataframe['ema_50_1h'] > dataframe['ema_200_1h']) & dataframe['lower'].shift().gt(0) & dataframe['bbdelta'].gt(dataframe['close'] * self.buy_bb40_bbdelta_close.value) & dataframe['closedelta'].gt(dataframe['close'] * self.buy_bb40_closedelta_close.value) & dataframe['tail'].lt(dataframe['bbdelta'] * self.buy_bb40_tail_bbdelta.value) & dataframe['close'].lt(dataframe['lower'].shift()) & dataframe['close'].le(dataframe['close'].shift()) & (dataframe['volume'] > 0)) | ((dataframe['close'] < dataframe['sma_9']) & (dataframe['close'] > dataframe['ema_200']) & (dataframe['close'] > dataframe['ema_200_1h']) & (dataframe['close'] < dataframe['ema_slow']) & (dataframe['close'] < self.buy_bb20_close_bblowerband.value * dataframe['bb_lowerband']) & (dataframe['volume'] < (dataframe['volume_mean_slow'].shift(1) * self.buy_bb20_volume.value))) | ((dataframe['close'] < dataframe['sma_5']) & (dataframe['close'] > dataframe['ema_200']) & (dataframe['close'] > dataframe['ema_200_1h']) & (dataframe['ssl_up_1h'] > dataframe['ssl_down_1h']) & (dataframe['ema_50'] > dataframe['ema_200']) & (dataframe['ema_50_1h'] > dataframe['ema_200_1h']) & (dataframe['rsi'] < dataframe['rsi_1h'] - self.buy_rsi_diff.value) & (dataframe['volume'] > 0)), 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[( (dataframe['rsi'] > self.sell_rsi_bb.value) & (dataframe['close'] > dataframe['bb_upperband']) & (dataframe['close'].shift(1) > dataframe['bb_upperband'].shift(1)) & (dataframe['close'].shift(2) > dataframe['bb_upperband'].shift(2)) & (dataframe['volume'] > 0)) | ((dataframe['rsi'] > self.sell_rsi_main.value) & (dataframe['volume'] > 0)) | ((dataframe['close'] < dataframe['ema_200']) & (dataframe['close'] > dataframe['ema_50']) & (dataframe['rsi'] > self.sell_rsi_2.value) & (dataframe['volume'] > 0)) | ((dataframe['close'] < dataframe['ema_200']) & (((dataframe['ema_200'] - dataframe['close']) / dataframe['close']) < self.sell_ema_relative.value) & (dataframe['rsi'] > dataframe['rsi_1h'] + self.sell_rsi_diff.value) & (dataframe['volume'] > 0)), 'sell'] = 1 return dataframe
class SMAOffsetStrategy(IStrategy): INTERFACE_VERSION = 2 # ROI table: minimal_roi = {"0": 1} # Stoploss: stoploss = -0.5 base_nb_candles_buy = IntParameter( 5, 80, default=buy_params['base_nb_candles_buy'], space='buy') base_nb_candles_sell = IntParameter( 5, 80, default=sell_params['base_nb_candles_sell'], space='sell') low_offset = DecimalParameter(0.9, 0.99, default=buy_params['low_offset'], space='buy') high_offset = DecimalParameter(0.99, 1.1, default=sell_params['high_offset'], space='sell') buy_trigger = CategoricalParameter([SMA, EMA], default=buy_params['buy_trigger'], space='buy') sell_trigger = CategoricalParameter([SMA, EMA], default=sell_params['sell_trigger'], space='sell') # Trailing stop: trailing_stop = False trailing_stop_positive = 0.1 trailing_stop_positive_offset = 0 trailing_only_offset_is_reached = False # Optimal timeframe for the strategy timeframe = '5m' use_sell_signal = True sell_profit_only = False process_only_new_candles = True startup_candle_count = 30 plot_config = { 'main_plot': { 'ma_offset_buy': { 'color': 'orange' }, 'ma_offset_sell': { 'color': 'orange' }, }, } use_custom_stoploss = False def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # uncomment for plotting #if self.buy_trigger.value == 'EMA': # dataframe['ma_buy'] = ta.EMA(dataframe, timeperiod=self.base_nb_candles_buy.value) #else: # dataframe['ma_buy'] = ta.SMA(dataframe, timeperiod=self.base_nb_candles_buy.value) #if self.sell_trigger.value == 'EMA': # dataframe['ma_sell'] = ta.EMA(dataframe, timeperiod=self.base_nb_candles_sell.value) #else: # dataframe['ma_sell'] = ta.SMA(dataframe, timeperiod=self.base_nb_candles_sell.value) #dataframe['ma_offset_buy'] = dataframe['ma_buy'] * self.low_offset.value #dataframe['ma_offset_sell'] = dataframe['ma_sell'] * self.high_offset.value return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if self.buy_trigger.value == EMA: dataframe['ma_buy'] = ta.EMA(dataframe, timeperiod=int( self.base_nb_candles_buy.value)) else: dataframe['ma_buy'] = ta.SMA(dataframe, timeperiod=int( self.base_nb_candles_buy.value)) dataframe[ 'ma_offset_buy'] = dataframe['ma_buy'] * self.low_offset.value dataframe.loc[((dataframe['close'] < dataframe['ma_offset_buy']) & (dataframe['volume'] > 0)), 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if self.sell_trigger.value == EMA: dataframe['ma_sell'] = ta.EMA(dataframe, timeperiod=int( self.base_nb_candles_sell.value)) else: dataframe['ma_sell'] = ta.SMA(dataframe, timeperiod=int( self.base_nb_candles_sell.value)) dataframe[ 'ma_offset_sell'] = dataframe['ma_sell'] * self.high_offset.value dataframe.loc[((dataframe['close'] > dataframe['ma_offset_sell']) & (dataframe['volume'] > 0)), 'sell'] = 1 return dataframe
class CombinedBinHAndClucV6H(IStrategy): minimal_roi = {"0": 0.0181} max_open_trades = 5 # Using custom stoploss stoploss = -0.99 use_custom_stoploss = True # Trailing stoploss trailing_stop = True trailing_only_offset_is_reached = True trailing_stop_positive = 0.01 trailing_stop_positive_offset = 0.025 timeframe = '5m' informative_timeframe = '1h' # derived from EMA 200 startup_candle_count: int = 200 # Run "populate_indicators()" only for new candle. process_only_new_candles = False use_sell_signal = True sell_profit_only = True sell_profit_offset = 0.001 ignore_roi_if_buy_signal = True # hyperspace default buy params buy_params = { 'buy_bin_bbdelta_close': 0.031, 'buy_bin_closedelta_close': 0.018, 'buy_bin_tail_bbdelta': 0.233, 'buy_bin_guard': True, 'buy_cluc_close_bblowerband': 0.993, 'buy_cluc_volume': 21, 'buy_cluc_guard': True, 'buy_long_rsi_diff': 43.276, 'buy_bin_enable': True, 'buy_cluc_enable': True, 'buy_long_enable': True, 'buy_minimum_conditions': 1 } # hyperspace default sell params sell_params = { 'sell_roi_override_rsi_threshold': 50, # to disable holding with high RSI set to 100 'cstp_loss_threshold': 0, 'cstp_bail_time': 5 } # if you want to see which buy conditions were met # or if there is an trade exit override due to high RSI set to True # logger will output the buy and trade exit conditions cust_log_verbose = False ########################################################################################################### ## IMPORTANT NOTICE REGARDING HYPEROPT: ## ## Use the optimize dict below to enable optimization (set to True) of one or more ## ## hyperspace parameters. ## ## ## ## Please be aware: Enabling ALL at once will very likely produce very low results! ## ## So please enable max. 1-2 parameters at once, you'll need to play around ## ## what makes more or less sense. At the end it always depends on a lot more ## ## parameters (stake, max trades, hyperopt-loss, etc.) ## ########################################################################################################### # use this switches to enable the optimization of hyperspace params cust_optimize = { 'buy_bin_bbdelta_close': False, 'buy_bin_closedelta_close': False, 'buy_bin_tail_bbdelta': False, 'buy_bin_guard': False, 'buy_cluc_close_bblowerband': False, 'buy_cluc_volume': False, 'buy_cluc_guard': False, 'buy_long_rsi_diff': False, 'buy_bin_enable': False, 'buy_cluc_enable': False, 'buy_long_enable': False, 'buy_minimum_conditions': False, 'sell_roi_override_rsi_threshold': False, 'cstp_bail_time': False, 'cstp_loss_threshold': False } # bin buy parameters buy_bin_bbdelta_close = DecimalParameter( 0.0, 0.05, default=0.031, space='buy', optimize=cust_optimize['buy_bin_bbdelta_close'], load=True) buy_bin_closedelta_close = DecimalParameter( 0.0, 0.03, default=0.018, decimals=4, space='buy', optimize=cust_optimize['buy_bin_closedelta_close'], load=True) buy_bin_tail_bbdelta = DecimalParameter( 0.0, 1.0, default=0.233, decimals=3, space='buy', optimize=cust_optimize['buy_bin_tail_bbdelta'], load=True) buy_bin_guard = CategoricalParameter( [True, False], default=True, space='buy', optimize=cust_optimize['buy_bin_guard'], load=True) # cluc buy parameters buy_cluc_close_bblowerband = DecimalParameter( 0.0, 1.5, default=0.993, decimals=3, space='buy', optimize=cust_optimize['buy_cluc_close_bblowerband'], load=True) buy_cluc_volume = IntParameter(10, 40, default=21, space='buy', optimize=cust_optimize['buy_cluc_volume'], load=True) buy_cluc_guard = CategoricalParameter( [True, False], default=True, space='buy', optimize=cust_optimize['buy_cluc_guard'], load=True) # log buy parameters buy_long_rsi_diff = DecimalParameter( 40, 45, default=43.276, decimals=3, space='buy', optimize=cust_optimize['buy_long_rsi_diff'], load=True) # enable bin, cluc or long buy_bin_enable = CategoricalParameter( [True, False], default=True, space='buy', optimize=cust_optimize['buy_bin_enable'], load=True) buy_cluc_enable = CategoricalParameter( [True, False], default=True, space='buy', optimize=cust_optimize['buy_cluc_enable'], load=True) buy_long_enable = CategoricalParameter( [True, False], default=True, space='buy', optimize=cust_optimize['buy_long_enable'], load=True) # minimum conditions to match in buy buy_minimum_conditions = IntParameter( 1, 2, default=1, space='buy', optimize=cust_optimize['buy_minimum_conditions'], load=True) # if RSI above threshold, override ROI sell_roi_override_rsi_threshold = IntParameter( 40, 70, default=50, space='sell', optimize=cust_optimize['sell_roi_override_rsi_threshold'], load=True) # custom stoploss, if trade open > x hours and loss > threshold then sell cstp_bail_time = IntParameter(1, 36, default=5, space='sell', optimize=cust_optimize['cstp_bail_time']) cstp_loss_threshold = DecimalParameter( -0.25, 0, default=0, decimals=2, space='sell', optimize=cust_optimize['cstp_loss_threshold']) """ Informative Pairs """ def informative_pairs(self): pairs = self.dp.current_whitelist() informative_pairs = [(pair, self.informative_timeframe) for pair in pairs] return informative_pairs """ Informative Timeframe Indicators """ def get_informative_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe = self.dp.get_pair_dataframe( pair=metadata['pair'], timeframe=self.informative_timeframe) # EMA dataframe['ema_50'] = ta.EMA(dataframe, timeperiod=50) dataframe['ema_200'] = ta.EMA(dataframe, timeperiod=200) # RSI dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) # SSL Channels ssldown, sslup = SSLChannels_ATR(dataframe, 20) dataframe['ssl-up'] = sslup dataframe['ssl-down'] = ssldown dataframe['ssl-dir'] = np.where(sslup > ssldown, 'up', 'down') return dataframe """ Main Timeframe Indicators """ def get_main_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # strategy BinHV45 bb_40 = qtpylib.bollinger_bands(dataframe['close'], window=40, stds=2) dataframe['lower'] = bb_40['lower'] dataframe['mid'] = bb_40['mid'] dataframe['bbdelta'] = (bb_40['mid'] - dataframe['lower']).abs() dataframe['closedelta'] = (dataframe['close'] - dataframe['close'].shift()).abs() dataframe['tail'] = (dataframe['close'] - dataframe['low']).abs() # strategy ClucMay72018 bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] dataframe['volume_mean_slow'] = dataframe['volume'].rolling( window=30).mean() # EMA dataframe['ema_50'] = ta.EMA(dataframe, timeperiod=50) dataframe['ema_200'] = ta.EMA(dataframe, timeperiod=200) # SMA dataframe['sma_5'] = ta.EMA(dataframe, timeperiod=5) # RSI dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) # drop NAN in hyperopt to fix "'<' not supported between instances of 'str' and 'int' error if self.config['runmode'].value == 'hyperopt': dataframe = dataframe.dropna() return dataframe """ Populate Informative and Main Timeframe Indicators """ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if self.config['runmode'].value in ('backtest', 'hyperopt'): assert (timeframe_to_minutes(self.timeframe) <= 5), "Backtest this strategy in a timeframe of 5m or less." assert self.dp, "DataProvider is required for multiple timeframes." informative = self.get_informative_indicators(dataframe, metadata) dataframe = merge_informative_pair(dataframe, informative, self.timeframe, self.informative_timeframe, ffill=True) dataframe = self.get_main_indicators(dataframe, metadata) return dataframe """ Buy Signal """ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] # reset additional dataframe rows dataframe.loc[:, 'buy_cond_bin'] = False dataframe.loc[:, 'buy_cond_cluc'] = False dataframe.loc[:, 'buy_cond_long'] = False dataframe.loc[:, 'conditions_count'] = 0 # strategy BinHV45 + guards by @iterativ dataframe.loc[( # @iterativ adition (guard) (((dataframe['close'] > dataframe['ema_200_1h']) & (dataframe['ema_50'] > dataframe['ema_200']) & (dataframe['ema_50_1h'] > dataframe['ema_200_1h']) & (self.buy_bin_guard.value == True)) | (self.buy_bin_guard.value == False)) & # strategy BinHV45 dataframe['lower'].shift().gt(0) & dataframe['bbdelta'].gt(dataframe['close'] * self.buy_bin_bbdelta_close.value) & dataframe['closedelta'].gt(dataframe['close'] * self.buy_bin_closedelta_close.value) & dataframe['tail'].lt(dataframe['bbdelta'] * self.buy_bin_tail_bbdelta.value) & dataframe['close'].lt(dataframe['lower'].shift()) & dataframe['close'].le(dataframe['close'].shift()) & (self.buy_bin_enable.value == True)), 'buy_cond_bin'] = 1 # strategy ClucMay72018 + guards by @iterativ dataframe.loc[( # @iterativ addition (guard) (((dataframe['close'] > dataframe['ema_200']) & (dataframe['close'] > dataframe['ema_200_1h']) & (self.buy_cluc_guard.value == True)) | (self.buy_cluc_guard.value == False)) & # strategy ClucMay72018 (dataframe['close'] < dataframe['ema_50']) & (dataframe['close'] < self.buy_cluc_close_bblowerband.value * dataframe['bb_lowerband']) & (dataframe['volume'] < (dataframe['volume_mean_slow'].shift(1) * self.buy_cluc_volume.value)) & (self.buy_cluc_enable.value == True)), 'buy_cond_cluc'] = 1 # long strategy by @iterativ dataframe.loc[((dataframe['close'] < dataframe['sma_5']) & (dataframe['ssl-dir_1h'] == 'up') & (dataframe['ema_50'] > dataframe['ema_200']) & (dataframe['ema_50_1h'] > dataframe['ema_200_1h']) & (dataframe['rsi'] < dataframe['rsi_1h'] - self.buy_long_rsi_diff.value) & (self.buy_long_enable.value == True)), 'buy_cond_long'] = 1 # count the amount of conditions met dataframe.loc[:, 'conditions_count'] = dataframe['buy_cond_bin'].astype( int) + dataframe['buy_cond_cluc'].astype( int) + dataframe['buy_cond_long'].astype(int) # append the minimum amount of conditions to be met conditions.append( dataframe['conditions_count'] >= self.buy_minimum_conditions.value) conditions.append(dataframe['volume'].gt(0)) if conditions: dataframe.loc[reduce(lambda x, y: x & y, conditions), 'buy'] = 1 # verbose logging enable only for verbose information or troubleshooting if self.cust_log_verbose == True: for index, row in dataframe.iterrows(): if row['buy'] == 1: buy_cond_details = f"count={int(row['conditions_count'])}/bin={int(row['buy_cond_bin'])}/cluc={int(row['buy_cond_cluc'])}/long={int(row['buy_cond_long'])}" logger.info( f"{metadata['pair']} - candle: {row['date']} - buy condition - details: {buy_cond_details}" ) return dataframe """ Sell Signal """ def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] conditions.append( (dataframe['close'] > dataframe['bb_upperband']) & (dataframe['close'].shift(1) > dataframe['bb_upperband'].shift(1)) & (dataframe['volume'] > 0) # Make sure Volume is not 0 ) if conditions: dataframe.loc[reduce(lambda x, y: x & y, conditions), 'sell'] = 1 return dataframe """ Custom Stop Loss """ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: if (current_profit < self.cstp_loss_threshold.value) & ( current_time - timedelta(hours=int(self.cstp_bail_time.value)) > trade.open_date_utc): return 0.01 return self.stoploss """ Trade Exit Confirmation """ def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, sell_reason: str, **kwargs) -> bool: dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() if self.cust_log_verbose == True: logger.info( f"{pair} - candle: {last_candle['date']} - exit trade {sell_reason} with profit {trade.calc_profit_ratio(rate)}" ) # failsafe for user triggered forced sells > always have highest prio! if sell_reason == 'force_sell': return True # Prevent ROI trigger, if there is more potential, in order to maximize profit if last_candle is not None and ((sell_reason == 'roi')): rsi = 0 if 'rsi' in last_candle.index: rsi = last_candle['rsi'] if ((rsi >= self.sell_roi_override_rsi_threshold.value)): if self.cust_log_verbose == True: logger.info( f"{pair} - candle: {last_candle['date']} - not exiting trade with current profit {trade.calc_profit_ratio(rate)}, rsi = {rsi} which is > than {self.sell_roi_override_rsi_threshold.value}" ) return False else: return True return True
class StarRise(IStrategy): """ Designed to use with StarRise DCA settings TTP: 1.1%(0.2%), BO: 38.0 USDT, SO: 38.0 USDT, OS: 1.2, SS: 1.13, MAD: 2, SOS: 1.6, MSTC: 11 2021/12 Crash ========================================================== BUY TAG STATS =========================================================== | TAG | Buys | Avg Profit % | Cum Profit % | Tot Profit USDT | Tot Profit % | Avg Duration | Win Draw Loss Win% | |-------+--------+----------------+----------------+-------------------+----------------+----------------+-------------------------| | TOTAL | 412 | 1.14 | 469.43 | 1157.492 | 0.45 | 5:04:00 | 412 0 0 100 | 2021/05 Crash ========================================================== BUY TAG STATS =========================================================== | TAG | Buys | Avg Profit % | Cum Profit % | Tot Profit USDT | Tot Profit % | Avg Duration | Win Draw Loss Win% | |-------+--------+----------------+----------------+-------------------+----------------+----------------+-------------------------| | TOTAL | 197 | 1.25 | 245.79 | 631.840 | 0.25 | 4:22:00 | 197 0 0 100 | 2021/09 - 2021/11 Bull ========================================================== BUY TAG STATS =========================================================== | TAG | Buys | Avg Profit % | Cum Profit % | Tot Profit USDT | Tot Profit % | Avg Duration | Win Draw Loss Win% | |-------+--------+----------------+----------------+-------------------+----------------+----------------+-------------------------| | TOTAL | 327 | 1.30 | 424.98 | 961.187 | 0.37 | 3:26:00 | 326 0 1 99.7 | """ # Minimal ROI designed for the strategy. minimal_roi = {"0": 0.092, "29": 0.042, "85": 0.03, "128": 0.005} # Sell hyperspace params: sell_params = { "pHSL": -0.998, # 1.1% TTP "pPF_1": 0.011, "pPF_2": 0.065, "pSL_1": 0.011, "pSL_2": 0.062, } # Max Deviation -0.349 stoploss = -0.998 # Custom stoploss use_custom_stoploss = True process_only_new_candles = True startup_candle_count = 168 # Optimal timeframe for the strategy timeframe = '5m' # hard stoploss profit pHSL = DecimalParameter(-0.500, -0.040, default=-0.08, decimals=3, space='sell', load=True) # profit threshold 1, trigger point, SL_1 is used pPF_1 = DecimalParameter(0.008, 0.020, default=0.016, decimals=3, space='sell', load=True) pSL_1 = DecimalParameter(0.008, 0.020, default=0.011, decimals=3, space='sell', load=True) # profit threshold 2, SL_2 is used pPF_2 = DecimalParameter(0.040, 0.100, default=0.080, decimals=3, space='sell', load=True) pSL_2 = DecimalParameter(0.020, 0.070, default=0.040, decimals=3, space='sell', load=True) def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: # hard stoploss profit HSL = self.pHSL.value PF_1 = self.pPF_1.value SL_1 = self.pSL_1.value PF_2 = self.pPF_2.value SL_2 = self.pSL_2.value # For profits between PF_1 and PF_2 the stoploss (sl_profit) used is linearly interpolated # between the values of SL_1 and SL_2. For all profits above PL_2 the sl_profit value # rises linearly with current profit, for profits below PF_1 the hard stoploss profit is used. if current_profit > PF_2: sl_profit = SL_2 + (current_profit - PF_2) elif current_profit > PF_1: sl_profit = SL_1 + ((current_profit - PF_1) * (SL_2 - SL_1) / (PF_2 - PF_1)) else: sl_profit = HSL # Only for hyperopt invalid return if sl_profit >= current_profit: return -0.99 return stoploss_from_open(sl_profit, current_profit) @informative('1h') def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # CTI dataframe['cti_40'] = pta.cti(dataframe["close"], length=40) # %R dataframe['r_96'] = williams_r(dataframe, period=96) dataframe['r_480'] = williams_r(dataframe, period=480) # 1h mama > fama for general trend check dataframe['hl2'] = (dataframe['high'] + dataframe['low']) / 2 dataframe['mama'], dataframe['fama'] = ta.MAMA(dataframe['hl2'], 0.5, 0.05) dataframe['mama_diff'] = ((dataframe['mama'] - dataframe['fama']) / dataframe['hl2']) return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # RSI dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) dataframe['rsi_84'] = ta.RSI(dataframe, timeperiod=84) dataframe['rsi_112'] = ta.RSI(dataframe, timeperiod=112) # Bollinger bands bollinger1 = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=17, stds=1) dataframe['bb_lowerband'] = bollinger1['lower'] dataframe['bb_middleband'] = bollinger1['mid'] dataframe['bb_upperband'] = bollinger1['upper'] # Close delta dataframe['closedelta'] = (dataframe['close'] - dataframe['close'].shift()).abs() # Dip Protection dataframe['tpct_change_0'] = top_percent_change(dataframe, 0) dataframe['tpct_change_1'] = top_percent_change(dataframe, 1) dataframe['tpct_change_2'] = top_percent_change(dataframe, 2) dataframe['tpct_change_4'] = top_percent_change(dataframe, 4) dataframe['tpct_change_5'] = top_percent_change(dataframe, 5) dataframe['tpct_change_9'] = top_percent_change(dataframe, 9) # SMA dataframe['sma_50'] = ta.SMA(dataframe['close'], timeperiod=50) dataframe['sma_200'] = ta.SMA(dataframe['close'], timeperiod=200) # CTI dataframe['cti'] = pta.cti(dataframe["close"], length=20) # ADX dataframe['adx'] = ta.ADX(dataframe) # %R dataframe['r_14'] = williams_r(dataframe, period=14) dataframe['r_96'] = williams_r(dataframe, period=96) # MAMA / FAMA dataframe['hl2'] = (dataframe['high'] + dataframe['low']) / 2 dataframe['mama'], dataframe['fama'] = ta.MAMA(dataframe['hl2'], 0.5, 0.05) dataframe['mama_diff'] = ((dataframe['mama'] - dataframe['fama']) / dataframe['hl2']) # CRSI (3, 2, 100) crsi_closechange = dataframe['close'] / dataframe['close'].shift(1) crsi_updown = np.where(crsi_closechange.gt(1), 1.0, np.where(crsi_closechange.lt(1), -1.0, 0.0)) dataframe['crsi'] = (ta.RSI(dataframe['close'], timeperiod=3) + ta.RSI( crsi_updown, timeperiod=2) + ta.ROC(dataframe['close'], 100)) / 3 return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: is_crash_1 = ((dataframe['tpct_change_1'] < 0.08) & (dataframe['tpct_change_2'] < 0.08) & (dataframe['tpct_change_4'] < 0.10)) dataframe.loc[(( # Dip check (dataframe['close'] < dataframe['mama']) & (dataframe['r_14'] < -30) & (dataframe['cti'] < 3.0) & (dataframe['adx'] > 26) & (dataframe['mama_diff_1h'] > 0.003) & # Bull confirm (dataframe['mama'] > dataframe['fama']) & (dataframe['sma_50'] > dataframe['sma_200'] * 1.01) & (dataframe['mama_1h'] > dataframe['fama_1h'] * 1.01) & # Overpump check (dataframe['rsi_84'] < 55) & (dataframe['rsi_112'] < 55) & (dataframe['cti_40_1h'] < 0.73) & (dataframe['r_96_1h'] < -6) & (dataframe['mama_diff_1h'] < 0.027) & (dataframe['close'].rolling(288).max() >= (dataframe['close'] * 1.03)))), 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[((dataframe['close'] > dataframe['bb_upperband'] * 0.999) & (dataframe['rsi'] > 76)), 'sell'] = 0 return dataframe
class MADisplaceV3(IStrategy): ma_lower_length = IntParameter(15, 25, default=buy_params['ma_lower_length'], space='buy') ma_lower_offset = DecimalParameter(0.95, 0.97, default=buy_params['ma_lower_offset'], space='buy') informative_fast_length = IntParameter( 15, 35, default=buy_params['informative_fast_length'], space='disable') informative_slow_length = IntParameter( 20, 40, default=buy_params['informative_slow_length'], space='disable') rsi_fast_length = IntParameter(2, 8, default=buy_params['rsi_fast_length'], space='disable') rsi_fast_threshold = IntParameter(5, 35, default=buy_params['rsi_fast_threshold'], space='disable') rsi_slow_length = IntParameter(10, 45, default=buy_params['rsi_slow_length'], space='disable') rsi_slow_confirmation = IntParameter( 1, 5, default=buy_params['rsi_slow_confirmation'], space='disable') ma_middle_1_length = IntParameter( 15, 35, default=sell_params['ma_middle_1_length'], space='sell') ma_middle_1_offset = DecimalParameter( 0.93, 1.005, default=sell_params['ma_middle_1_offset'], space='sell') ma_upper_length = IntParameter(15, 25, default=sell_params['ma_upper_length'], space='sell') ma_upper_offset = DecimalParameter(1.005, 1.025, default=sell_params['ma_upper_offset'], space='sell') minimal_roi = {"0": 1} stoploss = -0.2 trailing_stop = True trailing_stop_positive = 0.01 trailing_stop_positive_offset = 0.025 trailing_only_offset_is_reached = True timeframe = '5m' use_sell_signal = True sell_profit_only = False process_only_new_candles = True plot_config = { 'main_plot': { 'ma_lower': { 'color': 'red' }, 'ma_middle_1': { 'color': 'green' }, 'ma_upper': { 'color': 'pink' }, }, } use_custom_stoploss = True startup_candle_count = 200 informative_timeframe = '1h' def informative_pairs(self): pairs = self.dp.current_whitelist() informative_pairs = [(pair, self.informative_timeframe) for pair in pairs] return informative_pairs def get_informative_indicators(self, metadata: dict): if self.config['runmode'].value == 'hyperopt': dataframe = self.informative_dataframe.copy() else: dataframe = self.dp.get_pair_dataframe( pair=metadata['pair'], timeframe=self.informative_timeframe) dataframe['ema_fast'] = ta.EMA(dataframe, timeperiod=int( self.informative_fast_length.value)) dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=int( self.informative_slow_length.value)) dataframe['uptrend'] = ((dataframe['ema_fast'] > dataframe['ema_slow'])).astype('int') return dataframe def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: if current_profit < -0.04 and current_time - timedelta( minutes=35) > trade.open_date_utc: return -0.01 return -0.99 def get_main_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=int( self.rsi_fast_length.value)) dataframe['rsi_slow'] = ta.RSI(dataframe, timeperiod=int( self.rsi_slow_length.value)) dataframe['rsi_slow_descending'] = ( dataframe['rsi_slow'] < dataframe['rsi_slow'].shift()).astype('int') dataframe['ma_lower'] = ta.SMA( dataframe, timeperiod=int( self.ma_lower_length.value)) * self.ma_lower_offset.value dataframe['ma_middle_1'] = ta.SMA( dataframe, timeperiod=int( self.ma_middle_1_length.value)) * self.ma_middle_1_offset.value dataframe['ma_upper'] = ta.SMA( dataframe, timeperiod=int( self.ma_upper_length.value)) * self.ma_upper_offset.value # drop NAN in hyperopt to fix "'<' not supported between instances of 'str' and 'int' error if self.config['runmode'].value == 'hyperopt': dataframe = dataframe.dropna() return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if self.config['runmode'].value == 'hyperopt': self.informative_dataframe = self.dp.get_pair_dataframe( pair=metadata['pair'], timeframe=self.informative_timeframe) if self.config['runmode'].value != 'hyperopt': informative = self.get_informative_indicators(metadata) dataframe = self.merge_informative(informative, dataframe) dataframe = self.get_main_indicators(dataframe, metadata) return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # calculate indicators with adjustable params for hyperopt # it's calling multiple times and dataframe overrides same columns # so check if any calculated column already exist if self.config[ 'runmode'].value == 'hyperopt' and 'uptrend' not in dataframe: informative = self.get_informative_indicators(metadata) dataframe = self.merge_informative(informative, dataframe) dataframe = self.get_main_indicators(dataframe, metadata) pd.options.mode.chained_assignment = None dataframe.loc[((dataframe['rsi_slow_descending']. rolling(self.rsi_slow_confirmation.value).sum() == self.rsi_slow_confirmation.value) & (dataframe['rsi_fast'] < self.rsi_fast_threshold.value) & (dataframe['uptrend'] > 0) & (dataframe['close'] < dataframe['ma_lower']) & (dataframe['volume'] > 0)), 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if self.config[ 'runmode'].value == 'hyperopt' and 'uptrend' not in dataframe: informative = self.get_informative_indicators(metadata) dataframe = self.merge_informative(informative, dataframe) dataframe = self.get_main_indicators(dataframe, metadata) pd.options.mode.chained_assignment = None dataframe.loc[(( (dataframe['uptrend'] == 0) | (dataframe['close'] > dataframe['ma_upper']) | (qtpylib. crossed_below(dataframe['close'], dataframe['ma_middle_1']))) & (dataframe['volume'] > 0)), 'sell'] = 1 return dataframe def merge_informative(self, informative: DataFrame, dataframe: DataFrame) -> DataFrame: dataframe = merge_informative_pair(dataframe, informative, self.timeframe, self.informative_timeframe, ffill=True) # don't overwrite the base dataframe's HLCV information skip_columns = [ (s + "_" + self.informative_timeframe) for s in ['date', 'open', 'high', 'low', 'close', 'volume'] ] dataframe.rename(columns=lambda s: s.replace( "_{}".format(self.informative_timeframe), "") if (not s in skip_columns) else s, inplace=True) return dataframe