MACD Disappoints

The MACD is one of the most well know momentum indicators; however, the results of this textbook implementation disappoint. Over the past ten years, this trading strategy would have returned a whopping 87% less than its benchmark. I realize the algo is not perfect, and it doesn't incorporate shorting, but I just would have expected better out of such a widely used indicator. Your "polite" thoughts are more than welcome...

57
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
# This example algorithm uses the Moving Average Crossover Divergence (MACD) indicator as a buy/sell signal.

# When the MACD signal less than 0, the stock price is trending down and it's time to sell.
# When the MACD signal greater than 0, the stock price is trending up it's time to buy.

# Because this algorithm uses the history function, it will only run in minute mode.

import talib
import numpy as np
import pandas as pd

# Setup our variables
def initialize(context):
#context.stocks = symbols('MMM', 'SPY', 'GOOG_L', 'PG', 'DIA')
context.stocks = symbols('SPY')
context.pct_per_stock = 1.0 / len(context.stocks)

# Create a variable to track the date change to constrain the trading to
# once per day at market open
#context.date = None

def handle_data(context, data):
# Do nothing unless the date has changed
#todays_date = get_datetime().date()
#if todays_date == context.date:
#    return
#context.date = todays_date

# Load historical data for the stocks
prices = history(40, '1d', 'price')

# Create the MACD signal and pass in the three parameters: fast period, slow period, and the signal.
# This is a series that is indexed by sids.
macd = prices.apply(MACD, fastperiod=12, slowperiod=26, signalperiod=9)

# Iterate over the list of stocks
for stock in context.stocks:
current_position = context.portfolio.positions[stock].amount

# Close position for the stock when the MACD signal is negative and we own shares.
if macd[stock] < 0 and current_position > 0:
order_target(stock, 0)

# Enter the position for the stock when the MACD signal is positive and
# our portfolio shares are 0.
elif macd[stock] > 0 and current_position == 0:
order_target_percent(stock, context.pct_per_stock)

record(macd=macd[symbol('SPY')])
#record(goog=macd[symbol('GOOG_L')],
#       spy=macd[symbol('SPY')],
#       mmm=macd[symbol('MMM')])

# Define the MACD function
def MACD(prices, fastperiod=12, slowperiod=26, signalperiod=9):
'''
Function to return the difference between the most recent
MACD value and MACD signal. Positive values are long
position entry signals

optional args:
fastperiod = 12
slowperiod = 26
signalperiod = 9

Returns: macd - signal
'''
macd, signal, hist = talib.MACD(prices,
fastperiod=fastperiod,
slowperiod=slowperiod,
signalperiod=signalperiod)
return macd[-1] - signal[-1]

There was a runtime error.
5 responses

BTW - Here's another MACD backtest, but instead of trading SPY, this one trades XOM. The results are much worse! This MACD trading strategy returned 163% less than its benchmark!!!

57
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
# This example algorithm uses the Moving Average Crossover Divergence (MACD) indicator as a buy/sell signal.

# When the MACD signal less than 0, the stock price is trending down and it's time to sell.
# When the MACD signal greater than 0, the stock price is trending up it's time to buy.

# Because this algorithm uses the history function, it will only run in minute mode.

import talib
import numpy as np
import pandas as pd

# Setup our variables
def initialize(context):
#context.stocks = symbols('MMM', 'SPY', 'GOOG_L', 'PG', 'DIA')
context.stocks = symbols('XOM')
context.pct_per_stock = 1.0 / len(context.stocks)

# Create a variable to track the date change to constrain the trading to
# once per day at market open
#context.date = None

def handle_data(context, data):
# Do nothing unless the date has changed
#todays_date = get_datetime().date()
#if todays_date == context.date:
#    return
#context.date = todays_date

# Load historical data for the stocks
prices = history(40, '1d', 'price')

# Create the MACD signal and pass in the three parameters: fast period, slow period, and the signal.
# This is a series that is indexed by sids.
macd = prices.apply(MACD, fastperiod=12, slowperiod=26, signalperiod=9)

# Iterate over the list of stocks
for stock in context.stocks:
current_position = context.portfolio.positions[stock].amount

# Close position for the stock when the MACD signal is negative and we own shares.
if macd[stock] < 0 and current_position > 0:
order_target(stock, 0)

# Enter the position for the stock when the MACD signal is positive and
# our portfolio shares are 0.
elif macd[stock] > 0 and current_position == 0:
order_target_percent(stock, context.pct_per_stock)

record(macd=macd[symbol('XOM')])
#record(goog=macd[symbol('GOOG_L')],
#       spy=macd[symbol('SPY')],
#       mmm=macd[symbol('MMM')])

# Define the MACD function
def MACD(prices, fastperiod=12, slowperiod=26, signalperiod=9):
'''
Function to return the difference between the most recent
MACD value and MACD signal. Positive values are long
position entry signals

optional args:
fastperiod = 12
slowperiod = 26
signalperiod = 9

Returns: macd - signal
'''
macd, signal, hist = talib.MACD(prices,
fastperiod=fastperiod,
slowperiod=slowperiod,
signalperiod=signalperiod)
return macd[-1] - signal[-1]

There was a runtime error.

Technical analysis is mostly garbage, in my humble opinion. I've found it more helpful to focus on genuine situations where people are acting against their own self-interest, and find out how you can turn that into a trading signal/system.

i have read several post now about how so many indicators disappoint. maybe it is the creative mind that is lacking to find a way to use these technical indicators. i know of many successful independently audited track record money managers who use stupid disappointing moving averages to make millions for their clients. so i really think it's more up to the individual programming to succeed than the indicator chosen, just a thought.

Simon & Mark - Thanks for your feedback! Just to let you know, I have found that some technical indicators work much better than others. Although this standard implementation of the MACD algo was disappointing, I haven't tried tweaking its parameters.

Exact same strategy but modified for Quantopian 2. You would think the results would be identical but far from it.. weird

27
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
# This example algorithm uses the Moving Average Crossover Divergence (MACD) indicator as a buy/sell signal.

# When the MACD signal less than 0, the stock price is trending down and it's time to sell.
# When the MACD signal greater than 0, the stock price is trending up it's time to buy.

# Because this algorithm uses the history function, it will only run in minute mode.

import talib
import numpy as np
import pandas as pd

# Setup our variables
def initialize(context):
#context.stocks = symbols('MMM', 'SPY', 'GOOG_L', 'PG', 'DIA')
context.stocks = symbols('spy')
context.pct_per_stock = 1.0 / len(context.stocks)

# Create a variable to track the date change to constrain the trading to
# once per day at market open
#context.date = None

schedule_function(
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open(minutes=1),
half_days=True)

# Do nothing unless the date has changed
#todays_date = get_datetime().date()
#if todays_date == context.date:
#    return
#context.date = todays_date

# Load historical data for the stocks
prices = history(40, '1d', 'price')

# Create the MACD signal and pass in the three parameters: fast period, slow period, and the signal.
# This is a series that is indexed by sids.
macd = prices.apply(MACD, fastperiod=12, slowperiod=26, signalperiod=9)
print macd

# Iterate over the list of stocks
for stock in context.stocks:
current_position = context.portfolio.positions[stock].amount

# Close position for the stock when the MACD signal is negative and we own shares.
if macd[stock] < 0 and current_position > 0:
order_target(stock, 0)

# Enter the position for the stock when the MACD signal is positive and
# our portfolio shares are 0.
elif macd[stock] > 0 and current_position == 0:
order_target_percent(stock, context.pct_per_stock)

record(macd=macd[symbol('SPY')])
#record(goog=macd[symbol('GOOG_L')],
#       spy=macd[symbol('SPY')],
#       mmm=macd[symbol('MMM')])

# Define the MACD function
def MACD(prices, fastperiod=12, slowperiod=26, signalperiod=9):
'''
Function to return the difference between the most recent
MACD value and MACD signal. Positive values are long
position entry signals

optional args:
fastperiod = 12
slowperiod = 26
signalperiod = 9

Returns: macd - signal
'''
macd, signal, hist = talib.MACD(prices,
fastperiod=fastperiod,
slowperiod=slowperiod,
signalperiod=signalperiod)

return macd[-1] - signal[-1]

There was a runtime error.