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

Downloads

  • Obelisk_Ichimoku_ZEMA_v1.7z

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

Content


Introduction

Hi there, and welcome to this  post where I want to present to you THE MOST (hypothetical) PROFITABLE ALGORITHMIC TRADING STRATEGY YET.

When I tested this algorithm it produced simply unbeleivable  results straight out of the box. And I must tell you that I already am testing this algo in my Dry Run environment.

So let me quickly  show you how this algorithm works...

The strategy / algorithm location

This spot trading strategy is called the Obelisk Ichimoku Zema and was found in this github repository: https://github.com/brookmiles/freqtrade-stuff/tree/main/strategies/experimental. Owned by Brook Miles.

It is one of his experimental strategies, so we should be aware of  this fact. However, even though this is an experimental idea, it  produced the best overall backtest results. So if you ever want to use  his code for your own trading bot, then please give Mr. Miles credits  where credits are due.

Let's take a closer look at the code to determine what the general idea is behind this algorithm and then test it on my setup.

The code

The code starts of with the definition of the ssl_atr.

def ssl_atr(dataframe, length = 7):
   df = dataframe.copy()
   df['smaHigh'] = df['high'].rolling(length).mean() + df['atr']
   df['smaLow'] = df['low'].rolling(length).mean() - df['atr']
   df['hlv'] = np.where(df['close'] > df['smaHigh'], 1, np.where(df['close'] < df['smaLow'], -1, np.NAN))
   df['hlv'] = df['hlv'].ffill()
   df['sslDown'] = np.where(df['hlv'] < 0, df['smaHigh'], df['smaLow'])
   df['sslUp'] = np.where(df['hlv'] < 0, df['smaLow'], df['smaHigh'])
   return df['sslDown'], df['sslUp']

It uses a primary timeframe of 5 minutes (5m) for trading signals and  an informative timeframe of 1 hour (1h) for additional context.

# Optimal timeframe for the strategy
timeframe = '5m'

# generate signals from the 1h timeframe
informative_timeframe = '1h'

The startup candle count determines that the first 500 candles will  not be used. That's because the ichimoku cloud indicator cannot be build  with less than 500 candles.

# 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

The algo also uses a set of different timestamps where profits are realized if it has reached the defined percentages here.

# ROI table:
minimal_roi = {
   "0": 0.078,
   "40": 0.062,
   "99": 0.039,
   "218": 0
}

The stop loss has been set to exit the trade at almost 30 percent  loss. Which is quite large, but also understandable since at these low  timeframes there could be large swings to the downside before rising  into profitable areas again. Letting the trade breathe.

stoploss = -0.294

Then there is a part where we can see that this strategy is also able to be optimized.

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

Next there is a function where the slow indicators are defined for  this algo. The Ichimoku cloud is defined here, as well as the ATR, SSL  and trend determination indicators:

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

Interestingly, In this small portion of the code, you can see that  the conditions for the positive and negative ichimoku cloud  are  consolidated into signals that have certain values. Which will be used  later to determine buy signals.

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

That's also the case with this section where the exit conditions are defined.

Notice also that the author did not use binary signals but used  different numbers to indicate a certain market status and signal type.

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

There is also a function where the fast timeframe indicators are  defined. Like the ZEMA indicator and derived buy and sell signals. With  this function the dataframe will be filled with conditions with data for  faster timeframes.

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

In the following section, the indicators for the strategy are defined.

There is a check to see if this strategy is actually running on the 5  minute timeframe. That's why I only could test on this timeframe.

Then there will be a merge of the informative data with the actual  trading data for the actual signals. This leads to a final dataframe  that will be used for trading.

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

The next section determines the buy and sell settings.

In these rules there will be a buy signal If the ichimoku valid value  is higher than 0, there is no bear trending and the close price is  higher than the zema times an offset value.

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

Next to the configured ROI and stop loss, there is only one exit  signal defined. And that is when the close price is above the the ZEMA  indicator, multiplied by a high offset value.

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

Finally, the author of the code was friendly enough to also add some code with which we can plot the indicators and signals. And I will show you later how this looks.

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'},
       },
   }
}

Initial backtest results

So  with all this information in the back of our heads, let's start with  presenting the backtest results of this algorithm on the 5 minute  timeframe. And just as you probably are now I was very much pleasently  surprised about this algo's performance.

As you might already know, I always test under the same  circumstances. With the same pairs, time range and other backtesting  engine settings. And comparing with the other strategies I tested, this  algorithm performance is just off the charts.

According to the backtests, this trading strategy has 66 percent  winning trades and got away with a staggering profit amount. That amount  als has influence on the CAGR and Calmar ratio too, which are both very  high.

Also the Sortino and Sharpe ratio have very optimistic ratios too.  And with a drawdown of only slightly less than 18 percent it keeps the  earned gains on your account too.

The weekly performance chart shows that this algo reacts very well to  bull market conditions. But in the mean time also keeps a lit on the  funds when bearish circumstances dominate the market. Keeping your  profits in your pockets. The curve is not so much parabolic after last  bear market's high. But there is still an incline in funds, albeit not  as steep.

If I compare this performance with the other trading algorithms, then  we can clearly see that, just as with the Alligator strategy, it  responds very well to bull markets. But when markets decline, this algo  has the edge to still manage to make profits. A truly admirable feature  that I surely will investigate further in my spare time and hopefully  can apply to my other algo's as well.

Taking a closer look at what makes this performance so high, we can  see that the algo was particularly good in catching pumps of very  popular coins during the last bull market. Like Shiba Inu, Manam, QNT,  Fantom and others.

Also the exit reasons indicate that the exit signal is the one that  provides the best exits. This implies that there could be some  improvements in the ROI signal with Hyperparameter optimization. But I  will not do that since I am very content with the current settings and I  could break this by overoptimizing things.

Taking a look at the plot of SHIB/USDT, we can now see more clearly  what the intentions are of the author. There are 6 additional indicators  added to the chart that will be a combination for creating the actual  buy and sell signals.

Zooming in further, we can now see even better the indicators of the  Ichimoku cloud, like tenkan and kijun sen. And the cloud itself. The  subplots show us very well how the signals are build up and you can see  that the individual indicators get a score that add up to a certain  value. Like the Trend value in this chart to give you a clear example.  It gets green and a value of 4 to indicate that the Ichimoku detects a  bullish trend. Other subplots then create the additional signals for the  bot to buy or to sell.

As said, I will definetely investigate this algorithm further to learn what makes it give its edge.

Above are the results I already got from 1 day of dry running the algo. In my Youtube video I have some more recent images. But as you can see, after running it  for only one day or so, it already made me a hypothetical average profit  of 3.5 percent in 16 trades. With a winrate of around 85 percent. So  it's clear to me that this algo's performance will be closely watched  for the coming weeks...

Which ultimately leads to the following scoring table over all the  performance indicators I use to score an algorithm. And I'm not telling  you something new here I think. Almost all scores are top notch, with  clear outliers in the profit percentage, CAGR and Calmar ratio.

Bringing me to the last section of this video. Which is it's place on the Strategy League.

And you might already guessed it. It ends up waaay above at the top  spot of this league. With almost 100 points difference to the second  spot I found last time with the SMA Offset strategy.

Now it is clear that this Obelisk strategy will be hard to beat in  the coming period. But I will try to develop or find one that is even  better.

So stay definetely tuned to myPatreon site to see what will happen in the next year.

But for now, this is the end. And I will see you in the next post. I hope you all will have a GREAT 2024 trading year with the next halving around the corner.

Good luck and goodbye!


Files

Previews only

Comments

Kristopher Gainer

AttributeError: module 'technical.indicators' has no attribute 'zema' I get this

Dutch Algotrading

As far as I can tell I could not spot any. However the newest Freqtrade backtesting engine has a feature that spots these as well. So for the next year I am updating my setup and maybe rerun this algo to see if there are any differences. If so, I'll tell it immediately.

Wojtek Chruścicki

I have noticed that this strategy disappeared from Strategy League. If you found some issues with it, please keep us updated - it's quite amazing strategy if it really scores so high as it did

Johannes Potgieter

I implemented this strategy as a DRY test with 75 pairs - Currently losing strategy and i think might be due the the pairs it's trading on and possibly reducing this to 25 or 30 Win / Loss 12 / 15 Winrate 44.444% Expectancy (ratio) -3.92 (-0.35) Avg. Duration 2:34:37 Best performing DEGO/USDT: 8.60% Profit factor 0.38 Max Drawdown 14.07% (143.426 USDT) from 2024-01-07 16:05:14 to 2024-01-08 07:00:17

Johannes Potgieter

Ok made changes to my config file and pairlist volatility - will monitor the dry run during the next 24 hours

Dutch Algotrading

Look at the moment you started your bot. Current market circumstances are pretty high risk. My opinion is that we can experience a pullback. Watch the chart RSI and MACD and notice the bearish divergence. Also it is hard to determine the best moment to start a bot or manual trading that has nothing to do with the strategy but only the exact moment you started it. Hope this explains. Btw. not affiliated with the developer or the algo.

tang tang

in the docker running log it shows "/home/ftuser/.local/lib/python3.11/site-packages/technical/indicators/overlap_studies.py:52: FutureWarning: zema is deprecated, use dema instead" how to slove it?

Dutch Algotrading

Downgrade or use the tips from my other videos to fix these kinds of problems. I do not use the newer freqtrade since a lot of old algos have troubles with these deprecated functions

Matt

I managed to make it run. Getting warnings but also it is working. I technically run below command (using docker) but I don't think it apply any changes (using v 2024.2): docker compose run --rm freqtrade strategy-updater --strategy-list Obelisk_Ichimoku_ZEMA_v1

Raniel Endrino

Hi, I've been backtesting this strategy and cant get the same Total Profit% as you have, Ive been using your spot_config file.. only thing I've changed is I've downloaded the spot data. I could only get up to ~260% in 260 days. is there anything I'm missing? Thanks

Dutch Algotrading

260% in 260 days looks to be pretty good in my books. But this is why I also add all my output in the posts, so that you can check and verify where things are different.

Frederic Collard

Can you share the hyperopt results ? The actual Obelisk_Ichimoku_ZEMA_v1.7z file does not include the hyperopt parameters neither in the main python file, nor as a json file.

Luke Salsich

I would also appreciate seeing the hyperopt parameters which you are running as a result of your testing, if possible. I've run a bunch of hyperopt epochs, but would benefit from comparing to yours.

Matt

I use this also not hyper opt, the current params seem good enough for current market. I did to do hyperopt myself but only ended up with worse performance although I'm quite new to the subject. It works best with supervision as it eaisly can long during pump-n-dump. If it'll be useful for you, here are my whitelist of pairs (binance) relatively free of sudden pumps:

Matt

[ "BTC/USDT", "ETH/USDT", "USDC/USDT", "SOL/USDT", "XRP/USDT", "ARB/USDT", "DOGE/USDT", "SUI/USDT", "MATIC/USDT", "AVAX/USDT", "APT/USDT", "LINK/USDT", "PEPE/USDT", "LTC/USDT", "ADA/USDT", "DOT/USDT", "OP/USDT", "BONK/USDT", "SHIB/USDT", "BLUR/USDT", "DYDX/USDT", "BNB/USDT", "CVC/USDT", "ALT/USDT", "TRU/USDT", "PLOYX/USDT", "YGG/USDT", "OM/USDT", "NULS/USDT", "OCEAN/USDT"]

Matt

also, my protections: [ { "method": "LowProfitPairs", "lookback_period_candles": 100, "trade_limit": 1, "stop_duration": 360, "required_profit": -0.05 }, { "method": "MaxDrawdown", "lookback_period_candles": 60, "trade_limit": 1, "stop_duration_candles": 12, "max_allowed_drawdown": 0.2 }]

Tal Zion

Hi, I am getting an error, I think the strategy is not compatible with V3? Traceback (most recent call last): freqtrade_bot_three_debug | File "/freqtrade/freqtrade/commands/trade_commands.py", line 24, in start_trading freqtrade_bot_three_debug | worker = Worker(args) freqtrade_bot_three_debug | ^^^^^^^^^^^^ freqtrade_bot_three_debug | File "/freqtrade/freqtrade/worker.py", line 37, in __init__ freqtrade_bot_three_debug | self._init(False) freqtrade_bot_three_debug | File "/freqtrade/freqtrade/worker.py", line 53, in _init freqtrade_bot_three_debug | self.freqtrade = FreqtradeBot(self._config) freqtrade_bot_three_debug | ^^^^^^^^^^^^^^^^^^^^^^^^^^ freqtrade_bot_three_debug | File "/freqtrade/freqtrade/freqtradebot.py", line 71, in __init__ freqtrade_bot_three_debug | self.strategy: IStrategy = StrategyResolver.load_strategy(self.config) freqtrade_bot_three_debug | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ freqtrade_bot_three_debug | File "/freqtrade/freqtrade/resolvers/strategy_resolver.py", line 48, in load_strategy freqtrade_bot_three_debug | strategy: IStrategy = StrategyResolver._load_strategy( freqtrade_bot_three_debug | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ freqtrade_bot_three_debug | File "/freqtrade/freqtrade/resolvers/strategy_resolver.py", line 280, in _load_strategy freqtrade_bot_three_debug | raise OperationalException( freqtrade_bot_three_debug | freqtrade.exceptions.OperationalException: Impossible to load Strategy 'Obelisk_Ichimoku_ZEMA_v1.py'. This class does not exist or contains Python code errors.

Matt

for more curious, freqtrade analysis says the algo suffers from lookahead bias in chikou_span. Not sure if displacement code addresses it properly in the algo..

Dragos Vintu

hello, can you please share the config file and the zema strategy you are using in the bot that is live on youtube?