Back to Community
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...

Clone Algorithm
57
Loading...
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!!!

Clone Algorithm
57
Loading...
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

Clone Algorithm
27
Loading...
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(
    func=trade,
    date_rule=date_rules.every_day(),
    time_rule=time_rules.market_open(minutes=1),
    half_days=True)
    
def trade(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)
    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.