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

Repaints
Status
Not open for further replies.

Join useThinkScript to post your question to a community of 21,000+ developers and traders.

Here is a version of the Hull Ma Concavity w Deviation Bands
Code:
#
# Hull Moving Average Concavity and Turning Points
#  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
# Version 2021-01-13 H Kaczmarczyk added Deviation Bands Ver2
# ----------------------


declare upper;

input price = HL2;
input HMA_Length = 21;
input lookback = 2;
Input BandLength = 34;
Input Deviation = 1.3185;

plot HMA = HullMovingAvg(price = price, length = HMA_Length);
plot UZone = SimpleMovingAvg(HMA, BandLength) + (Deviation * StDev(HMA, BandLength));
plot LZone = SimpleMovingAvg(HMA, Bandlength) - (Deviation * StDev(HMA, BandLength));
Plot MidZone = (UZone-LZone)/2 + LZone;
UZone.SetStyle(Curve.Medium_DASH);
UZone.SetDefaultColor(Color.CYAN);
MidZone.SetStyle(Curve.Short_DASH);
MidZone.SetDefaultColor(Color.CYAN);
LZone.SetStyle(Curve.Medium_DASH);
LZone.SetDefaultColor(Color.CYAN);

# 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 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);
MA_Max.SetLineWeight(3);

plot MA_Min = if HMA[-1] > HMA and HMA < HMA[1] then HMA 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 = HMA - 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);
 
Last edited:
@mashume Have there been any thoughts/attempts at projecting either the lower or the upper out into the future, or would that just be a fools-errand? I know the lower is already displaced by -1, but was wondering if either could be pushed out with an extension script.
 
@mashume Have there been any thoughts/attempts at projecting either the lower or the upper out into the future, or would that just be a fools-errand? I know the lower is already displaced by -1, but was wondering if either could be pushed out with an extension script.
@mashume How about calculating the price at which the lower indicator will cross the zero line and have it as a label?
 
@mashume How about calculating the price at which the lower indicator will cross the zero line and have it as a label?
Reversing the calculation of the HMA is not a trivial task. One of these days, I'll post about the calculation of reverse moving averages, but it gets quite messy, quite quickly. I looked into the idea once upon a time and decided to move on. Perhaps it would be possible to have a set of labels that showed what would happen if the price were to move up or down a certain distance or set of distances (+- 5c, +- 10c, etc...) because that would simply be adding a fake (test) value to the array of values used in the calculation and testing to see whether the indicator had moved over or under the desired point. But that's an exercise for a very bored afternoon. Maybe tomorrow. ;-)

-mashume
 

Reverse Calculating Moving Averages -- LONG

There have been several (many) requests from the community for an indicator that would show at what price an indicator (usually the HMA concavity indicator) would flip. I will attempt to show how this is could be done, and why it isnt, and propose a 'close but not really' solution.

Let's begin with a simple moving average. The calculation for a 3 bar simple moving average would be:
Code:
SMA(CLOSE, 3) := (CLOSE + CLOSE[1] + CLOSE[2]) / 3
Adding some values, say (10, 10, 10) we can see that the resultant value is 10
Code:
(10 + 10 + 10) / 3 = 10
Let's complicate the matter a bit and make the values different. (9, 10, 11):
Code:
(9 + 10 + 11) /  3 = 10
So far, so good.
now let's ask what value would make the series (9, 10, x) return a value of 10. We already know from the previous exercise:
Code:
(9 + 10 + x) / 3 = 10
a little bit of algebra gets us to this:
Code:
x = -9 -10 + 30
which gives us a value of 11 for x, as we would expect.
we can gereralize this for a simple moving average of length 5 as such
Code:
x = -CLOSE[1] - CLOSE[2] - CLOSE[3] - CLOSE[4] + 5 * DESIRED_VALUE
and we could keep going. for simple moving averages.

The HMA formula is this:
Code:
HMA= WMA(2*WMA(n/2) − WMA(n)),sqrt(n))
where the formula for the WMA (for 5 periods) is:
Code:
WMA = (P1 * 5) + (P2 * 4) + (P3 * 3) + (P4 * 2) + (P5 * 1) / (5 + 4 + 3 + 2 + 1)

These formulas are taken from https://www.fidelity.com/learning-c...hnical-analysis/technical-indicator-guide/wma and https://www.fidelity.com/learning-c...technical-indicator-guide/hull-moving-average

Combining them then, we have a 4 period HMA as:

Code:
HMA(4) = 2 * ((2*((CLOSE[0] * 2) + (CLOSE[1] * 1) / (2 + 1)) − ((CLOSE[0] * 4) + (CLOSE[1] * 3) + (CLOSE[2] * 2) + (CLOSE[3] * 1) / (4 + 3 + 2 + 1))) * 2) + ((2*((CLOSE[1] * 2) + (CLOSE[2] * 1) / (2 + 1)) − ((CLOSE[1] * 4) + (CLOSE[2] * 3) + (CLOSE[3] * 2) + (CLOSE[4] * 1) / (4 + 3 + 2 + 1))) * 1)

Now, substitute in x and a bunch of letters so I can run it through a symbolic solver:
Code:
CLOSE[0] -> x
CLOSE[1] -> a
CLOSE[2] -> b
CLOSE[3] -> c
CLOSE[4] -> d

HMA = 2 * ((2*((x * 2) + (a * 1) / (2 + 1)) − ((x * 4) + (a * 3) + (b * 2) + (c * 1) / (4 + 3 + 2 + 1))) * 2) + ((2*((a * 2) + (b * 1) / (2 + 1)) − ((a * 4) + (b * 3) + (c * 2) + (d * 1) / (4 + 3 + 2 + 1))) * 1) + y

For those wishing to reproduce this, here is the python code I used to isolate (solve) that mess for x:
Python:
from sympy.solvers import solve
from sympy import Symbol
from sympy import Eq
x = Symbol('x')
y = Symbol('y')
a = Symbol('a')
b = Symbol('b')
c = Symbol('c')
d = Symbol('d')
eq = Eq((2 * ((2*((x * 2) + (a * 1) / (2 + 1)) - ((x * 4) + (a * 3) + (b * 2) + (c * 1) / (4 + 3 + 2 + 1))) * 2) + ((2*((a * 2) + (b * 1) / (2 + 1)) - ((a * 4) + (b * 3) + (c * 2) + (d * 1) / (4 + 3 + 2 + 1))) * 1) - y), x)
solve(eq, x)

It returns the following:
Code:
-28*a/3 - 31*b/3 - 12*c/5 - d/10 - y

which says, substituting back our replacements from above:
Code:
-28 * CLOSE[1] / 3 - 31 * CLOSE[2] / 3 - 12 * CLOSE[3] / 5 - CLOSE[4] / 10 - 10 = x

and we can plug in our values and see what happens.
That's for a 4 period Hull Moving Average. Now, lets see what the mess looks like for the default 55 period HMA used in the concavity study... NOT.

So what can be done?

we could try to create a series where the most recent value was increased or decreased by some amount and the equations solved the right-way-round, at which point we could look and see whether the result is above or below zero (or whatever point we wanted really) so it would look a bit like this:

for a four period simple moving average:
Code:
10 + 10 + 10 + (10 + 0.5) / 4 >= 10
10 + 10 + 10 + (10 + 1.0) / 4 >= 10
10 + 10 + 10 + (10 - 0.5) / 4 >= 10
10 + 10 + 10 + (10 - 1.0) / 4 >= 10

We can eyeball this one and say [true, true, false, false] to the four equations. The labels might show us those four +- values and a color code to tell us they are over or under 10 (in this case, or 0, or whatever target value).

So perhaps, though I haven't tested it, we can create a series where the last value only is changed:

Code:
def hl_v = if isNan(HL2[-1]) then HL2 - 10 else HL2;

The +- values could be set:
Code:
input over_under = 0.5;
def hl_over_a = if isNan(HL2[-1]) then HL2 + over_under else HL2;
def hl_over_b = if isNan(HL2[-1]) then HL2 + over_under * 2 else HL2;
def hl_under_a = if isNan(HL2[-1]) then HL2 - over_under else HL2;
def hl_under_b = if isNan(HL2[-1]) then HL2 - over_under  * 2 else HL2;

These arrays then get used in the calculation of the HMA (or whatever function you wish -- z scores etc...) and compared, as above to a value.

Conclusions

I don't really have any today. This is kind of preliminary, and sketchy, and hackey... I would welcome thoughts from others on the usefulness of this, the processor cycles required vs the benefit gained, and such ruminations as you all care to share.

Happy Trading,
Mashume
 
EDIT WARNING Watching this on /ES this afternoon, it is not behaving as I would have expected it to.

@sunstar
Something like this:
Code:
declare upper;

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

input stddev_len = 21;
input zlength = 13;
input threshold = 1.5;

input over_under = 0.5;
def a_price = if isNan(HL2[-1]) then (LOW + (HIGH + over_under) / 2) else HL2;
def b_price = if isNan(HL2[-1]) then (LOW + (HIGH + (over_under * 2 )) / 2) else HL2;
def c_price = if isNan(HL2[-1]) then (HIGH - (LOW - over_under) / 2) else HL2;
def d_price = if isNan(HL2[-1]) then (HIGH - (LOW - (over_under * 2 )) / 2) else HL2;

# A
def a_HMA             = HullMovingAvg(price = a_price, length = HMA_Length);
def a_delta         = a_HMA[1] - a_HMA[lookback + 1];
def a_delta_per_bar = a_delta / lookback;
def a_next_bar         = a_HMA[1] + a_delta_per_bar;
def a_divergence     = (a_HMA - a_next_bar) * 5000;
def a_Zscore         = ( (a_divergence - Average(a_divergence, zlength)) / StDev(a_divergence, zlength));

# B
def b_HMA             = HullMovingAvg(price = b_price, length = HMA_Length);
def b_delta         = b_HMA[1] - b_HMA[lookback + 1];
def b_delta_per_bar = b_delta / lookback;
def b_next_bar         = b_HMA[1] + b_delta_per_bar;
def b_divergence     = (b_HMA - b_next_bar) * 5000;
def b_Zscore         = ( (b_divergence - Average(b_divergence, zlength)) / StDev(b_divergence, zlength));

# C
def c_HMA             = HullMovingAvg(price = c_price, length = HMA_Length);
def c_delta         = c_HMA[1] - c_HMA[lookback + 1];
def c_delta_per_bar = c_delta / lookback;
def c_next_bar         = c_HMA[1] + c_delta_per_bar;
def c_divergence     = (c_HMA - c_next_bar) * 5000;
def c_Zscore         = ( (c_divergence - Average(c_divergence, zlength)) / StDev(c_divergence, zlength));

# D
def d_HMA             = HullMovingAvg(price = d_price, length = HMA_Length);
def d_delta         = d_HMA[1] - d_HMA[lookback + 1];
def d_delta_per_bar = d_delta / lookback;
def d_next_bar         = d_HMA[1] + d_delta_per_bar;
def d_divergence     = (d_HMA - d_next_bar) * 5000;
def d_Zscore         = ( (d_divergence - Average(d_divergence, zlength)) / StDev(d_divergence, zlength));


AddLabel(yes, if a_Zscore > 0 then "IF High > " + (HIGH + over_under) + " Z > 0" else "High + " + (High + over_under) + " NOT < 0", if a_Zscore > 0 then color.dark_green else color.gray);

AddLabel(yes, if b_Zscore > 0 then "IF High > " + (HIGH +  (2 * over_under)) + " Z > 0" else "High + " + (High + (2 * over_under)) + " NOT < 0", if a_Zscore > 0 then color.dark_green else color.gray);

AddLabel(yes, if c_Zscore > 0 then "IF Low < " + (LOW - over_under) + " Z < 0" else "Low - " + (LOW + over_under) + " NOT < 0", if a_Zscore > 0 then color.dark_red else color.gray);

AddLabel(yes, if d_Zscore > 0 then "IF Low < " + (LOW - (2 * over_under)) + " Z < 0" else "Low - " + (LOW + (2 * over_under)) + " NOT < 0", if a_Zscore > 0 then color.dark_red else color.gray);

-mashume

P.S. I have over_under set to 0.5 -- you may need to adjust it for the instrument you're trading. Or set it to ATR or some fraction thereof.
 
Last edited:
so my question after reading all 26 pages is the code on the 1st page updated to reflect all the changes and additions made to the orignal code and what are the best settings for a 5 minute chart.
 
As far as I know, the version shown at the BOTTOM of the first post (V4) is the latest. It's what I am running at any rate.

As for ideal timeframes, it depends on the instrument you're trading and your tolerance for noise vs early entries.

Happy Trading,
-mashume
 
Having traded this manually for a number of days on various (faster) timeframes, I'm liking it more and more. There haven't been a lot of posts to this thread in the last few months, so I hope that interest in it hasn't waned.

@tradegeek , quite some time ago you and @Aeteux had discussed porting this to Ninja. As I recall, you went ahead and did the lower with the help of a developer and @Aeteux was interested but not financially able at the time to invest in having the upper created for Ninja. @diazlaz and @kelso , I believe you also expressed some interest.

I don't know if any of you still trade this indicator. Possibly you've moved on to other things as I've seen many posts from you on many other topics over the last 6 months.

A trading partner and I are interested in creating an automated trading (scalping) tool for Ninja and are prepared to invest to get it done. I say scalping because our trading styles lend to being in and out of the market quickly to limit risk. I'm FULLY aware of all of the limitations that ToS strategies have with regards to PLs (disappearing entries, entering at unrealistic spots, no accounting for commissions, fees & slippage, etc.) So we've concluded that if this code performs only a fraction as well as what is displayed in the image below, then pursuing this will hopefully be worthwhile.

It is also currently being looked at in TradeStation, but I believe there is an issue with the referencing of a future candle.

I don't know if any of you would like to join in to defray the costs.

@tradegeek , if you ARE still using this indicator and still love it, I'd like to get your opinion on some of the limitations you've experienced using it on Ninja. Also, I'd be interested in any additional thoughts that you may have regarding hiring someone to get this done. On the other hand, if you have concluded that this type of thing would be a futile endeavour, I'd like to hear that too.

Please get in touch by messaging me directly either on the usethinkscript discord server ( invite: https://discord.gg/HQVZUxB ) where this nonVIP member I think can still message, or on the B4 Trading Community discord server ( invite: https://discord.gg/kD3pKE2CQd )

Best to you....

 
Last edited:
I'm no javascript expert (most of my professional dev work is python). Can you post whatever js code you do have though and I'll be happy to try to figure it out.

-Mashume

Thanks, but we don't actually have anything set up yet. We started out with a Hull crossover indicator, with one of the lines being displaced by 2 but havent proceeded any further because we couldn't figure out how to do the prediction. However, I do have the code for Tradovate's Hull Moving Average script. That is first below. Second and third below are the scripts for the constants noted in the parent script.

Thanks much Seth! It's very considerate of you.

HULL MOVING AVERAGE (JAVASCRIPT)
Code:
const predef = require("./tools/predef");
const WMA = require("./tools/WMA");

class hullMovingAverage {
    init() {
        const period = this.props.period;
        this.wmaLong = WMA(period);
        this.wmaShort = WMA(period / 2);
        this.wmaSqrt = WMA(Math.sqrt(period));
    }

    map(d) {
        const value = d.value();
        const wmaLong = this.wmaLong(value);
        const wmaShort = this.wmaShort(value) * 2;
        const wmaDiff = wmaShort - wmaLong;
        return this.wmaSqrt(wmaDiff);
    }
}

module.exports = {
    name: "hma",
    description: "Hull Moving Average",
    calculator: hullMovingAverage,
    params: {
        period: predef.paramSpecs.period(14)
    },
    tags: [predef.tags.MovingAverage],
    schemeStyles: predef.styles.solidLine("#8cecff")
};

CONSTANT "predef"
Code:
const lodash = require("lodash");

const { ParamType } = require("./meta");


function mkStyle(style) {

    if (typeof style === "string") {

        style = { color: style };

    }

    return {

        color: style.color || "gray",

        lineWidth: style.lineWidth || 1,

        opacity: style.opacity || 100,

        lineStyle: style.lineStyle || 1

    };

}


module.exports = {

    plotters: {

        line: { type: "line" },

        multiline(fields) {

            return {

                type: "multiline",

                fields

            };

        },

        singleline(field) {

            return {

                type: "multiline",

                fields: [field]

            };

        },

        pivotpoints(fields) {

            return {

                type: "pivotpoints",

                fields

            };

        },

        histogram: { type: "histogram" },

        cumulative: { type: "cumulative" },

        zigzag(fields) {

            return {

                type: "zigzag",

                fields

            };

        },

        macd: { type: "macd" },

        scatter: { type: "scatter" },

        dots(field) {

            return {

                type: "dots",

                field: field || "_"

            };

        },

        columns(field) {

            return {

                type: "columns",

                field

            };

        },

        custom(func) {

            return {

                type: "custom",

                function: func

            };

        },

        range(fieldFrom, fieldTo) {

            return {

                type: "range",

                fields: [fieldFrom, fieldTo]

            };

        }

    },

    scalers: {

        singlePath: { type: "singlePath" },

        multiPath(fields) {

            return {

                type: "multiPath",

                fields

            };

        }

    },

    paramSpecs: {

        period(defValue) {

            return {

                type: ParamType.NUMBER,

                def: defValue,

                restrictions: {

                    step: 1,

                    min: 1

                },

                validate(value) {

                    if (value < 1) {

                        return "Period should be a positive number";

                    }

                    return undefined;

                }

            };

        },

        number(defValue, step, min) {

            return {

                type: ParamType.NUMBER,

                def: defValue,

                restrictions: {

                    step: step || 1,

                    min: min || 0

                }

            };

        },

        percent(defValue, step, min, max) {

            return {

                type: ParamType.NUMBER,

                def: defValue,

                restrictions: {

                    step: step || 1,

                    min,

                    max

                }

            };

        },

        bool(defValue) {

            return {

                type: ParamType.BOOLEAN,

                def: defValue

            };

        },

        text(defValue) {

            return {

                type: ParamType.TEXT,

                def: defValue

            };

        },

        enum(enumSet, defValue) {

            return {

                type: ParamType.ENUM,

                enumSet,

                def: defValue,

                toSelectOptions() {

                    return lodash.toPairs(enumSet)

                        .map(p => ({

                            label: p[1],

                            value: p[0]

                        }));

                }

            };

        },

        color(defValue) {

            return {

                type: ParamType.COLOR,

                def: defValue

            };

        }

    },

    filters: {

        onlyNumberValue(d) {

            return d && (typeof d.value === "number" && !isNaN(d.value) || typeof d === "object");

        },

        isNumber(d) {

            return typeof d === "number" && !isNaN(d);

        }

    },

    tags: {

        MovingAverage: "Moving Averages",

        Channels: "Channels",

        Volatility: "Volatility",

        Oscillators: "Oscillators",

        Volumes: "Volume-based"

    },

    styles: {

        solidLine(plotName, dark, light) {

            if (arguments.length === 1) {

                dark = plotName;

                plotName = "_";

            }

            if (!light) {

                light = dark;

            }

            const result = {

                dark: {},

                light: {}

            };

            result.dark[plotName] = mkStyle(dark);

            result.light[plotName] = mkStyle(light);

            return result;

        },

        plot(partialStyle) {

            return mkStyle(partialStyle);

        },

        dashLine(plotName, dark, light) {

            if (arguments.length === 1) {

                dark = plotName;

                plotName = "_";

            }

            if (!light) {

                light = dark;

            }

            const result = {

                dark: {},

                light: {}

            };

            result.dark[plotName] = mkStyle(dark);

            result.dark[plotName].lineStyle = 3;

            result.light[plotName] = mkStyle(light);

            result.light[plotName].lineStyle = 3;

            return result;

        },

    }

};

CONSTANT "WMA"
Code:
function triangular(value) {
    return (value / 2) * (value + 1);
}

function WeightedMovingAverage(period) {
    function wma(value) {
        return wma.push(value);
    }

    wma.reset = () => {
        wma.state = {
            items: []
        };
    };

    wma.push = (value) => {
        wma.state.items.push(value);
        if (wma.state.items.length > period) {
            wma.state.items.shift();
        }
        return wma.avg();
    };

    wma.avg = () => {
        const items = wma.state.items;
        const denominator = triangular(items.length);

        function wmaAccumulator(sum, value, index) {
            return sum + (value * (index + 1) / denominator);
        }

        return items.reduce(wmaAccumulator, 0);
    };

    wma.reset();

    return wma;
}

module.exports = WeightedMovingAverage;
 
@mashume thank you for creating this indicator i am using v4 with 21 feel its faster then the original since i am a scalper

i know i have alot on my screens but basically followed thru when it turned bright green on 1 minute and became ready to sell as soon as it turned orange and with balance bb i found on this forum as soon as histogram volume became lower i got out perfectly

curious what others have combined your indicator with here


 
Last edited:
Ok here's a thought. Labels to show the polarity of this indicator from a higher timeframe (e.g. 2m position on a 1m chart). I use this for confirmation when entering i.e. if the positive turning point (Dark Green line) has fired on the higher timeframe i can enter with higher risk, if the Buy signal (Green line) has fired i can enter with low risk.

I'm going to attempt to code this now but hoping someone who actually knows what they are doing can help while i bumble about in the meantime ;-)
 
Ok here's a thought. Labels to show the polarity of this indicator from a higher timeframe (e.g. 2m position on a 1m chart). I use this for confirmation when entering i.e. if the positive turning point (Dark Green line) has fired on the higher timeframe i can enter with higher risk, if the Buy signal (Green line) has fired i can enter with low risk.

I'm going to attempt to code this now but hoping someone who actually knows what they are doing can help while i bumble about in the meantime ;-)
replying to my own question/request like the sad puppy i am. I think this covers it bu would be grateful if someone could check my code please.

https://tos.mx/m4Sv5GX
 
@BBDPDC Can you post the code, using the </> icon, for review without the need to import into TOS please... Please make it a habit to always post the code, not just a shared link...
@rad14733 apologies, I hadn't considered that folks might not want to import my potentially p!ss poor code into there TOS! Here's the code:

Code:
#Hull HigherAgg Label

declare upper;

input price = HL2;
input HMA_Length = 18;
input lookback = 1;
input agg =  { "1 min", default "2 min", "5 min", "10 min", "15 min", "30 min", "60 min", "4 hours"};

def nan = double.nan;

def HMA = HullMovingAvg(HL2(period=agg), 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 nan;

AddLabel(yes, "HULL",  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);
 
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
qdaQ0uz.png



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


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


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.
This indicator is awesome! Is there a way to scan for MA_Min alert and MA_Max alert? I changed it to VWAP 30, 2 and love it on the 15mn and the 30mn. Buy on the Min and sell on the Max. Also how could I make this a buy script? All based on the upper script I have a 6yo in online Kindergarten so it's hard to watch charts now.
 
Last edited:
@mashume Thank you for creating this amazing indicator! I have been using this indicator on ThinkorSwim along with the QQE indicator for the last year and have found some success with my trading. Recently I have been using Tradingview to chart crypto, but the implementations of Hull on Tradingview felt somewhat lacking. I missed having this Hull indicator I loved so much, so I rewrote it for Tradingview one weekend haha. I have credited you in the source as the author of the original code along with some edits made by myself and a mentor.
Here is the script converted for Tradingview:
https://www.tradingview.com/script/6Fpb0VvK-MashumeHullTV/

Cheers, and thanks again!
-machdragon
 
Last edited:
Last edited:
Status
Not open for further replies.

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
560 Online
Create Post

Similar threads

Similar threads

The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
  • Exclusive indicators
  • Proven strategies & setups
  • Private Discord community
  • ‘Buy The Dip’ signal alerts
  • Exclusive members-only content
  • Add-ons and resources
  • 1 full year of unlimited support

Frequently Asked Questions

What is useThinkScript?

useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.

How do I get started?

We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.

If you are new, or just looking for guidance, here are some helpful links to get you started.

What are the benefits of VIP Membership?
VIP members get exclusive access to these proven and tested premium indicators: Buy the Dip, Advanced Market Moves 2.0, Take Profit, and Volatility Trading Range. In addition, VIP members get access to over 50 VIP-only custom indicators, add-ons, and strategies, private VIP-only forums, private Discord channel to discuss trades and strategies in real-time, customer support, trade alerts, and much more. Learn all about VIP membership here.
How can I access the premium indicators?
To access the premium indicators, which are plug and play ready, sign up for VIP membership here.
Back
Top