The ‘DEMA & SMA Crossover’ strategy – Is this strategy profitable or not? (Patreon)
Downloads
Content
Intro
In this blog series I will be searching for the best trading strategy for cryptocurrency trading with a trading bot. These posts are complementary to the video’s I make on Youtube, which you can watch below.
The trading bot I use is the Freqtrade (https://www.freqtrade.io/en/latest/) trading bot which not only has good options for bot trading, but also is excellent in backtesting and optimization of trading parameters. Therefore I will use this program to not only trade automatically, but also look for the best strategies and setups.
All code that is specifically written for this strategy or for hyperparameter optimisation is available at the bottom of this post.
If you appreciate the effort I take to create this strategy or optimisation code, then please consider a tip for this post!
Strategy
In this video I am going to test one of my personal older strategies which I abandoned a long time ago, but sometimes still refer to because at that time it was one of my more profitable strategies.
This strategy makes use of three indicators
which are the
- Double ema
- Two simple moving averages
- And the RSI indicator together with a rsi moving average
It is a swing trading strategy with use of the default freqtrade roi and stop loss functions.
The way this strategy works is as follows:
The first moving average is a longer term trend indicator. The default value I am using here is the 55 day moving average. If the price is above this moving average there is a medium term uptrend and the strategy has its first signal to make a possible trade.
Then there is a combination of the double exponential moving average and another shorter term moving average. The double EMA has a timeperiod of 9 and the short sma has a period of 21.
When the Dema9 is above the SMA21, then a second trigger is formed which indicates that there is a short term price upswing.
When the RSI 14 in this case, is above the RSI 21 period moving average over than the final trigger for a market entry is given and a trade could be made.
Stoploss and takeprofit
This strategy uses the default Freqtrade ROI and Stoploss and depends on the entry and exit signals from the indicators described above.
Initial backtest results
After backtesting, the initial backtest results are as follows:
- Best timeframe: 1d and 4h have equal amount of points
- Total profit of strategy: 660% (1d) or 1788% (4h)
- Drawdown of strategy: 42% (1d) or 59% (4h)
- Winrate: around 45-50%
- Risk of ruin of strategy: 89% (1d) or 122% (4h)
Hyperparameter optimisation
I am going to optimise the 1day and 4 hour timeframe to see which timeframe has the best results since they have a similar score in my initial backtest comparison.
In the Hyperopt session I wil ltry to find the optimal settings for the following parameters:
- The Simple moving average range
- DEMA range
- The LT SMA range
And I also added two additional Moving averages to determine the sell signals so that they are not dependend on the buy signal crossover of DEMA and SMA. Therefore providing more flexibility to the exit signals.
- Exit DEMA
- Exit SMA
Also in my Youtube video I will thourougly explain how the hyperparameter code works. So if you want to know more on how to write your own hyperopt code, then see my video!
After this session, there will be another backtest with the optimised parameters on the given timeframe and range to see if this strategy has improved or not.
Conclusions
After running two optimization sessions, one on the 1day timeframe and one on the 4h timeframe I have the following results.
The 4h timeframe clearly has more profits in store for us with the found best parameters.
In this case the best buy settings are
- The sma 101 for determining the mid term uptrend
- And buy signals when the dema 14 is above the sma 32
- And this with the default rsi 14 and rsi sma 21
- Then the sell signal comes when the dema 11 gets below the sma 14
So here we see clearly that the sell signals are pretty quick in comparison with the buy signals with longer term moving averages.
In my video I am also comparing the plots between the old strategy settings and new strategy settings to see where the main differences are.
Strategy League
The list is getting longer and longer and this strategy enters the 13th place on the list.
Be aware that I have developed a new approach to compare these strategies between each other so it might look a little bit different when comparing this list with an earlier posts.
The profits of this strategy are quite nice if you look at better scoring strategies. What makes this one enter at the 13th place is mainly because of its drawdown, its lower win rate and somewhat high risk of ruin.
But if you want to take some higher risks in combination with the pairs that this strategy might fit better, then it could be quite an interesting and above all, a simple to follow strategy to examine further.
Be also aware that the results on the plot look like there are curve fitted a little bit too much. By doing your own research you might find better parameters that work also in future markets and have less risk.
Files
Download all files with code, backtest results and more are included in this post.
The strategy code
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
import numpy as np
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
class DemaSmaCrossover(IStrategy):
stoploss = -0.25
timeframe = "4h"
minimal_roi = {
"240": 0.05,
"480": 0.1,
"720": 0.17,
"960": 0.22,
"0": 0.3,
}
plot_config = {
"main_plot": {
# Configuration for main plot indicators.
# Specifies `ema10` to be red, and `ema50` to be a shade of gray
"dema": {"color": "red"},
"sma": {"color": "blue"},
},
"subplots": {
# Additional subplot RSI
"rsi": {"rsi": {"color": "blue"}, "rsi_sma": {"color": "red"}},
},
}
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe["lt_sma"] = ta.SMA(dataframe, timeperiod=55)
dataframe["sma"] = ta.SMA(dataframe, timeperiod=21)
dataframe["dema"] = ta.DEMA(dataframe, timeperiod=9)
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
dataframe["rsi_sma"] = dataframe["rsi"].rolling(window=21).mean()
# SMA check
dataframe["ma_pos"] = np.where(dataframe["dema"] > dataframe["sma"], 1, 0)
# RSI check
dataframe["rsi_pos"] = np.where(dataframe["rsi"] > dataframe["rsi_sma"], 1, 0)
# Posities tellen
dataframe["pos_cnt"] = dataframe["ma_pos"] + dataframe["rsi_pos"]
return dataframe
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe["close"]>dataframe["lt_sma"])
& (dataframe["pos_cnt"] == 2)
),
"buy",
] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe["dema"] < dataframe["sma"])
),
"sell",
] = 1
return dataframe
The hyperopt code
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
import numpy as np
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
# These libs are for hyperopt, remove if not used.
from functools import reduce
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,IStrategy, IntParameter)
class DemaSmaCrossoverHopt(IStrategy):
stoploss = -0.25
timeframe = "1d"
minimal_roi = {
"240": 0.05,
"480": 0.1,
"720": 0.17,
"960": 0.22,
"0": 0.3,
}
plot_config = {
"main_plot": {
# Configuration for main plot indicators.
# Specifies `ema10` to be red, and `ema50` to be a shade of gray
"buy_dema": {"color": "red"},
"buy_sma": {"color": "orange"},
"sell_dema": {"color": "blue"},
"sell_sma": {"color": "purple"},
"lt_sma": {"color": "green"},
},
"subplots": {
# Additional subplot RSI
"rsi": {"rsi": {"color": "blue"}, "rsi_sma": {"color": "red"}},
},
}
# Hyperopt spaces where you search for the best parameter value
lt_sma_space = IntParameter(40, 101, default=55, space="buy")
buy_sma_space = IntParameter(14, 40, default=21, space="buy")
buy_dema_space = IntParameter(6, 21, default=14, space="buy")
sell_sma_space = IntParameter(14, 40, default=21, space="sell")
sell_dema_space = IntParameter(6, 21, default=14, space="sell")
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# dataframe["lt_sma"] = ta.SMA(dataframe, timeperiod=55)
for val in self.lt_sma_space.range:
dataframe[f'lt_sma_{val}'] = ta.SMA(dataframe, timeperiod=val)
# dataframe["buy_sma"] = ta.SMA(dataframe, timeperiod=21)
for val in self.buy_sma_space.range:
dataframe[f'buy_sma_{val}'] = ta.SMA(dataframe, timeperiod=val)
# dataframe["buy_dema"] = ta.DEMA(dataframe, timeperiod=9)
for val in self.buy_dema_space.range:
dataframe[f'buy_dema_{val}'] = ta.DEMA(dataframe, timeperiod=val)
# dataframe["sell_sma"] = ta.SMA(dataframe, timeperiod=21)
for val in self.sell_sma_space.range:
dataframe[f'sell_sma_{val}'] = ta.SMA(dataframe, timeperiod=val)
# dataframe["sell_dema"] = ta.DEMA(dataframe, timeperiod=9)
for val in self.sell_dema_space.range:
dataframe[f'sell_dema_{val}'] = ta.DEMA(dataframe, timeperiod=val)
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
dataframe["rsi_sma"] = dataframe["rsi"].rolling(window=21).mean()
# # BUY MA check
# dataframe["buy_ma_pos"] = np.where(dataframe["buy_dema"] > dataframe["buy_sma"], 1, 0)
# # SELL MA
# dataframe["sell_ma_pos"] = np.where(dataframe["sell_dema"] < dataframe["sell_sma"], 1, 0)
# # RSI check
# dataframe["rsi_pos"] = np.where(dataframe["rsi"] > dataframe["rsi_sma"], 1, 0)
# # Posities tellene
# dataframe["pos_cnt"] = dataframe["buy_ma_pos"] + dataframe["rsi_pos"]
# print(dataframe)
return dataframe
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe["close"] > dataframe[f'lt_sma_{self.lt_sma_space.value}'])
& (dataframe[f'buy_dema_{self.buy_dema_space.value}'] > dataframe[f'buy_sma_{self.buy_sma_space.value}'])
& (dataframe["rsi"] > dataframe["rsi_sma"])
),
"buy",
] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe[f'sell_dema_{self.sell_dema_space.value}'] < dataframe[f'sell_sma_{self.sell_sma_space.value}'])
),
"sell",
] = 1
return dataframe
The hyperopt json results
{
"strategy_name": "DemaSmaCrossoverHopt",
"params": {
"trailing": {
"trailing_stop": false,
"trailing_stop_positive": null,
"trailing_stop_positive_offset": 0.0,
"trailing_only_offset_is_reached": false
},
"buy": {
"buy_dema_space": 14,
"buy_sma_space": 32,
"lt_sma_space": 101
},
"sell": {
"sell_dema_space": 11,
"sell_sma_space": 14
},
"protection": {},
"roi": {
"0": 0.149,
"1832": 0.106,
"3817": 0.074,
"9484": 0
},
"stoploss": {
"stoploss": -0.282
}
},
"ft_stratparam_v": 1,
"export_time": "2022-05-17 18:26:20.556672+00:00"
}