FSD – Trailing stoploss

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

Sometimes, there are times when a trade almost reach your roi level, but then it stop rise up and start to go down and ends up triggering stoploss. That’s where trailing stoploss (I’m gonna refer to it as tsl throughout this article) helps you. You can see minimal roi as the preferred profit target, and tsl as a minimum profit target. For example, let’s see this code

minimal_roi = {
    "0": 0.05
}
trailing_stop = True
trailing_stop_positive = 0.01
trailing_stop_positive_offset = 0.03
trailing_only_offset_is_reached = True

5% is the preferred profit target. But you set the minimum profit target at (around) 3-1 = 2% profit target. Of course the profit have to at least reached 3% before the trailing activated and the 2% profit locked in.

The issue with the code above is it introduced backtest trap. You can read it more here. So personally, I prefer using custom_stoploss function, which look like the code below

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

The code above will make sure the tsl is backtest-able. The tsl will only start to be checked once the trade duration past the open candle (in this case 15 mins), and current_profit is tied to last candle’s close value. The full code will look like this (don’t forget to copy the import lines, because there are new import lines)

from freqtrade.strategy import IStrategy
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
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 populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['ema_9'] = ta.EMA(dataframe, 9)
        dataframe['ema_20'] = ta.EMA(dataframe, 20)
        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            qtpylib.crossed_above(dataframe['ema_9'], dataframe['ema_20'])
            &
            (dataframe['volume'] > 0)
            , ['enter_long', 'enter_tag']
        ] = (1, 'golden cross')
        return dataframe
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            qtpylib.crossed_below(dataframe['ema_9'], dataframe['ema_20'])
            &
            (dataframe['volume'] > 0)
            , ['exit_long', 'exit_tag']
        ] = (1, 'death cross')
        return dataframe

5 Comments

  1. Hi. Very interesting content you have here. I’m trying to get backtesting a bit more representative: I see huge differences in backtests between tsl and custom_stoploss method you proposed. Shall they behave the same in DRY/LIVE tough ? Or should I revert to standart tsl for live ?

    • They won’t behave the same in dry/live, because my custom_stoploss is modified a little bit as said in the post. Read it again.

      Reverting to standard TSL on live won’t give you same result as backtest.

Leave a Reply

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