FSD – Multiple entry and exit logics

DISCLAIMER
The codes in this post is just for educational purpose, and not to be used on live trading

To have multiple logics is quite easy. You can just do multiple dataframe.loc[] like this

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

    dataframe.loc[
        entry_1
        , ['enter_long', 'enter_tag']
    ] = (1, 'entry_1 ')

    dataframe.loc[
        entry_2
        , ['enter_long', 'enter_tag']
    ] = (1, 'entry_2 ')

    dataframe.loc[
        entry_3
        , ['enter_long', 'enter_tag']
    ] = (1, 'entry_3 ')

    return dataframe

def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

    dataframe.loc[
        exit_1
        , ['exit_long', 'exit_tag']
    ] = (1, 'exit_1 ')

    dataframe.loc[
        exit_2
        , ['exit_long', 'exit_tag']
    ] = (1, 'exit_2 ')

    dataframe.loc[
        exit_3
        , ['exit_long', 'exit_tag']
    ] = (1, 'exit_3 ')

    return dataframe

The issue with the code above is if multiple logics are triggered, then your enter_tag and exit_tag only have the last logic’s tag. For example, let’s say all the entry and exit logics are triggered respectively on enter and exit, then your enter_tag and exit_tag will be entry_3 and exit_3. While this won’t have any effect on current code, if you want to have exit logics that are tied to specific enter_tag (I’ll discuss this next), then you want to have your enter_tag to be accurate, which means it should be entry_1 entry_2 entry_3.

To have that accurate enter and exit tag, the code should look like this

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

    dataframe['enter_tag'] = ''

    dataframe.loc[
        entry_1
        |
        entry_2
        |
        entry_3
        , 'enter_long'
    ] = 1

    dataframe.loc[
        entry_1
        , 'enter_tag'
    ] += 'entry_1 '

    dataframe.loc[
        entry_2
        , 'enter_tag'
    ] += 'entry_2 '

    dataframe.loc[
        entry_3
        , 'enter_tag'
    ] += 'entry_3 '

    return dataframe

def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

    dataframe['exit_tag'] = ''

    dataframe.loc[
        exit_1
        |
        exit_2
        |
        exit_3
        , 'exit_long'
    ] = 1

    dataframe.loc[
        exit_1
        , 'exit_tag'
    ] += 'exit_1 '

    dataframe.loc[
        exit_2
        , 'exit_tag'
    ] += 'exit_2 '

    dataframe.loc[
        exit_3
        , 'exit_tag'
    ] += 'exit_3 '

    return dataframe

One important thing to note, if possible, don’t use space for your enter and exit tags if you want to use them further. Now let’s try to implement it on our sample strategy. On top of the golden/death cross, let’s add 2 more entry and exit logics, when close below ema_9/20 for entry and when close above ema_9/20 for exit. Our strategy now looks like this

from freqtrade.strategy import IStrategy, informative
from pandas import DataFrame
import freqtrade.vendor.qtpylib.indicators as qtpylib
import talib.abstract as ta
from freqtrade.persistence import Trade
from datetime import datetime, timedelta
from typing import Optional, Union

def EWO(source, sma_length=5, sma2_length=35):
    sma1 = ta.SMA(source, timeperiod=sma_length)
    sma2 = ta.SMA(source, timeperiod=sma2_length)
    smadif = (sma1 - sma2) / source * 100
    return smadif

class strat_template (IStrategy):

    def version(self) -> str:
        return "template-v1"

    INTERFACE_VERSION = 3

    minimal_roi = {
        "0": 0.05
    }

    stoploss = -0.05

    timeframe = '15m'

    process_only_new_candles = True
    startup_candle_count = 999

    use_custom_stoploss = True

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float:
        
        sl_new = 1

        if (current_time - timedelta(minutes=15) >= trade.open_date_utc):

            dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            current_candle = dataframe.iloc[-1].squeeze()
            current_profit = trade.calc_profit_ratio(current_candle['close'])

            if (current_profit >= 0.03):
                sl_new = 0.01

        return sl_new

    def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> Optional[Union[str, bool]]:

        if ((current_time - timedelta(minutes=15)) >= trade.open_date_utc):

            dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            current_candle = dataframe.iloc[-1].squeeze()
            current_profit = trade.calc_profit_ratio(current_candle['close'])
            
            if (current_profit >= 0):
                if (current_candle['rsi'] >= 70):
                    return "rsi_overbought"

    @informative('30m')
    def populate_indicators_inf1(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        
        dataframe['rsi'] = ta.RSI(dataframe['close'], 14)

        return dataframe

    @informative('1h')
    def populate_indicators_inf2(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        
        dataframe['rsi'] = ta.RSI(dataframe['close'], 14)

        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        dataframe['ema_9'] = ta.EMA(dataframe['close'], 9)
        dataframe['ema_20'] = ta.EMA(dataframe['close'], 20)
        dataframe['rsi'] = ta.RSI(dataframe['close'], 14)
        dataframe['ema_9_rsi'] = ta.EMA(dataframe['rsi'], 9)
        dataframe['ewo'] = EWO(dataframe['close'], 50, 200)

        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        dataframe['enter_tag'] = ''

        enter_1 = (
            qtpylib.crossed_above(dataframe['ema_9'], dataframe['ema_20'])
            &
            (dataframe['rsi_30m'] < 50)
            &
            (dataframe['rsi_1h'] < 30)
            &
            (dataframe['ema_9_rsi'] < 70)
            &
            (dataframe['ewo'] > 3)
            &
            (dataframe['volume'] > 0)
        )

        enter_2 = (
            (dataframe['close'] < dataframe['ema_9'])
            &
            (dataframe['volume'] > 0)
        )

        enter_3 = (
            (dataframe['close'] < dataframe['ema_20'])
            &
            (dataframe['volume'] > 0)
        )

        dataframe.loc[
            enter_1 | enter_2 | enter_3
            , 'enter_long'
        ] = 1

        dataframe.loc[
            enter_1
            , 'enter_tag'
        ] += 'golden_cross '

        dataframe.loc[
            enter_2
            , 'enter_tag'
        ] += 'close_below_9 '

        dataframe.loc[
            enter_3
            , 'enter_tag'
        ] += 'close_below_20 '

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        dataframe['exit_tag'] = ''

        exit_1 = (
            qtpylib.crossed_below(dataframe['ema_9'], dataframe['ema_20'])
            &
            (dataframe['volume'] > 0)
        )

        exit_2 = (
            (dataframe['close'] > dataframe['ema_9'])
            &
            (dataframe['volume'] > 0)
        )

        exit_3 = (
            (dataframe['close'] > dataframe['ema_20'])
            &
            (dataframe['volume'] > 0)
        )

        dataframe.loc[
            exit_1 | exit_2 | exit_3
            , 'exit_long'
        ] = 1

        dataframe.loc[
            exit_1
            , 'exit_tag'
        ] += 'death_cross '

        dataframe.loc[
            exit_2
            , 'exit_tag'
        ] += 'close_above_9 '

        dataframe.loc[
            exit_3
            , 'exit_tag'
        ] += 'close_above_20 '

        return dataframe

3 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *