Hull Moving Average Turning Points and Concavity (2nd Derivatives)

M

mashume

Well-known member
VIP
As I was looking at the Hull Moving Average, it occurred to me that taking the local minimums and maximums might not be the most effective use of it's smooth nature. Nor did it seem that a moving average cross over would provide timely signals.

And then I remembered that I used to teach physics and calculus and decided to look at rates of change and that lead me to...

CONCAVITY

which is nothing but the second derivative of the function (if you remember or not) :) and so this indicator was born.

It is in two parts -- the upper which is the Hull Moving Average with the addition of colored segments representing concavity and turning points: maxima, minima and inflection. The last of these are of the greatest interest. The second part is a plot of the calculation used in finding the turning points (which is roughly the second derivative of the HMA function, where zero crosses are the inflection points.



UPPER INDICATOR

Upper HMA colors:
  • Green: Concave Up but HMA decreasing. The 'mood' has changed and the declining trend of the HMA is slowing. Long trades were entered at the turning point
  • Light Green: Concave up and HMA increasing. Price is increasing, and since the curve is still concave up, it is accelerating upward.
  • Orange: Concavity is now downward, and though price is still increasing, the rate has slowed, perhaps the mood has become less enthusiastic. We EXIT the trade (long) when this phase starts. Very little additional upward price movement is likely.
  • Red: Concave down and HMA decreasing. Not good for long trades, but get ready for a turning point to enter long on again.

Upper Label Colors:
these are useful for getting ready to enter a trade, or exit a trade and serve as warnings that a turning point may be reached soon
  • Green: Concave up and divergence (the distance from the expected HMA value to the actual HMA value is increasing). That is, we're moving away from a 2nd derivative zero crossover.
  • Yellow: Concave up but the divergence is decreasing (heading toward a 2nd derivative zero crossover); it may soon be time to exit the trade.
  • Red: Concave down and the absolute value of the divergence is increasing (moving away from crossover)
  • Pink: Concave down but approaching a zero crossover from below (remember that that is the entry signal, so pink means 'get ready').
Arrows are provided as Buy and Sell and could perhaps be scanned against.

LOWER INDICATOR

For those who prefer less cluttered uppers, I offer the plot of the divergence from expected HMA values; analogous to the second derivative in that the zero crossovers are of interest, as is the slope of the line. The further from zero, the stronger the curve of the concavity, and the more likely to reach a local minima or maxima in short order.


Miscelaneous

If you find that there are too many buy and sell signals, you can change the length of the HMA (I find 34 a happy medium, though 55 and sometimes 89 can be appropriate). You can also play with the value of the lookback, though that will slow down signals.

This works well on Daily timeframes as well as intraday candles.

I set it up with High + Low / 2 as the default so that it shouldn't wait for close prices. That may not be appropriate to how you wish to trade.

Comments welcome. Let me know how you use it, how it works for your trades, and whether it made your head hurt trying to remember your calculus.

Happy Trading,
Mashume

UPPER:
https://tos.mx/cgKFdmm

LOWER:
https://tos.mx/p0k7ims

UPPER CODE

Code:
#
# Hull Moving Average Concavity and Turning Points
#  or
# The Second Derivative of the Hull Moving Average
#
# Author: Seth Urion (Mashume)
# Version: 2020-02-23 V3
# Faster, but not necessarily mathematically as good as the first
#
# This code is licensed (as applicable) under the GPL v3
#
# ----------------------


declare upper;

input price = HL2;
input HMA_Length = 21;
input lookback = 2;

plot HMA = HullMovingAvg(price = price, length = HMA_Length);

# def delta_per_bar =
# (fold n = 0 to lookback with s do s + getValue(HMA, n, lookback - 1)) / lookback;

def delta = HMA[1] - HMA[lookback + 1];
def delta_per_bar = delta / lookback;

def next_bar = HMA[1] + delta_per_bar;

def concavity = if HMA > next_bar then 1 else -1;

plot turning_point = if concavity[1] != concavity then HMA else double.nan;

HMA.AssignValueColor(color = if concavity[1] == -1 then
    if HMA > HMA[1] then color.dark_orange else color.red else
    if HMA < HMA[1] then color.dark_green else color.green);

HMA.SetLineWeight(3);

turning_point.SetLineWeight(4);
turning_point.SetPaintingStrategy(paintingStrategy = PaintingStrategy.POINTS);
turning_point.SetDefaultColor(color.white);

plot MA_Max = if HMA[-1] < HMA and HMA > HMA[1] then HMA else Double.NaN;
MA_Max.SetDefaultColor(Color.WHITE);
MA_Max.SetPaintingStrategy(PaintingStrategy.SQUARES);

#####
# Added Alerts 2020-02-23
Alert(condition = buy, text = "Buy", "alert type" = Alert.BAR, sound = Sound.Chimes);
Alert(condition = sell, text = "Sell", "alert type" = Alert.BAR, sound = Sound.Chimes);

LOWER CODE

Code:
#
# Hull Moving Average Concavity Divergence
#  or
# The Second Derivative of the Hull Moving Average
#
# Author: Seth Urion (Mashume)
# Version: 2020-02-23 V3
#
# This code is licensed (as applicable) under the GPL v3
#
# ----------------------

declare lower;

input price = OPEN;

input HMA_length = 55;
input lookback = 2;

def HMA = HullMovingAvg(length = HMA_length, price = price);

def delta = HMA[1] - HMA[lookback + 1];
def delta_per_bar = delta / lookback;

def next_bar = HMA[1] + delta_per_bar;

def concavity = if HMA > next_bar then 1 else -1;

plot zero = 0;
zero.setdefaultcolor(color.gray);
zero.setpaintingstrategy(PaintingStrategy.DASHES);

plot divergence = HMA - next_bar;
divergence.setDefaultColor(Color.LIME);
divergence.SetLineweight(2);

plot cx_up = if divergence crosses above zero then 0 else double.nan;
cx_up.SetPaintingStrategy(PaintingStrategy.POINTS);
cx_up.SetDefaultColor(Color.LIGHT_GREEN);
cx_up.SetLineWeight(4);

plot cx_down = if divergence crosses below zero then 0 else double.nan;
cx_down.SetPaintingStrategy(PaintingStrategy.POINTS);
cx_down.SetDefaultColor(Color.RED);
cx_down.SetLineWeight(4);

NEW VERSION 4

Now with Mobile!
https://tos.mx/NXqbYE9
 
Last edited:
BenTen

BenTen

Administrative
Staff
VIP
Thank you for sharing this. Since ToS shareable links are still not working, I went ahead and posted the source code for both the upper and lower study directly into the post. I hope you don't mind :)
 
C

chillc15

New member
@mashume I would personally like to say thank you for your input and find this a very interesting take on the Hull and a better representation of a moving average that I have ever seen. The ability for the trader to see possible turning points is most impressive. Understandably we all know or should know that most indicators and moving averages alike are lagging but yours puts a new spin and element into the mix.

The Hull MA can be a useful tool but I do find like others it has its limitations and can overshoot etc. I (personal taste) find the ALMA to be the smoothest of the smooth when it comes to a MA. It has its limitations as well but utilizing other indicators like support and resistance and price action etc works for me.

My question to you is using your formulas, is it possible to apply the same principles and math to the ALMA MA for an upper study MA.

Thank you again.
 
Last edited:
M

mailbagman2000

Member
Great indicator, is there anyway to backtest and show profit and loss? or run as a scan?
 
R

RickAns

Member
VIP
Very nice, mashume. Thanks for sharing this with us. I look forward to trying it out.
 
M

mashume

Well-known member
VIP
Thank you all for your comments and encouragement.

I have a laundry list of things in this post:
  1. Variations to scripts
  2. Scanner
  3. Testing

Script Variations

@chillc15, I'm intrigued by the Arnaud Legoux MA, and so here is a variant of the upper study that will allow you to choose between Hull, Simple, Exponential, Williams, and ALMA. I haven't gone through the various permutations, but bear in mind that the 'overshoot' and smoothing of the Hull is part of what makes the turning point signal so attractive.

@Miket Used your posted code for the ALMA. Hope you're good with this use. Thanks!

Code:
#
# Multiple Moving Average Concavity and Turning Points
#  or
# The Second Derivative of the A Moving Average
#
# via useThinkScript
# request from chillc15
# Added Arnaud Legoux MA and other Moving Averages
#
# Author: Seth Urion (Mahsume)
# Version: 2020-02-22 V2
#
# This code is licensed (as applicable) under the GPL v3
#
# ----------------------

declare upper;

input price = HL2;
input MA_Length = 21;
input lookback = 2;

input MovingAverage = {default "HMA", "EMA", "SMA", "WMA", "ALMA"};

script ALMA {
# Attributed to Miket
# https://tos.mx/9mznij
# https://usethinkscript.com/threads/alma-arnaud-legoux-ma-indicator-for-thinkorswim.174/
input Data = close;
input Window = 9;
input Sigma = 6;
input Offset = 0.85;

def m = (Offset * (Window - 1));
def s = Window/Sigma;

def SumVectorData = fold y = 0 to Window with WS do WS + Exp(-(sqr(y-m))/(2*sqr(s))) * getvalue(Data, (Window-1)-y);
def SumVector = fold z = 0 to Window with CW do CW + Exp(-(sqr(z-m))/(2*sqr(s)));

plot ALMA = SumVectorData / SumVector;
}

plot MA;
switch (MovingAverage) {
case EMA:
    MA = MovAvgExponential(price, length = MA_Length);
case SMA:
    MA = simpleMovingAvg(price, length = MA_Length);
case WMA:
    MA = wma(price, length = MA_Length);
case ALMA:
    MA = ALMA(Data = price, window = MA_Length);
default:
    MA = HullMovingAvg(price = price, length = MA_Length);
}


def delta = MA[1] - MA[lookback + 1];
def delta_per_bar = delta / lookback;

def next_bar = MA[1] + delta_per_bar;

def concavity = if MA > next_bar then 1 else -1;

plot turning_point = if concavity[-1] != concavity then MA else double.nan;

MA.AssignValueColor(color = if concavity == -1 then
    if MA > MA[1] then color.dark_orange else color.red else
    if MA < MA[1] then color.dark_green else color.green);

MA.SetLineWeight(3);

turning_point.SetLineWeight(4);
turning_point.SetPaintingStrategy(paintingStrategy = PaintingStrategy.POINTS);
turning_point.SetDefaultColor(color.white);

plot MA_Max = if MA[-1] < MA and MA > MA[1] then MA else Double.NaN;
MA_Max.SetDefaultColor(Color.WHITE);
MA_Max.SetPaintingStrategy(PaintingStrategy.SQUARES);
MA_Max.SetLineWeight(3);

plot MA_Min = if MA[-1] > MA and MA < MA[1] then MA else Double.Nan;
MA_Min.SetDefaultColor(Color.WHITE);
MA_Min.SetPaintingStrategy(PaintingStrategy.TRIANGLES);
MA_Min.SetLineWeight(3);

plot sell = if turning_point and concavity == 1 then high else double.nan;
sell.SetDefaultColor(Color.DARK_ORANGE);
sell.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN);
sell.SetLineWeight(3);

plot buy = if turning_point and concavity == -1 then low else double.nan;
buy.SetDefaultColor(Color.CYAN);
buy.SetPaintingStrategy(PaintingStrategy.ARROW_UP);
buy.SetLineWeight(3);

def divergence = MA - next_bar;

addLabel(yes, concat("DIVERGENCE: " , divergence), color = if concavity < 0 then if divergence[1] > divergence then Color.RED else color.PINK else if divergence[1] < divergence then color.green else color.yellow);

SCANS

@mailbagman2000 , Scans, yes...
Scan 1 -- Buy signals.

This scan relies on the upper study being called "Concavity" and is entered on the scan page as a custom.
Code:
Concavity("hma length" = 55, price = CLOSE)."buy" is true within 2 bars

Scan 2 -- Stocks approaching possible Buy signals
I thought that, since the lower indicator crossovers can generate signals, that an 'early warning' scan might be of interest. We can scan for any HMA that has a negative convergence, and look for a decrease in the distance below zero. It may be a long way off, it may never get there, but we are alerted before the trade entry.

Code:
script ConcavityDivergence {
#
# Hull Moving Average Concavity Divergence
#  or
# The Second Derivative of the Hull Moving Average
#
# Author: Seth Urion (Mahsume)
# Version: 2020-02-21 Initial Public
#
# This code is licensed (as applicable) under the GPL v3
#
# ----------------------

declare lower;

input price = HL2;

input HMA_length = 34;
input lookback = 2;

def HMA = HullMovingAvg(length = HMA_length, price = price);

def delta = HMA[1] - HMA[lookback + 1];
def delta_per_bar = delta / lookback;

def next_bar = HMA[1] + delta_per_bar;

def concavity = if HMA > next_bar then 1 else -1;

plot zero = 0;
zero.setdefaultcolor(color.gray);
zero.setpaintingstrategy(PaintingStrategy.DASHES);

plot divergence = displacer(-1, HMA - next_bar);
divergence.setDefaultColor(Color.LIME);
divergence.SetLineweight(2);

plot cx_up = if divergence crosses above zero then 0 else double.nan;
cx_up.SetPaintingStrategy(PaintingStrategy.POINTS);
cx_up.SetDefaultColor(Color.LIGHT_GREEN);
cx_up.SetLineWeight(4);

plot cx_down = if divergence crosses below zero then 0 else double.nan;
cx_down.SetPaintingStrategy(PaintingStrategy.POINTS);
cx_down.SetDefaultColor(Color.RED);
cx_down.SetLineWeight(4);

}

def signal = ConcavityDivergence("hma length" = 55)."concavity";
def HMA = ConcavityDivergence("hma length" = 55)."HMA";
def next = ConcavityDivergence("hma length" = 55)."next_bar";

plot buy = signal < 0 and (HMA - next) > (HMA[1] - next[1]);

TESTING

I used the built-in strategy feature of thinkorswim, with block sizes of 100. All tests were run on 1 year Daily charts, with an HMA length of 55 and set price to CLOSE

First SPX
Buy and Hold: $55,600
Strategy: $77,372
Delta + $21772



Next ADI
Buy and Hold: $1718
Strategy: $5202
Delta: + $3483


Last BA
Buy and Hold: ($9322)
Strategy: $14393
Delta: + $23715


SHORT TIME FRAMES
I did not take time today to test shorter timeframes, though I imagine the results would be good. If there is interest, I'll try to post up some another time.

If you've read this far, thank you.
Happy trading and good luck.
 
Last edited:
T

toopoor88

Member
@mashume Hi, possibly you can link the strategy. For some reason when I try to copy it for a scan or backtest it doesn't work for me :( .
 
S

StockJockey

New member
Mashume your script is nothing short of amazing. Thank you , Thank You sir! Is there anyway that I can have the Strategy Version to use without the "addorder" I tested all 3 versions of your script and the strategy version works best for me. I cannot use the strategy version with addorder during the "Normal Market Hours" on thinkorswim , If I disable the Long Enter and Long Exit the visuals disappear. can you please help? TIA !
 
M

mashume

Well-known member
VIP
@StockJockey --

Per your request, below is the exact code from the study, with the buy and sell order mechanics replaced with plots. Hope that gets toward what you wanted.

-mashume

Code:
input price = HL2;
input HMA_Length = 21;
input lookback = 2;

def HMA = HullMovingAvg(price = price, length = HMA_Length);

def delta = HMA[1] - HMA[lookback + 1];
def delta_per_bar = delta / lookback;

def next_bar = HMA[1] + delta_per_bar;

def concavity = if HMA > next_bar then 1 else -1;

def turning_point = if concavity[-1] != concavity then HMA else Double.NaN;

Plot buy = if turning_point and concavity == -1 then low else double.nan;

plot buy = if turning_point and concavity == -1 then low else double.nan;
buy.SetDefaultColor(Color.CYAN);
buy.SetPaintingStrategy(PaintingStrategy.ARROW_UP);
buy.SetLineWeight(3);

plot sell = if  turning_point and concavity == 1 then high else double.nan;
sell.SetDefaultColor(Color.DARK_ORANGE);
sell.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN);
sell.SetLineWeight(3);
 
Last edited:
P

Poc

New member
Thanks. Fantastic, spending the weekend playing with chart settings and indicator settings. One request: Add an offset variable for the buy and sell signals to adjust the distance away from the price chart and the indicator. With shorter time frame charts, the arrows tighten down right to the indicator plot and the appearance gets busy.
 
T

toopoor88

Member
@mashume any chance adding a sound alert to the lower and upper?
 
M

mashume

Well-known member
VIP
The Upper Indicator code above has had the following lines added to the end:

Code:
Alert(condition = buy, text = "Buy", "alert type" = Alert.BAR, sound = Sound.Chimes);
Alert(condition = sell, text = "Sell", "alert type" = Alert.BAR, sound = Sound.Chimes);

I have NOT yet 'shared' a version with these alerts, but can at some point. This was just a quick fix for those who want them before the morning open. :)
 

Similar threads

Top