Home Creators Posts Import Register Favorites Logout
haven't archived this post yet. have a subscription? use the importer!

Downloads

  • SMAOffsetProtectOptV1.zip

Missing 1 full-res photo, 1 file, as this post has not yet been imported.

Content



Introduction

Hi there, and welcome to this  blogpost where I want to present you a strategy I found on Github and that  has earned a place very high up in the rankings. So let's not waste  anymore time and get right to it.

Where I found this strategy

As  with many precoded algorithms for the backtesting engine build in the  Freqtrade bot, this strategy was found in a github repository by stash86  and the file is called the SmaOffsetProtectOptV1. But I will call this strategy the Sma Offset algorithm.

The algorithm

Let me glance over the code so that we can understand what it's intentions are.

The code for the algorithm has a function that creates the Elliot  Wave Oscillator. That indicator will be used later in the buy and sell  settings.

def EWO(dataframe, ema_length=5, ema2_length=35):
   df = dataframe.copy()
   ema1 = ta.EMA(df, timeperiod=ema_length)
   ema2 = ta.EMA(df, timeperiod=ema2_length)
   emadif = (ema1 - ema2) / df['close'] * 100
   return emadif

Then there is a section with dictionaries that holds the buy and sell settings. These will also be used later in the algorithm.

# Buy hyperspace params:
buy_params = {
   "base_nb_candles_buy": 13,
   "ewo_high": 5.835,
   "ewo_low": -19.909,
   "low_offset": 0.978,
   "rsi_buy": 55,
}
# Sell hyperspace params:
sell_params = {
   "base_nb_candles_sell": 18,
   "high_offset": 1.012,
}

The algorithm has two predefined exit defined. One that sells at 1000  percent profit, effectively disabling the ROI sell signal. And a stop  loss setting that sells at a loss of 15 percent after entry.

# and disable roi:
# ROI table:
minimal_roi = {
   "0": 100.0
}

# Stoploss:
stoploss = -0.15

Then there are some protections defined which will be used in live  trading. Effectively determining that pairs with low profit will be  locked, or excluded, from trading for the next 60 candles when they do  not have a required profit of -5 percent and at least 1 trade over the  last 60 candles.

{
       "method": "LowProfitPairs",
       "lookback_period_candles": 60,
       "trade_limit": 1,
       "stop_duration": 60,
       "required_profit": -0.05
   },

And a cooldown period that does not buy the pair again after selling within the next 2 candles.

{
       "method": "CooldownPeriod",
       "stop_duration_candles": 2
   }

In this next part there are some Hyperparameter optimization spaces  determined for parameters that will be used for determining the buy and  sell signals.

# SMAOffset
base_nb_candles_buy = IntParameter(
   5, 80, default=buy_params['base_nb_candles_buy'], space='buy', optimize=True)
base_nb_candles_sell = IntParameter(
   5, 80, default=sell_params['base_nb_candles_sell'], space='sell', optimize=True)
low_offset = DecimalParameter(
   0.9, 0.99, default=buy_params['low_offset'], space='buy', optimize=True)
high_offset = DecimalParameter(
   0.99, 1.1, default=sell_params['high_offset'], space='sell', optimize=True)

# Protection
fast_ewo = 50
slow_ewo = 200
ewo_low = DecimalParameter(-20.0, -8.0,
                          default=buy_params['ewo_low'], space='buy', optimize=True)
ewo_high = DecimalParameter(
   2.0, 12.0, default=buy_params['ewo_high'], space='buy', optimize=True)
rsi_buy = IntParameter(30, 70, default=buy_params['rsi_buy'], space='buy', optimize=True)

No trailing stop.

trailing_stop = False

And the timeframe for this algorithm. It has been set at 5 minute  here, with an informative timeframe of 1 hour. So in this case I will  not test timeframes higher than 1 hour for this algo.

timeframe = '5m'
informative_timeframe = '1h'

In this section we can see that the algo makes use of the pairslist  as informative pairs and the 1-hour timeframe for creatig informative  indicators.

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):

   dataframe = self.dp.get_pair_dataframe(
       pair=metadata['pair'], timeframe=self.informative_timeframe)

   return dataframe

As said earlier, this strategie will use the EMA, Elliot Wave Oscillator and RSI to determine buy and sell moments.

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

   if self.config['runmode'].value == 'hyperopt':
       # Calculate all ma_buy values
       for val in self.base_nb_candles_buy.range:
           dataframe[f'ma_{val}'] = ta.EMA(dataframe, timeperiod=val)
   else:
       dataframe[f'ma_{self.base_nb_candles_buy.value}'] = ta.EMA(dataframe, timeperiod=self.base_nb_candles_buy.value)
       dataframe[f'ma_{self.base_nb_candles_sell.value}'] = ta.EMA(dataframe, timeperiod=self.base_nb_candles_sell.value)

   # Elliot
   dataframe['EWO'] = EWO(dataframe, self.fast_ewo, self.slow_ewo)

   # RSI
   dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)

   return dataframe

And the Entry signals will be made by the bot if:

  • The strategy buys when the current price is lower than a specific  EMA value adjusted by a low offset factor (low_offset), potentially  indicating a buying opportunity.
  • In the second line it also considers the EWO value to be above a  high threshold (ewo_high). A high Elliot value can be indicative of  strong positive momentum or a bullish trend.
  • And in the last line, the RSI value is checked to be below a  certain threshold (rsi_buy). This is typically used to identify oversold  conditions, suggesting that the asset may be undervalued and could be  due for a price increase.conditions.append(
         (
             (dataframe['close'] < (dataframe[f'ma_{self.base_nb_candles_buy.value}'] * self.low_offset.value)) &
             (dataframe['EWO'] > self.ewo_high.value) &
             (dataframe['rsi'] < self.rsi_buy.value) &
             (dataframe['volume'] > 0)
         )
     )

However in the second set of conditions, the EWO value is below a  specified low threshold. This could indicate strong negative momentum or  a bearish trend. The strategy might interpret this as a potential  reversal point where the price could start to increase. Since the  conditions are evaluated in parallel, there's no hierarchical preference  given to one over the other. The strategy treats both conditions as  equally valid for generating entry signals.

conditions.append(
       (
           (dataframe['close'] < (dataframe[f'ma_{self.base_nb_candles_buy.value}'] * self.low_offset.value)) &
           (dataframe['EWO'] < self.ewo_low.value) &
           (dataframe['volume'] > 0)
       )
   )

Selling will be done when A sell signal is generated when the close  price is higher than a specific EMA value adjusted by a high offset  factor (high_offset).

def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
   conditions = []

   conditions.append(
       (
           (dataframe['close'] > (dataframe[f'ma_{self.base_nb_candles_sell.value}'] * self.high_offset.value)) &
           (dataframe['volume'] > 0)
       )
   )

So as you can see, this code looks to be a momentum-based trading  strategy that uses moving averages, Elliot Wave Oscillator, and RSI to  make buy and sell decisions.

And, it includes dynamic parameters for optimization, protection mechanisms, and specific conditions for entry and exit.

As i see it now, this strategy is designed for short-term trends as  indicated by its 5-minute operational timeframe, making it suitable for  day trading or short-term speculation.

Now it is time to test this code and find out how its hypothetical performance would be in previous market conditions.

Strategy backtest results

Interestingly,  the configured informative pairs and timeframe were never used in the  actual algorithm. That's why I could also test higher timeframes on this  algorithm.

Not that it mattered much though because the 30 minute timeframe  seems to have the best cards here. But other low timeframes like the 15  and 5 minute also perform very good here.

The 30 minute timeframe wins because of its high profits, Sharpe and  Sortino combination. But the other timeframes have their own, very  specific, positive scores. Like higher win percentage, lower drawdown,  Calmar ratio and Pairs percentage.

It's a close call here, but the 30 minute timeframe scores just a  little higher. Choosing this strategies timeframe for you depends on how  it performs on your preferred pairs and possible backtest results here.

Taking a look at it's weekly performance, you can see that it is  almost a straight profit line here. You can hardly spot a large  drawdown. Even after the top of the last bear cycle, this algorithm kept  adding profits to your balance. Which I cannot remember seeing in the  performance of earlier algorithms. They always had a period of large  drawdowns at some point.

I can check this performance in the following chart here. And you can  see that other algos have at some point a noticable drawdown, whereas  this one just has an upward line without any noticable large periods of  declines. Like the Clucmay algo for example.

Which brings us to the comparison of the performance indicators of  this algo with the other top 10 algorithms. You can see that overall  this algo does not visibly outperform the other top 10 algorithms. But  what it does is being consistently in the top 3 or 4. Gathering points  that add up to a total high score, where other algorithms sometimes fail  to score well.

Trading is being consistent and this seems to be a consistent  strategy. Whether it is in trading, with it's almost straight line, or  scoring on performance indicators like the plots given here.

Strategy League

This algorithm ended up at the second spot in my personal league of tested  trading strategies. Now this SMA Offset strategy might  nog score the most on all performance indicators shown here, but you  saw earlier that on average it scored consistenty high on all  parameters, which made it get this overall added up score.

Ending

Please be aware that these results are  tested on historical data and that there is no guarantee that it will  keep performing in the future. Also always do your own research and  forward test anything before using actual money.

As usual, I will add all the output to this post so that you can check and verify my results. I also included the algorithm for use with the Freqtrade bot.

I am curious to hear from you how this algo performs on your setup. Does it perform great there too? Or are there some things to take into consideration before usage...

This is it for me now, so thanks for reading this post and see you the next time.

Goodbye!!


Files

Previews only

Comments

Jayson

Thanks DCD! Where can we find the strategy league these days?

John Match

Thanks for sharing the moon strategy. Could you provide link to strategy code that you used for explanation above? Strategy on github varies in parameters yet the same concepts as yours. Perhaps, a cross examination between the tested and linked strategies could be helpful if possible. :)

Dutch Algotrading

The files I add to the post are the original files I test with. Sometimes I have to adjust them to make them work on the newest FT version. These are then included in the post. Consider that added benefits for being a Patron :-)

Богдан Попович

Hello. I'm running out of strategy and i have mistakes: freqtrade.commands.trade_commands - ERROR - DEPRECATED: Using 'sell_profit_only' moved to 'exit_profit_only'. 2023-12-23 17:59:37,891 - freqtrade.commands.trade_commands - ERROR - Fatal exception! Traceback (most recent call last): File "/freqtrade/freqtrade/commands/trade_commands.py", line 24, in start_trading worker = Worker(args) ^^^^^^^^^^^^

Dutch Algotrading

Please use the strategy file from my attachment. I corrected the original strategy to make it work.

Richard

I came across a requirement, in the dry run, that the refresh rate for traidng pairs is at least 1d. Do you expect this to affect the performance of the strategy versus its backtesting results which use static pairs (hence no refresh rate)? Do you know why this limitation is a thing?

Wojtek Chruścicki

Hi! I have modified this strategy to work with leverage and shorts. During hyperopt I get error "Amount *amount* too high for *symbol*/USDT:USDT". Any idea how to fix it?

Wojtek Chruścicki

It seems like after some time, positions gets to large to use with leverage. if I could somehow limit it to max possible position and not go over it