The Obelisk Ichimoku Slow algotrading strategy (Patreon)
Downloads
Missing 1 file, as this post has not yet been imported.
Content
Introduction
Hi there and welcome to yet another postwhere I try to find the best trading strategy or algorithm for manual or bot trading.
This time I will test a second strategy from a repository, owned by Brook Miles. And I hope that this one performs as well as the previous strategy I tested from this repo.
In my previous video I tested an experimental strategy called the "Obelisk Ichimoku Zema" from this repo which proved to be the best performing algorithm I found until that moment. In this video I will test the Obelisk Ichimoku Slow version 1.3 algorithm and hope that it will show a similar or even better hypothetical performance.
And I sey hypothetical because I test these algo's in a controlled backtesting environment with historical data. So future performances are not guaranteed as you might already realised.
Anyway, let's take a quick and high level look at the code to find out what the filosophy is behind this algo before I show you the testresults.
The strategy
The code starts with the definition of an ssl_atr indicator that will be used in the algo later on.
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']The strategy is made with the 1 hour timeframe in mind. Probably because the author had the best testresults at this timeframe.
It uses a startup time of 180 candles to let it create the indicators in a correct way.
Also the minimal ROI has been set to take profit at 1000 percent. Effectively disabling the ROI, except when there is an actual 100 percent profits to be taken.
Stoploss has been set to -99 percent stop loss. Which also means that an automatic stop loss has been disabled. Unless the pair actuallu suffers a loss of 99 percent.
class Obelisk_Ichimoku_Slow_v1_3(IStrategy):
# Optimal timeframe for the strategy
timeframe = '1h'
# generate signals from the 1h timeframe
informative_timeframe = '1h'
# WARNING: ichimoku is a long indicator, if you remove or use a
# shorter startup_candle_count your results will be unstable/invalid
# for up to a week from the start of your backtest or dry/live run
# (180 candles = 7.5 days)
startup_candle_count = 180
# 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
minimal_roi = {
"0": 10,
}
# WARNING setting a stoploss for this strategy doesn't make much sense, as it will buy
# back into the trend at the next available opportunity, unless the trend has ended,
# in which case it would sell anyway.
# Stoploss:
stoploss = -0.99Then there is a section where informative pairs are defined. In this case all the pairs configured in the config settings.
And the slow timeframe indicator function is defined. In that functon the Ichimoku is used for determining the signals that will be used for the buy and sell moments for the trading bot that uses this code.
def informative_pairs(self):
pairs = self.dp.current_whitelist()
informative_pairs = [(pair, self.informative_timeframe) for pair in pairs]
return informative_pairs
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
# 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['senkou_a']) &
(dataframe['chikou_span'] > dataframe['senkou_b'])
).shift(displacement).fillna(0).astype('int')
# DANGER ZONE ENDIn this section the EMA's are defined that will be used in the strategy. And it looks like a sort of golden & death cross condition over here. Where the close price is above the ema50 and ema50 is above the ema200.
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
dataframe['ema200'] = ta.EMA(dataframe, timeperiod=200)
dataframe['ema_ok'] = (
(dataframe['close'] > dataframe['ema50'])
& (dataframe['ema50'] > dataframe['ema200'])
).astype('int') * 2It goes a little too far to explain each and every line here. But the overall idea of this section is that it detects the moments where there is a trend found or where it is stopped. This will then be used to determine the actual buy and sell moments of this algorithm.
dataframe['efi_base'] = ((dataframe['close'] - dataframe['close'].shift()) * dataframe['volume'])
dataframe['efi'] = ta.EMA(dataframe['efi_base'], 13)
dataframe['efi_ok'] = (dataframe['efi'] > 0).astype('int')
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['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['entry_ok'] = (
(dataframe['efi_ok'] > 0)
& (dataframe['open'] < dataframe['ssl_up'])
& (dataframe['close'] < dataframe['ssl_up'])
).astype('int') * 1
dataframe['trend_pulse'] = (
(dataframe['ichimoku_ok'] > 0)
& (dataframe['ssl_ok'] > 0)
& (dataframe['ema_ok'] > 0)
).astype('int') * 2
dataframe['trend_over'] = (
(dataframe['ssl_ok'] == 0)
).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)
return dataframeNo fast indicators are defined here at the moment.
def fast_tf_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# none atm
return dataframeThe actual indicators used in the strategy are normally defined in this section. However because the actual indicators were already defined earlier, they are defined as the dataframe the backtest engine will use to determine the buy and sell settings.
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Obelisk_Ichimoku_Slow does not use trailing stop or roi and should be safe to backtest at 1h
# if self.config['runmode'].value in ('backtest', 'hyperopt'):
# assert (timeframe_to_minutes(self.timeframe) <= 5), "Backtest this strategy in 5m or 1m timeframe."
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 dataframeIn these sections, the buy and sell signals are defined. For example: if the trending column in the dataframe is larger than 0, and if the entry OK column also contains a value lager than 0 and the timeframe has been set to 0 then there will be a buy signal.
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(dataframe['trending'] > 0)
& (dataframe['entry_ok'] > 0)
& (dataframe['date'].dt.minute == 0) # when backtesting at 5m/1m only set signal on the hour
, 'buy'] = 1
return dataframeFor the sell signal, this only sells if the trend is over and returns to a value of 0.
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(dataframe['trending'] == 0)
& (dataframe['date'].dt.minute == 0) # when backtesting at 5m/1m only set signal on the hour
, 'sell'] = 1
return dataframeAgain the author of the code has given all the information to create plot output of the strategies workings, signals and indicators. So I can use that to give more insights to the inner workings here.
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' },
},
'subplots': {
"Trend": {
'trend_pulse': {'color': 'blue'},
'trending': {'color': 'orange'},
'trend_over': {'color': 'red'},
},
"Signals": {
'ichimoku_ok': {'color': 'green'},
'ssl_ok': {'color': 'red'},
'ema_ok': {'color': 'orange'},
'entry_ok': {'color': 'blue'},
},
}
}Let's quickly start by backtesting this code over different timeframes and find out if this algorithm has the potential to make us money in the crypto trading market here.
I created one of these plots from a random backtest to show you vidually how this strategy uses combinations of signals that determine the buy and sell settings.
Based on the conditions in the slow_tf_indicators function, there are indicators calculated that, together, form the base for a buy signal. To give you one example from the trend subplot: If the orange trending line is 3 and the blue trend pulse equals 2 (or is at lease higher than 0), then this forms a buy signal with the blue entry ok signal from the signals subplot.
So as you can see, a nifty way of generating signals based on indicator conditions here.
Now lets close this chart and see how this trading strategy performs on my backtest setup.
Backtest results
So after I have backtested the code, I just presented, on my setup, I got the following results back.
I could test this algo on the 1 hour and lower timeframes. And the 30 minute timeframe seems to have the best fit for this strategy, considering it's results.
All the backtests have profitable outcomes, only the performance indicators have somewhat dissapointing numbers here. Overall the winrate is lower than 40%, and a 40 percent win rate is the minimum I personally expect from a trend following strategy.
This also has its influence on other performance and risk indicators like CAGR, Calmar, Sortino and Sharpe ratio. They lag a little big behind of great performing strategies. Also this algo suffers from a high drawdown, higher lose streaks than win streaks and the pairs I use for backtesting are responding less well too here.
I know that nowadays I am a little bit spoiled by all the great performing algo's I found until now. But this also means that I set the bar higher for this current and future tested algorithms. Which on itself I think is a good thing here because the quality also gets higher as time progresses.
In this next plot you can clearly see that the strategy has a potential to attain great wealth. But the way it holds the gains is not great at all. The fact that it gives its gains to the market again is not preferable and there are other algorithms that have better rules to keep the attained profits in your account balance.
You can see this more clearly if I compare this performance with other top 10 strategies I tested.
The Obelisk Zema, Godstra, Beastbot and SMA Offset algorithms have much better ways to hold their gains. They also have a more even profit curve that has a steady incline and almost no clearly visible drawdowns. At least, in this overview. If I would only consider the end amount of hypothetical gains, then this strategy would end up as the fifth best.
But since I also consider applicability and risk factors like Sortino, Sharpe, winrate, drawdown and more, then the endscore ends up totally different.
Comparing the individual performance indicators it is also clear to see that the strategy lags somewhat behind the top 10 algos I already found so far. The most significant plot here is the drawdown, which is way too high. No bonuspoints there.
Furthermore the CAGR is comparable with other strategies, but this does not make enough difference and I give more weight to factors like Sortino and Sharpe to determine if a strategy will be used by me.
This all results in a endscore of 274 points and the 15th spot. Ending with the same points as the Bollinger real pullback strategy. But that one ends up one place higher because of the sortino and sharpe ratios that are better.
Which automatically brings us to the end of yet another postwhere I test a trading strategy algorithm.
If you like these posts, then give a thumbs up. And if you have a suggestion for me to test another algo then leave it as a comment or send a direct message. Then I'll see what I can do with it.
Thanks for reading,
And I'll see you in the next post.
Good luck and goodbye!