Need help tuning minimum/maximum detection for custom Hull moving average indicator

 

Hey all,

I'm currently working on a custom Hull moving average (HMA) indicator that detects and labels local minimum and maximum points of the Hull curve. At first glance, this modified indicator seems to work well (see images below), tagging HMA minimum points with a green buy signal arrow and HMA maximum points with a red sell signal arrow.It applies the signal arrows "across the board" onto the entire chart timeline which allowed me to verify that I got the slope and zero-cross logic correct.

However, a snag comes when the chart is live and new data is coming in - the HMA indicator no longer tags points correctly, labeling buy and sell signals when the Hull moving average has not leveled off. Here is a snippet of the EUR/USD daily chart, right in the center core of the chart frame. As you can see, the custom HMA indicator labels these points very well, with almost no false signals. There is a slight offset, but nothing egregious or overly concerning.

EDIT: Just realized that I'm unable to post photos. Below is the link to the image in question.

https://www.mql5.com/en/charts/11830487/eurusd-fx-d1-ig-group-limited

However, you can see in the past couple days where it began to give false signals - signaling a sell twice in a row, with the first signal coming well before the slope had actually leveled off. Then, the next sell signal had a conflicting buy signal occurring within the same bar. My line of thinking, although I have done no inspection yet, is that the current bar or candle isn't "cemented" or "closed", so variations in price get calculated into a HMA value that is constantly in flux. The behavior I'm intending for is for the period to close and then the indicator make a labeling determination. Is there a way of making the discrimination (a closed period as opposed to a current one) and then adjusting the code to only factor in closed periods for the labeling?

Below is the complete code for the indicator - I've tried to leave in as many comments as I can in order to give a better idea as to my thought process during its construction.Thank you for any help any of you can provide.

#include <MovingAverages.mqh>

#define   ZCD_NONE  0
#define   ZCD_BUY   1
#define   ZCD_SELL -1

#property indicator_chart_window
#property indicator_buffers         3
#property indicator_color1          clrBlack
#property indicator_color2          clrSpringGreen
#property indicator_color3          clrSpringGreen
#property indicator_style1          0
#property indicator_style2          0
#property indicator_style3          0
#property indicator_width1          1
#property indicator_width2          2
#property indicator_width3          2

//+---------------------------------------------------------------------------+ 
//| Inputs and configurable parameters for the HMA indicator.                 |
//+---------------------------------------------------------------------------+
input int                Period      = 12;
input int                Shift       = 0;
input ENUM_APPLIED_PRICE Price_Mode  = PRICE_TYPICAL;
input long               Arrow_Size  = 5;

//+---------------------------------------------------------------------------+
//| Indicator buffers for exporting of calculated data.                       |
//+---------------------------------------------------------------------------+
double  HMA_Pre_Buffer[];
double  HMA_Slope_Buffer[];
double  Ext_Signal_Buffer[];

//+---------------------------------------------------------------------------+
//| OnInit()                                                                  |
//| This function is responsible for initialization of the indicator. It sets |
//| the indicator's displayed precision, attaches buffers and performs other  |
//| static setup tasks.                                                       |
//+---------------------------------------------------------------------------+
int OnInit(void)
{
    // Indicator's displayed precision.
    IndicatorDigits(Digits + 1);

    // Drawing modes and settings.
    SetIndexShift(0, Shift);
    SetIndexShift(1, Shift);
    SetIndexStyle(1, DRAW_LINE);
    SetIndexDrawBegin(1, Period);

    // Map out the HMA buffers.
    SetIndexBuffer(0, HMA_Pre_Buffer);
    SetIndexBuffer(1, HMA_Slope_Buffer);
    SetIndexBuffer(2, Ext_Signal_Buffer);

    // Configure display strings.
    string HMA_Period_String = "(" + IntegerToString(Period) + ")";
    string HMA_Value_Display_String = "HMA" + HMA_Period_String;
    string HMA_Slope_Display_String = "d-HMA" + HMA_Period_String;
    IndicatorShortName(HMA_Value_Display_String);
    SetIndexLabel(0, HMA_Value_Display_String);
    SetIndexLabel(1, HMA_Slope_Display_String);
    SetIndexLabel(2, HMA_Value_Display_String);

    if (Period <= 1) {
        string PerStr = IntegerToString(Period);
        Print("Period of '" + PerStr + "' is invalid! Please try again.");
        return INIT_FAILED;
    }
    else 
        return INIT_SUCCEEDED;
}

//+---------------------------------------------------------------------------+
//| ZeroCrossDet()                                                            |
//| This function determines if a zero-crossing event has occurred. It        |
//| returns one of three possible values:                                     |
//|      1. ZCD_NONE : No zero-crossing event occurred. Do nothing.           |
//|      2. ZCD_BUY  : The d-HMA crossed from negative (or zero) to positive. |
//|                    This represents a buy signal.                          |
//|      3. ZCD_SELL : The d-HMA crossed from positive (or zero) to negative. |
//|                    This represents a sell signal.                         |
//+---------------------------------------------------------------------------+
int ZeroCrossDet(double dHMAp, double dHMAc)
{
    if ((dHMAp < 0 || dHMAp == 0) && dHMAc > 0)
        return ZCD_BUY;
    if ((dHMAp > 0 || dHMAp == 0) && dHMAc < 0)
        return ZCD_SELL;
    else
        return ZCD_NONE;
}

//+---------------------------------------------------------------------------+
//| OnCalculate()                                                             |
//| This function performs the bulk of the calculations required for this     |
//| indicator. In particular, it performs three tasks:                        |
//|      1. Calculates the Hull moving average (HMA) for the given period.    |
//|         The HMA is computed as WMA(2WMA(n/2) - WMA(n), sqrt(N)). The HMA, |
//|         developed by Alan Hull, is an indicator designed to lag less than |
//|         SMA-type moving averages while retaining the smoothness of SMA    |
//|         indications.                                                      |
//|      2. Calculates the slope of the HMA. This is represented as d-HMA or  |
//|         HMA'. This is essentially a second derivative of price, and used  |
//|         in the determination (3) of a zero-crossing event, where the      |
//|         HMA(n) at bar T represents a local maximum or minimum.            |
//|      3. Performs zero-crossing detection using HMA'.                      |
//+---------------------------------------------------------------------------+
int OnCalculate(const int       rates_total,
                const int       prev_calculated,
                const datetime& time[],
                const double&   open[],
                const double&   high[],
                const double&   low[],
                const double&   close[],
                const long&     tick_volume[],
                const long&     volume[],
                const int&      spread[])
{
    // Set up initial iterators and compute the integer WMA periods for HMA
    // calculation.
    int i = 0, limit = rates_total - prev_calculated;
    int m = (int) floor(Period / 2);
    int n = (int) floor(MathSqrt(Period));

    // If we have fewer bars than the HMA period requires, return 0 and exit.
    if (rates_total <= Period)
        return 0;
    
    // If we've previously calculated HMA values for existing bars, update the 
    // limit accordingly.
    if (prev_calculated > 0)
        limit++;

    // Calculate the WMA delta buffer. This is the first step in calculating 
    // the Hull moving average.
    for (i = 0; i < limit; i++) {
        double wma2 = 2 * iMA(NULL, 0, m, 0, MODE_LWMA, Price_Mode, i);
        double wma1 = iMA(NULL, 0, Period, 0, MODE_LWMA, Price_Mode, i);
        HMA_Pre_Buffer[i] = wma2 - wma1;
    }

    // Calculate the Hull moving average from the prebuffer.
    int w = 0;
    for (i = n; i > 0; i--) {
        w += i;
        LinearWeightedMAOnBuffer(
            rates_total, 
            prev_calculated, 
            0, 
            n, 
            HMA_Pre_Buffer, 
            Ext_Signal_Buffer, 
            w
        );
    }

    // Calculate the HMA slope/derivative. Because we can safely assume that
    // a bar or timeframe period is itself a unit of time, the derivative
    // calculation reduces to current - previous.
    //
    // Also, we perform crossover detection here. See ZeroCrossDet() for 
    // more information.
    HMA_Slope_Buffer[0] = 0;    // no -1 index, kludge this
    for (i = 1; i < rates_total; i++) {
        // Compute the current slope.
        HMA_Slope_Buffer[i] = Ext_Signal_Buffer[i] - Ext_Signal_Buffer[i - 1];

        // Store the current and previous slope to variables.
        double dHMAp = HMA_Slope_Buffer[i - 1];
        double dHMAc = HMA_Slope_Buffer[i];

        // Perform zero-crossing determination.
        int zcd = ZeroCrossDet(dHMAp, dHMAc);
        if (zcd != ZCD_NONE) {
            if (zcd == ZCD_BUY) {
                // Construct the graphical indicator.
                string ObjID = "BUY(" + Symbol() + "):" + IntegerToString(i);
                double ObjPos = close[i] * 0.99;
                long chart = ChartID();
                ObjectCreate(chart, ObjID, OBJ_ARROW_UP, 0, time[i], ObjPos);
                  
                // Color it accordingly.
                ObjectSetInteger(chart, ObjID, OBJPROP_COLOR, clrLimeGreen);
                ObjectSetInteger(chart, ObjID, OBJPROP_WIDTH, Arrow_Size);
            }
            else {
                // Construct the graphical indicator.
                string ObjID = "SELL(" + Symbol() + "):" + IntegerToString(i);
                double ObjPos = close[i] * 1.01;
                long chart = ChartID();
                ObjectCreate(chart, ObjID, OBJ_ARROW_DOWN, 0, time[i], ObjPos);
                
                // Color and size it accordingly.
                ObjectSetInteger(chart, ObjID, OBJPROP_COLOR, clrOrangeRed);
                ObjectSetInteger(chart, ObjID, OBJPROP_WIDTH, Arrow_Size);
            }
        }
    }
        
    return rates_total;
}
Documentation on MQL5: Constants, Enumerations and Structures / Objects Constants / Methods of Object Binding
Documentation on MQL5: Constants, Enumerations and Structures / Objects Constants / Methods of Object Binding
  • www.mql5.com
Graphical objects Text, Label, Bitmap and Bitmap Label (OBJ_TEXT, OBJ_LABEL, OBJ_BITMAP and OBJ_BITMAP_LABEL) can have one of the 9 different ways of coordinate binding defined by the OBJPROP_ANCHOR property. – defines the chart corner relative to which the anchor point coordinates are specified. Can be one of the...
 

 Is there a way of making the discrimination (a closed period as opposed to a current one) and then adjusting the code to only factor in closed periods for the labeling?


Just don't process the current bar,  reduce the value of limit by 1 

 
Paul Anscombe:

Just don't process the current bar,  reduce the value of limit by 1 

Okay.

While we're at it, I was worried that I was getting confused on the orientation of the D/O/H/L/C/V arrays that are parameters to OnCalculate(). Are they oriented "most recent first", that is, 0 to (limit - 1) with the (limit - 1) index corresponding to the current bar?

Would I simply make the adjustment into the for loop of the calculations? - i.e.:

for (i = 0; i < (limit - 1); i++) {
    // HMA pre-buffer calculations ...
}

Also, to root out any potential future errors, was it correct to use rates_total as the increment maximum in the second for loop, or should I have also used (limit - 1) here?

// Calculate the HMA slope/derivative. Because we can safely assume that
// a bar or timeframe period is itself a unit of time, the derivative
// calculation reduces to current - previous.
//
// Also, we perform crossover detection here. See ZeroCrossDet() for 
// more information.
HMA_Slope_Buffer[0] = 0;
for (i = 1; i < rates_total; i++) { // <- should this be changed to "i = 1; i < (limit - 1); i++"?
    // Zero-crossing detection
    // Drawing of graphical objects
}

Thank you for your help - it is much appreciated.

 
physecfed:

Hey all,

I'm currently working on a custom Hull moving average (HMA) indicator that detects and labels local minimum and maximum points of the Hull curve. At first glance, this modified indicator seems to work well (see images below), tagging HMA minimum points with a green buy signal arrow and HMA maximum points with a red sell signal arrow.It applies the signal arrows "across the board" onto the entire chart timeline which allowed me to verify that I got the slope and zero-cross logic correct.

However, a snag comes when the chart is live and new data is coming in - the HMA indicator no longer tags points correctly, labeling buy and sell signals when the Hull moving average has not leveled off. Here is a snippet of the EUR/USD daily chart, right in the center core of the chart frame. As you can see, the custom HMA indicator labels these points very well, with almost no false signals. There is a slight offset, but nothing egregious or overly concerning.

EDIT: Just realized that I'm unable to post photos. Below is the link to the image in question.

https://www.mql5.com/en/charts/11830487/eurusd-fx-d1-ig-group-limited

However, you can see in the past couple days where it began to give false signals - signaling a sell twice in a row, with the first signal coming well before the slope had actually leveled off. Then, the next sell signal had a conflicting buy signal occurring within the same bar. My line of thinking, although I have done no inspection yet, is that the current bar or candle isn't "cemented" or "closed", so variations in price get calculated into a HMA value that is constantly in flux. The behavior I'm intending for is for the period to close and then the indicator make a labeling determination. Is there a way of making the discrimination (a closed period as opposed to a current one) and then adjusting the code to only factor in closed periods for the labeling?

Below is the complete code for the indicator - I've tried to leave in as many comments as I can in order to give a better idea as to my thought process during its construction.Thank you for any help any of you can provide.

No problem to post a screenshot. Use the pocket tool.

Reason: