Step Three: Developing the Hull Moving Average Indicator

In this series on creating an Expert Advisor for MetaTrader, I will guide you through the process of building my trading strategy. First, I developed the strategy in TradingView to visually inspect and test it. While this method helped me understand the strategy, converting it from TradingView’s Pine Script to MetaTrader 5’s MQL code proved challenging.

One major issue was achieving an effective conversion from Pine Script to MQL5. I first tried using AI tools, including Copilot, for automatic conversion, but it didn't go as smoothly as expected. The generated MQL5 code needed many revisions and extensive debugging before it worked properly. Small errors in coding for algorithmic trading can lead to big problems later.

Another challenge was that MetaTrader lacks some indicators available in TradingView. I specifically needed two custom indicators: the Hull Moving Average and the Donchian Channel. This post focuses on the Hull Moving Average. I will share my process of coding this indicator in MQL5, the bugs I faced, and how I created a reliable implementation.

In hindsight, it would have been more advantageous to first identify the key indicators and develop them independently before constructing the main script of the expert advisor. This foundational approach allows for a more nuanced understanding of how each indicator operates, facilitating a clearer insight into their interactions within the overall strategy. By building the expert advisor from the ground up based on well-established indicators, developers gain a more comprehensive grasp of the code's functionality, which ultimately simplifies the problem-solving process. Such a method encourages a deeper engagement with the logic behind the system, reducing the complexity that can arise when indicators are integrated too late in the development cycle.

The Hull Moving Average

The Hull Moving Average (HMA) is calculated using the weighted moving average (WMA) of prices over a specified period, followed by applying a square root function to eliminate lag. Mathematically, HMA is computed as follows:

1) Calculate WMA for a shorter period (e.g., WMA(0.5 * period)) and a longer period (e.g., WMA(period)).

2) Combine these results to achieve the Hull Moving Average: HMA = WMA(2 * WMA(0.5 * period) - WMA(period)).

Metatrader 5 HMA indicator

The outcome of the Hull Moving Average (HMA) indicator is reflected in Figure 1. In the current implementation, the script plots the HMA on the chart as a red line. After initially plotting this and testing the code, it was observed that the first HMA value corresponds to the first bar, in contrast to Python's approach where it typically aligns with the last bar. This discrepancy led to repainting issues in the results. This revised code correctly aligns the HMA values with the intended bar, eliminating the repainting issue and providing reliable results.

Hull Moving Average MetaTrader 5

Figure 1. Hull Moving Average (red line) and the HMA value in the legend on the right.

Next Steps

In the upcoming blog post, we will delve into the Donchian Channels indicator which will be used as an exit strategy our algorithm.

The HMA Script

//+------------------------------------------------------------------+
//|                                                   HullMA.mq5     |
//|    Custom Indicator to Display the Hull Moving Average           |
//+------------------------------------------------------------------+
#property copyright "Your Copyright"
#property link      "Your Link"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_color1 Red
#property indicator_width1 3
#property indicator_style1 STYLE_SOLID
#property indicator_applied_price PRICE_CLOSE

// Input parameter: Hull MA period
input int InpHmaLength = 20;

// Indicator buffer for the Hull MA values
double HmaBuffer[];

//-------------------------------------------------------------------+
//| Weighted Moving Average calculation in series mode               |
//-------------------------------------------------------------------+
double WMA_series(const double &arr[], int period, int idx, int total)
{
   if(idx - period + 1 < 0)
      return EMPTY_VALUE;

   double sumWeighted = 0.0;
   double sumWeights  = 0.0;
   for(int j = 0; j < period; j++)
   {
      int weight = period - j;
      sumWeighted += arr[idx - j] * weight;
      sumWeights  += weight;
   }

   if(sumWeights == 0)
      return EMPTY_VALUE;
   return sumWeighted / sumWeights;
}

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, HmaBuffer, INDICATOR_DATA);
   ArraySetAsSeries(HmaBuffer, true);

   int sqrtLen = (int)MathSqrt(InpHmaLength);
   int draw_begin = InpHmaLength + sqrtLen - 2;
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, draw_begin);
   PlotIndexSetInteger(0, PLOT_SHIFT, 0);

   string short_name = StringFormat("HMA(%d)", InpHmaLength);
   PlotIndexSetString(0, PLOT_LABEL, short_name);
   IndicatorSetString(INDICATOR_SHORTNAME, short_name);

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Main calculation                                                 |
//+------------------------------------------------------------------+
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[])
{
   if(rates_total < InpHmaLength)
      return(0);

   double price[];
   ArrayResize(price, rates_total);
   ArrayCopy(price, close);
   ArraySetAsSeries(price, true);

   int halfLen = InpHmaLength / 2;
   int sqrtLen = (int)MathSqrt(InpHmaLength);

   double diffArray[];
   ArrayResize(diffArray, rates_total);
   ArrayInitialize(diffArray, EMPTY_VALUE);
   ArraySetAsSeries(diffArray, true);

   // Calculate intermediate difference WMA values
   for (int i = rates_total - 1; i >= 0; i--)
   {
      if(i - InpHmaLength + 1 < 0)
         continue;

      double wma_half = WMA_series(price, halfLen, i, rates_total);
      double wma_full = WMA_series(price, InpHmaLength, i, rates_total);
      if(wma_half == EMPTY_VALUE || wma_full == EMPTY_VALUE)
         continue;

      diffArray[i] = 2.0 * wma_half - wma_full;
   }

   // Final Hull MA using WMA on the diff array
   for (int i = rates_total - 1; i >= 0; i--)
   {
      if(i - sqrtLen + 1 < 0 || diffArray[i] == EMPTY_VALUE)
      {
         HmaBuffer[i] = EMPTY_VALUE;
         continue;
      }

      HmaBuffer[i] = WMA_series(diffArray, sqrtLen, i, rates_total);
   }

   return(rates_total);
}
Previous
Previous

Step Two: Coding in TradingView