THE BEST ALGO AT THE MOMENT!! "Obelisk_Ichimoku_ZEMA_v1" (Patreon)
Downloads
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 = TrueThe 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.294Then 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') * 3Interestingly, 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') * 2That'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 dataframeThere 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.valueIn 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 dataframeThe 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 dataframeNext 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 TrueFinally, 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