# --- Do not remove these libs --- from freqtrade.strategy import IStrategy, merge_informative_pair, DecimalParameter, IntParameter, CategoricalParameter from typing import Dict, List from functools import reduce from pandas import DataFrame # -------------------------------- import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib from datetime import datetime, timedelta from freqtrade.persistence import Trade import pandas as pd # inspired by @tirail SMAOffset entry_params = {'ma_lower_length': 15, 'ma_lower_offset': 0.96, 'informative_fast_length': 20, 'informative_slow_length': 25, 'rsi_fast_length': 4, 'rsi_fast_threshold': 35, 'rsi_slow_length': 20, 'rsi_slow_confirmation': 1} exit_params = {'ma_middle_1_length': 30, 'ma_middle_1_offset': 0.995, 'ma_upper_length': 20, 'ma_upper_offset': 1.01} class MADisplaceV3(IStrategy): INTERFACE_VERSION = 3 ma_lower_length = IntParameter(15, 25, default=entry_params['ma_lower_length'], space='entry') ma_lower_offset = DecimalParameter(0.95, 0.97, default=entry_params['ma_lower_offset'], space='entry') informative_fast_length = IntParameter(15, 35, default=entry_params['informative_fast_length'], space='disable') informative_slow_length = IntParameter(20, 40, default=entry_params['informative_slow_length'], space='disable') rsi_fast_length = IntParameter(2, 8, default=entry_params['rsi_fast_length'], space='disable') rsi_fast_threshold = IntParameter(5, 35, default=entry_params['rsi_fast_threshold'], space='disable') rsi_slow_length = IntParameter(10, 45, default=entry_params['rsi_slow_length'], space='disable') rsi_slow_confirmation = IntParameter(1, 5, default=entry_params['rsi_slow_confirmation'], space='disable') ma_middle_1_length = IntParameter(15, 35, default=exit_params['ma_middle_1_length'], space='exit') ma_middle_1_offset = DecimalParameter(0.93, 1.005, default=exit_params['ma_middle_1_offset'], space='exit') ma_upper_length = IntParameter(15, 25, default=exit_params['ma_upper_length'], space='exit') ma_upper_offset = DecimalParameter(1.005, 1.025, default=exit_params['ma_upper_offset'], space='exit') 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_exit_signal = True exit_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_entry_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), 'enter_long'] = 1 return dataframe def populate_exit_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), 'exit_long'] = 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