Trader Pro’s video claimed that MACD+EMA trading strategy performed very well. I wanted to test his assertion and see how it played out with Bitcoin. In this article I will first describe the strategy, then backtest it with last 12 month’s five minute Bitcoin historical price data, afterwards optimize the strategy, and finally test the optimized parameters with data out of the range of the optimization. I would like to remind that this is not trading or investment advice, backtest results are often unreliable.
Exponential moving average (EMA) is a kind of moving average (MA). It allocates a greater weight and importance on the most recent data points. EMA reacts faster than a simple moving average to recent price changes. Traders use EMA, similar to other MAs, to detect buy and sell signals depending on crossovers and divergences from the historical mean. The most common used periods with EMA are 10, 50, and 200 periods.
When the momentum of the price increases, a shorter (e.g. 12) and a longer (e.g. 26) period EMA diverge from each other. MACD aims to profit from this divergence. The difference between the EMAs is plotted on a chart. A positive value indicates an upward momentum whereas a negative value represents a downwards momentum.
MACD function in TALib return three values: MACD, MACD signal, and histogram. We only use the first two in this strategy.
Entry and Exit Signals
For long positions:
- price > EMA(200) and
- MACD and MACD signal are both < 0
- MACD crosses over MACD signal
- fixed percentage of stop loss (1%) and profit target (2%).
For short positions:
- price < EMA(200) and
- MACD and MACD signal are both > 0
- MACD signal crosses over MACD
- fixed percentage of stop loss (1%) and profit target (2%).
Results and Interpretation
I tested this strategy within the time frame of 1 May 2020 and 5 May 2021. Here are the results:
Start 2020-05-01 00:00:00 End 2021-05-05 23:55:00 Duration 369 days 23:55:00 Exposure Time [%] 54.156457 Equity Final [$] 320916.64728 Equity Peak [$] 1151537.3636 Return [%] -67.908335 Buy & Hold Return [%] 554.260205 Return (Ann.) [%] -67.008369 Volatility (Ann.) [%] 12.597566 Sharpe Ratio 0.0 Sortino Ratio 0.0 Calmar Ratio 0.0 Max. Drawdown [%] -73.713143 Avg. Drawdown [%] -2.771741 Max. Drawdown Duration 355 days 20:55:00 Avg. Drawdown Duration 9 days 09:52:00 # Trades 744 Win Rate [%] 31.989247 Best Trade [%] 4.103754 Worst Trade [%] -1.321455 Avg. Trade [%] -0.159261 Max. Trade Duration 5 days 09:20:00 Avg. Trade Duration 0 days 06:23:00 Profit Factor 0.815869 Expectancy [%] -0.14776 SQN -2.376315
This does not look good. Where buy and hold was highly profitable this strategy lost money. I think we got whipsawed a lot. In its current form this strategy is not only useless but looks harmful. Let’s optimize and see how it can be made better.
Optimization and Optimized Results
I optimized stop loss and profit targets for both short and long positions. Here are the top ten performing optimizations:
profit_short profit stop_loss stop_loss_short 97 109 95 104 4.592635e+06 92 108 4.500133e+06 93 106 4.498104e+06 107 4.307740e+06 98 109 90 102 4.233051e+06 97 109 93 105 4.062826e+06 90 106 4.057114e+06 98 109 93 107 3.962142e+06 97 109 94 106 3.942139e+06 93 108 3.877335e+06
Below is the heatmap of the parameters. We are interested in the yellow areas.
Here are the results with the optimized parameters profit_short = 97, profit = 109, stop_loss = 95, and stop_loss_short = 104.
Start 2020-05-01 00:00:00 End 2021-05-05 23:55:00 Duration 369 days 23:55:00 Exposure Time [%] 91.019611 Equity Final [$] 4592635.10706 Equity Peak [$] 4886035.48912 Return [%] 359.263511 Buy & Hold Return [%] 554.260205 Return (Ann.) [%] 354.60579 Volatility (Ann.) [%] 303.798608 Sharpe Ratio 1.16724 Sortino Ratio 9.517271 Calmar Ratio 14.031138 Max. Drawdown [%] -25.272774 Avg. Drawdown [%] -1.387017 Max. Drawdown Duration 61 days 08:00:00 Avg. Drawdown Duration 1 days 01:41:00 # Trades 137 Win Rate [%] 55.474453 Best Trade [%] 11.651555 Worst Trade [%] -5.307878 Avg. Trade [%] 1.119704 Max. Trade Duration 29 days 09:00:00 Avg. Trade Duration 2 days 10:55:00 Profit Factor 1.619271 Expectancy [%] 1.284819 SQN 1.808693
Looks much better. We did not loose money and made about two thirds of buy and hold. 55% of the trades were winning trades. But notice that we wait a lot. Average trade duration was 2 days. And at least once we had to wait about a month. Here is a chart from the optimized results. The green triangles show successful trades and red ones show the failed ones.
Testing Optimized Parameters with Another Time Frame
Although I am not comfortable with setting the stop loss for short at a higher percentage at profit target, I will test the optimized strategy for one year ago using again a 12 month period. Between 1 May 2018 and 30 April 2020. Here are the results:
Start 2018-05-01 00:00:00 End 2020-04-30 23:55:00 Duration 730 days 23:55:00 Exposure Time [%] 90.787322 Equity Final [$] 757737.8354 Equity Peak [$] 1085257.11776 Return [%] -24.226216 Buy & Hold Return [%] -4.626487 Return (Ann.) [%] -12.540586 Volatility (Ann.) [%] 53.761354 Sharpe Ratio 0.0 Sortino Ratio 0.0 Calmar Ratio 0.0 Max. Drawdown [%] -55.7138 Avg. Drawdown [%] -8.489944 Max. Drawdown Duration 426 days 17:45:00 Avg. Drawdown Duration 42 days 22:38:00 # Trades 246 Win Rate [%] 47.560976 Best Trade [%] 10.927534 Worst Trade [%] -5.313921 Avg. Trade [%] -0.113487 Max. Trade Duration 66 days 00:05:00 Avg. Trade Duration 2 days 16:41:00 Profit Factor 1.009916 Expectancy [%] 0.024146 SQN -0.415716
Not very good again. Because we over-fitted. Although our winning rate is close to 50%, we would have lost money during this period had we used this strategy.
MACD and EMA are commonly used in trading strategies but as we have seen it is not always reliable. We got many entry signals but the highly volatile and unpredictable Bitcoin market led us to fail about half of our attempts. I suggest doing careful backtesting on your own to criticize any trading system that is claimed to be profitable, especially if you are dealing with different markets that the strategies were offered for. Again, not trading advice.
Complete Python Code
import sqlite3 as sql import pandas as pd import numpy as np import talib as talib from datetime import datetime from datetime import timedelta from backtesting import Strategy, Backtest from backtesting.lib import crossover from matplotlib import pyplot as plt import seaborn as sns dbfile = 'bitcoin.db' table = 'bitcoin_5minute_raw' conn = sql.connect(dbfile) start_date = '2021-05-01' how_many_months = "-24" SQL = "SELECT DISTINCT Date,Open,Close,Low,High,Volume_USD FROM " + \ table + " WHERE Date < date('"+ start_date +"') and Date > date('"+ \ start_date+"','start of month','"+how_many_months+ \ " month') ORDER BY Date" print(SQL) data = pd.read_sql(SQL, conn) data['Date'] = pd.to_datetime(data['Date']) data.set_index("Date", inplace = True) data.columns = ['Open','Close','Low','High','Volume']; class MACDCross(Strategy): stop_loss = 95 profit = 109 stop_loss_short = 104 profit_short = 97 long_short = 0 buy_price = 0 sell_price = 0 def init(self): self.macd, self.macdsignal, self.macdhist = self.I(talib.MACD, self.data.Close, fastperiod=12, slowperiod=26, signalperiod=9) self.ema200 = self.I(talib.EMA, self.data.Close, timeperiod=200) def next(self): price = self.data.Close[-1] if (not self.position and price > self.ema200[-1] and crossover(self.macd,self.macdsignal) and self.macd[-1] < 0 and self.macdsignal[-1] < 0): self.buy(sl=price*self.stop_loss/100) self.buy_price = price self.long_short = 1 elif (price > self.buy_price*self.profit/100 and self.long_short == 1): self.position.close() self.long_short = 0 elif (not self.position and price < self.ema200[-1] and crossover(self.macdsignal,self.macd) and self.macd[-1] > 0 and self.macdsignal[-1] > 0): self.sell(sl=price*self.stop_loss_short/100) self.sell_price = price self.long_short = 2 elif (price < self.sell_price*self.profit_short/100 and self.long_short == 2): self.position.close() self.long_short = 0 self.sell_price = 0 bt = Backtest(data, MACDCross, cash=1000000, commission=.002, exclusive_orders=True) output = bt.run() print(output) bt.plot() ''' stats, heatmap = bt.optimize(profit_short = range(90,99,1), profit = range(101,110,1), stop_loss=range(90,99,1), stop_loss_short=range(101,110,1), maximize='Equity Final [$]',return_heatmap=True) print(stats) print(stats._strategy) hm = heatmap.groupby(['profit', 'profit_short','stop_loss','stop_loss_short']).mean().unstack() print(heatmap.sort_values(ascending=False).iloc[:10]) plt.figure(figsize=(12, 10)) sns.heatmap(hm[::-1], cmap='viridis') plt.savefig('foo.png') bt.plot() '''