Back to Community
Hull Moving Average

Could I get some help patching in a weighted moving average? I was trying to do so though talib, but cant get it to work! My end goal was to create a hull moving average, when positive it would buy and when negative it would short. If you run a 13 day HMA in a charting program, you'll see it's probably the first technical indicator to signal a change in direction, and rarely gives false signs. I think this has great potential as a quant indicator.

If we can import weighted moving average through talib, it would be decently simple calculations:
HMA(N) = WMA(2*WMA(N/2) –
WMA(N)),sqrt(N))

I've found that N is best left at a fib number, I use 13 for my trading. I'm completely new to coding and fiddled with this for a few hours, its definitely over my head. I cant figure out how to patch in a weighted moving average through talib.

When hull moving average is positive, it is the first technical indicator I've found of a buy signal. When HMA turns negative, its time to short. I would LOVE to see a python code for this, but cant patch in WMA!

11 responses

@Tyler S. I think this is about right. Have a look-see.

Added a "HMAIsUp" flag on the data[stock] security object. When tested with your settings I can't say that they reflect the best conditions. Weekly maybe? Or daily against hourly? Anyway, you can view just the Close and the HMAIsUp in the recorded metrics in the chart to see their values.

Clone Algorithm
100
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
import talib
import math

HMAPeriods   = 42
PricePeriods = 17

def initialize(context):
    symbols('SPY', 'DIA', 'QQQ')
    schedule_function(func=CalculateHMA, date_rule=date_rules.every_day())
    schedule_function(func=HandleEntry,  date_rule=date_rules.every_day())    
    
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~    
def handle_data(context, data):
    plotStock       = data.keys()[0]
    record(Leverage = context.account.leverage)    
    record(Close    = data[plotStock].close_price)
    record(SMA      = data[plotStock].mavg(PricePeriods))
    if('HMA' in data[plotStock]):
        record(HMA  = data[plotStock].HMA)
        record(HMAUp = data[plotStock].HMA if data[plotStock].HMAIsUp else None)
        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def HandleEntry(context, data):
    eligible   = [] 
    ineligible = []
    for stock in data:
        if (data[stock].mavg(PricePeriods) > data[stock].HMA):
            eligible.append(stock)
        else:
            ineligible.append(stock)
            
    eligibleCount = float(len(eligible))
    for stock in eligible:
        order_target_percent(stock, 1.0 / eligibleCount)
        if (context.portfolio.positions[stock].amount == 0.0):
            print("  Entry Long  {0:<5} @ {1:>6.2f}".format(
                    stock.symbol, data[stock].close_price))
        
    for stock in ineligible:
        order_target_percent(stock, 0.0)
        if (context.portfolio.positions[stock].amount != 0.0):        
            print("  Exit Long   {0:<5} @ {1:>6.2f}".format(
                    stock.symbol, data[stock].close_price))    
            
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateHMA(context, data):
    closes   = history(HMAPeriods * 2, "1d", "close_price")
    closes   = closes.dropna(axis=1)
    valid    = [sid for sid in closes if sid in data]  
    closes   = closes[valid]      
    wmaA     = closes.apply(talib.MA,   timeperiod = HMAPeriods / 2, matype = MAType.WMA).dropna() * 2.0
    wmaB     = closes.apply(talib.MA,   timeperiod = HMAPeriods, matype = MAType.WMA).dropna()
    wmaDiffs = wmaA - wmaB
    hma      = wmaDiffs.apply(talib.MA, timeperiod = math.sqrt(HMAPeriods), matype = MAType.WMA)
    
    for stock in data:
        data[stock].HMA     = hma[stock][-1]
        data[stock].HMAIsUp = hma[stock][-1] > hma[stock][-2]
    
    #record(Close    = data[data.keys()[0]].close_price)
    #record(WmaA     = wmaA[data.keys()[0]][-1])
    #record(WmaB     = wmaB[data.keys()[0]][-1])
    #record(wmaDiffs = wmaDiffs[data.keys()[0]][-1])
    #record(HullMA   = hma[data.keys()[0]][-1])
    
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class MAType():
    SMA   = 0; EMA   = 1; WMA   = 2; DEMA  = 3; TEMA  = 4;    
    TRIMA = 5; KAMA  = 6; MAMA  = 7; T3    = 8
There was a runtime error.

@Market Tech
Thank you! The calculations look correct. I'm trying to figure out where the lag is coming from. Is the HMA really that poor of an indicator?? Here is a screen shot from thinkorswim on aapl, with a hull moving average. I thought it would backtest much better than this.

What if you modified this code to generate buys and sells based off the change in hma? If hmahma-1, buy? It seems it currently watches for hma/sma crossovers.

Also, if there was a large market move in one day, wouldn't this code only make the trade after the fact? Is it hard to adjust timeframes so it can pull 1m data and trade accordingly but still use a 13 day hma?

Sorry I haven't contributed more! I'm still a python noob so this is a bit over my head :)

You can switch the strategy to use the data[stock].HMAIsUp flag to determine when to get in or out.

    for stock in data:  
        if (data[stock].HMAIsUp):  
            eligible.append(stock)  
        else:  
            ineligible.append(stock)  

And of course change the periods to 13 to match your settings:

HMAPeriods   = 13  

Regarding the calculation, TradingView appears to have nearly the same lines when compared to the above chart:

Tradingview.com HMA compared to Quantopian HMA

That ThinkOrSwim image was not really representative. You should plot only the Close price when comparing. Looking at all those garish colors in ToS makes it look like the HMA indicator is much more responsive as the balance of the colored bars are to one side or another, but, it's not. Just plot the Close price to really see.

General caveats: No single moving average (of whatever type) will be the one thing you can use to trade. Especially on individual securities. You can use the HMA as a general basis to determine trend perhaps, just like you can do with any of the other moving averages, but it's just a generalization. Using the HMA to trade by itself will just get you into Losing Town that much faster.

Has something changed? I get a runtime error:

There was a runtime error.
AttributeError: 'zipline._protocol.SidView' object has no attribute 'HMA'
... USER ALGORITHM:57, in CalculateHMA
data[stock].HMA = hma[stock][-1]

Blame Q's engineering. They changed stuff and broke the web.

Hi guys,

About two months ago, Quantopian rolled out a major update. You can read about it here. A lot of our API has changed for the better, and unfortunately that means that some old code has broken. Here is the migration guide for the new API.

Basically, storing the value of the Hull Moving Average as data[stock].HMA is no longer possible. I would recommend that you switch to using a dict which is a property of context, like this:

context.hma_dict = {}

context.hma_dict[stock] = hma[stock][-1]  
Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Thanks. My Python isn't so great so I will be struggling for quite a while trying to get an HMAisUp flag into my trading rules using the Quantopian 2.

So, Nathan, why won't a dynamic property attach to the Expando Object below?
Mark, see code below, this appears to work in QII.
Well, aside from all the Q-broke-the-Web messages.
Criminy Q, give me a "QVersion = 1.0" option in my algos so I don't have to experience your adolescent growing pains.
I don't have time to let this trickle the excruciatingly slow backtest, so I post the code here.

import talib  
import math

HMAPeriods   = 11  
HMAPeriodsb  = 21  
PricePeriods = 5

def initialize(context):  
    symbols('SPY', 'DIA', 'QQQ')  
    schedule_function(func=InitFramework, date_rule=date_rules.every_day())  
    schedule_function(func=CalculateHMA,  date_rule=date_rules.every_day())  
    schedule_function(func=CalculateHMAb, date_rule=date_rules.every_day())  
    schedule_function(func=HandleEntry,   date_rule=date_rules.every_day())  
    context.Indicators = {}  
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
def handle_data(context, data):  
    plotStock       = data.keys()[0]  
    record(Leverage = context.account.leverage)  
    record(Close    = data[plotStock].close_price)  
    if (not plotStock in context.Indicators):  
        return  
    indicators = context.Indicators[plotStock]  
    if (not indicators is None):  
        record(HMA  = indicators.HMA)  
        record(HMAbUp = indicators.HMAb if indicators.HMAbIsUp else None)  
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
def InitFramework(context, data):  
    for stock in data:  
        if (stock not in context.Indicators):  
            context.Indicators[stock] = Expando()

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
def HandleEntry(context, data):  
    eligible   = []  
    ineligible = []  
    for stock in data:  
        indicators = context.Indicators[stock]  
        if (indicators is None):  
            continue  
        if (indicators.HMAb > indicators.HMA):  
            eligible.append(stock)  
        else:  
            ineligible.append(stock)  
    eligibleCount = float(len(eligible))  
    for stock in eligible:  
        order_target_percent(stock, 1.0 / eligibleCount)  
        if (context.portfolio.positions[stock].amount == 0.0):  
            print("  Entry Long  {0:<5} @ {1:>6.2f}".format(  
                    stock.symbol, data[stock].close_price))  
    for stock in ineligible:  
        order_target_percent(stock, 0.0)  
        if (context.portfolio.positions[stock].amount != 0.0):  
            print("  Exit Long   {0:<5} @ {1:>6.2f}".format(  
                    stock.symbol, data[stock].close_price))  
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
def CalculateHMA(context, data):  
    closes   = history(HMAPeriods * 2, "1d", "close_price")  
    closes   = closes.dropna(axis=1)  
    valid    = [sid for sid in closes if sid in data]  
    closes   = closes[valid]  
    wmaA     = closes.apply(talib.MA,   timeperiod = HMAPeriods / 2, matype = MAType.WMA).dropna() * 2.0  
    wmaB     = closes.apply(talib.MA,   timeperiod = HMAPeriods, matype = MAType.WMA).dropna()  
    wmaDiffs = wmaA - wmaB  
    hma      = wmaDiffs.apply(talib.MA, timeperiod = math.sqrt(HMAPeriods), matype = MAType.WMA)  
    try:  
        for stock in data:  
            context.Indicators[stock].HMA = hma[stock][-1]  
            context.Indicators[stock].HMAIsUp = hma[stock][-1] > hma[stock][-2]  
    except:  
        return

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
def CalculateHMAb(context, data):  
    closes   = history(HMAPeriodsb * 2, "1d", "close_price")  
    closes   = closes.dropna(axis=1)  
    valid    = [sid for sid in closes if sid in data]  
    closes   = closes[valid]  
    wmaA     = closes.apply(talib.MA,   timeperiod = HMAPeriodsb / 2, matype = MAType.WMA).dropna() * 2.0  
    wmaB     = closes.apply(talib.MA,   timeperiod = HMAPeriodsb, matype = MAType.WMA).dropna()  
    wmaDiffs = wmaA - wmaB  
    hma      = wmaDiffs.apply(talib.MA, timeperiod = math.sqrt(HMAPeriodsb), matype = MAType.WMA)  
    try:  
        for stock in data:  
            context.Indicators[stock].HMAb = hma[stock][-1]  
            context.Indicators[stock].HMAbIsUp = hma[stock][-1] > hma[stock][-2]  
    except:  
        return  
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
class MAType():  
    SMA   = 0; EMA   = 1; WMA   = 2; DEMA  = 3; TEMA  = 4;  
    TRIMA = 5; KAMA  = 6; MAMA  = 7; T3    = 8  
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
class Expando(object):  
    def __init__(self):  
        self.HMA = None  
        self.HMAIsUp = False  
        self.HMAb = None  
        self.HMAbIsUp = False  

Thanks. I will use this as a starting point. I will be working to get both HMA and RSI into a model. From experience on Trade Station, I have noticed adding parameters helps the return, e.g.:

def HandleEntry(context, data):  
    eligible   = []  
    ineligible = []  
    #  MG modification  
    multiplier = 1.02  # parametrize and tweak this as needed in backtests along with RSI  
    for stock in data:  
        indicators = context.Indicators[stock]  
        if (indicators is None):  
            continue  
 #       if (indicators.HMAb > indicators.HMA):                 # Original statement  
        if (indicators.HMAb  > (multiplier * indicators.HMA)):  # MG Mod  

@Mark G. You can check out this project which I just updated:
https://www.quantopian.com/posts/trying-on-for-size-a-technical-framework

As mentioned it still has deprecated code, but it runs and has many of the indicators you might want to use.

I may update this further to "QII"ify it. I don't much enjoy having to rewrite stuff because of arbitrary decisions made by others...

Thanks I had noticed this before but the learning curve for me is daunting. I'm at the level of learning how to properly access individual rows & columns from the Pandas structures the framework creates. Your project clearly contains many useable things for me.