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.
The Strategy
EMA Indicator
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.
MACD Indicator
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:
Buy when:
- price > EMA(200) and
- MACD and MACD signal are both < 0
- MACD crosses over MACD signal
Sell when:
- fixed percentage of stop loss (1%) and profit target (2%).
For short positions:
Buy when:
- price < EMA(200) and
- MACD and MACD signal are both > 0
- MACD signal crosses over MACD
Sell when:
- 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.
Concluding Thoughts
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()
'''