Пример #1
0
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
Пример #2
0
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'},
            },
        }
    }
Пример #4
0
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
Пример #5
0
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
Пример #6
0
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
Пример #7
0
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
Пример #8
0
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 []
Пример #11
0
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
Пример #12
0
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
Пример #14
0
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
Пример #15
0
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
Пример #16
0
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