| //+------------------------------------------------------------------+
|
| //| UnifiedHullMA.mq5 |
|
| //| |
|
| //| Hull Moving Average calculation built around the work of |
|
| //| Mladen Rakic. |
|
| //| |
|
| //| This implementation extends that foundation with multiple Hull |
|
| //| periods, directional colouring, configurable Hull lines, stable |
|
| //| buffer layout, and ring-buffer based memory management. |
|
| //| |
|
| //| Copyright © 2026 Alfonso Golden Trader |
|
| //+------------------------------------------------------------------+
|
| #property copyright "Copyright 2026, C.J. Weekes"
|
| #property copyright "Copyright 2026, Alfonso Golden Trader"
|
| #property version "1.00"
|
| #property description "Hull Moving Average indicator with four periods on a single chart."
|
| #property description "Colour-coded by direction. Ring-buffer memory management."
|
| #property description "Buffer layout: 0/1=Hull1, 2/3=Hull2, 4/5=Hull3, 6/7=Hull4."
|
|
|
| #property indicator_chart_window
|
| #property indicator_buffers 8
|
| #property indicator_plots 4
|
|
|
| //--- Hull Line 1 (Buffer 0/1)
|
| #property indicator_type1 DRAW_COLOR_LINE
|
| #property indicator_color1 clrGray, clrLime, clrRed
|
| #property indicator_width1 2
|
| #property indicator_label1 "Hull1"
|
|
|
| //--- Hull Line 2 (Buffer 2/3)
|
| #property indicator_type2 DRAW_COLOR_LINE
|
| #property indicator_color2 clrGray, clrDodgerBlue, clrOrangeRed
|
| #property indicator_width2 2
|
| #property indicator_label2 "Hull2"
|
|
|
| //--- Hull Line 3 (Buffer 4/5)
|
| #property indicator_type3 DRAW_COLOR_LINE
|
| #property indicator_color3 clrGray, clrAqua, clrMagenta
|
| #property indicator_width3 2
|
| #property indicator_label3 "Hull3"
|
|
|
| //--- Hull Line 4 (Buffer 6/7)
|
| #property indicator_type4 DRAW_COLOR_LINE
|
| #property indicator_color4 clrGray, clrGold, clrDarkOrchid
|
| #property indicator_width4 2
|
| #property indicator_label4 "Hull4"
|
|
|
| //--- Input parameters
|
| input int InpPeriod1 = 8; // Hull 1 Period
|
| input int InpPeriod2 = 20; // Hull 2 Period
|
| input int InpPeriod3 = 50; // Hull 3 Period
|
| input int InpPeriod4 = 200; // Hull 4 Period
|
| input int InpDivisor = 2; // Hull Divisor
|
| input int InpMaxBars = 5000; // Max bars to retain (0 = unlimited)
|
|
|
| //--- Indicator buffers
|
| double hull1Buffer[], color1Buffer[];
|
| double hull2Buffer[], color2Buffer[];
|
| double hull3Buffer[], color3Buffer[];
|
| double hull4Buffer[], color4Buffer[];
|
|
|
| //+------------------------------------------------------------------+
|
| //| CHull class - memory-optimized Hull MA calculator |
|
| //+------------------------------------------------------------------+
|
| class CHull
|
| {
|
| private:
|
| int m_fullPeriod;
|
| int m_halfPeriod;
|
| int m_sqrtPeriod;
|
| int m_maxBars;
|
| int m_arraySize;
|
| double m_weight1;
|
| double m_weight2;
|
| double m_weight3;
|
| double m_divisor;
|
|
|
| struct sHullArrayStruct
|
| {
|
| double value;
|
| double value3;
|
| double wsum1;
|
| double wsum2;
|
| double wsum3;
|
| double lsum1;
|
| double lsum2;
|
| double lsum3;
|
| };
|
|
|
| sHullArrayStruct m_array[];
|
|
|
| void ZeroArray()
|
| {
|
| for(int i = 0; i < m_arraySize; i++)
|
| {
|
| m_array[i].value = 0;
|
| m_array[i].value3 = 0;
|
| m_array[i].wsum1 = 0;
|
| m_array[i].wsum2 = 0;
|
| m_array[i].wsum3 = 0;
|
| m_array[i].lsum1 = 0;
|
| m_array[i].lsum2 = 0;
|
| m_array[i].lsum3 = 0;
|
| }
|
| }
|
|
|
| public:
|
| CHull() : m_fullPeriod(1), m_halfPeriod(1), m_sqrtPeriod(1),
|
| m_maxBars(0), m_arraySize(0), m_divisor(2.0) { }
|
| ~CHull() { ArrayFree(m_array); }
|
|
|
| bool init(int period, double divisor, int maxBars=0)
|
| {
|
| m_divisor = (divisor > 1.0) ? divisor : 2.0;
|
| m_fullPeriod = (int)(period > 1 ? period : 1);
|
| m_halfPeriod = (int)(m_fullPeriod > 1 ? m_fullPeriod / m_divisor : 1);
|
| m_sqrtPeriod = (int)MathSqrt(m_fullPeriod);
|
| m_maxBars = (maxBars > m_fullPeriod * 2) ? maxBars : 0;
|
| m_arraySize = 0;
|
| m_weight1 = m_weight2 = m_weight3 = 1;
|
|
|
| if(m_maxBars > 0)
|
| {
|
| m_arraySize = ArrayResize(m_array, m_maxBars);
|
| if(m_arraySize < m_maxBars) return false;
|
| ZeroArray();
|
| }
|
| return true;
|
| }
|
|
|
| double calculate(double value, int i, int bars)
|
| {
|
| if(m_maxBars <= 0)
|
| {
|
| if(m_arraySize < bars)
|
| {
|
| m_arraySize = ArrayResize(m_array, bars + 500);
|
| if(m_arraySize < bars) return 0;
|
| }
|
| return CalculateUnlimited(value, i);
|
| }
|
| return CalculateRingBuffer(value, i);
|
| }
|
|
|
| private:
|
| double CalculateUnlimited(double value, int i)
|
| {
|
| m_array[i].value = value;
|
|
|
| if(i > m_fullPeriod)
|
| {
|
| m_array[i].wsum1 = m_array[i-1].wsum1 + value * m_halfPeriod - m_array[i-1].lsum1;
|
| m_array[i].lsum1 = m_array[i-1].lsum1 + value - m_array[i-m_halfPeriod].value;
|
| m_array[i].wsum2 = m_array[i-1].wsum2 + value * m_fullPeriod - m_array[i-1].lsum2;
|
| m_array[i].lsum2 = m_array[i-1].lsum2 + value - m_array[i-m_fullPeriod].value;
|
| }
|
| else
|
| {
|
| m_array[i].wsum1 = m_array[i].wsum2 =
|
| m_array[i].lsum1 = m_array[i].lsum2 = m_weight1 = m_weight2 = 0;
|
| for(int k=0, w1=m_halfPeriod, w2=m_fullPeriod; w2>0 && i>=k; k++, w1--, w2--)
|
| {
|
| if(w1>0)
|
| {
|
| m_array[i].wsum1 += m_array[i-k].value * w1;
|
| m_array[i].lsum1 += m_array[i-k].value;
|
| m_weight1 += w1;
|
| }
|
| m_array[i].wsum2 += m_array[i-k].value * w2;
|
| m_array[i].lsum2 += m_array[i-k].value;
|
| m_weight2 += w2;
|
| }
|
| }
|
|
|
| m_array[i].value3 = 2.0 * m_array[i].wsum1 / m_weight1 - m_array[i].wsum2 / m_weight2;
|
|
|
| if(i > m_sqrtPeriod)
|
| {
|
| m_array[i].wsum3 = m_array[i-1].wsum3 + m_array[i].value3 * m_sqrtPeriod - m_array[i-1].lsum3;
|
| m_array[i].lsum3 = m_array[i-1].lsum3 + m_array[i].value3 - m_array[i-m_sqrtPeriod].value3;
|
| }
|
| else
|
| {
|
| m_array[i].wsum3 =
|
| m_array[i].lsum3 = m_weight3 = 0;
|
| for(int k=0, w3=m_sqrtPeriod; w3>0 && i>=k; k++, w3--)
|
| {
|
| m_array[i].wsum3 += m_array[i-k].value3 * w3;
|
| m_array[i].lsum3 += m_array[i-k].value3;
|
| m_weight3 += w3;
|
| }
|
| }
|
|
|
| return(m_array[i].wsum3 / m_weight3);
|
| }
|
|
|
| double CalculateRingBuffer(double value, int i)
|
| {
|
| int idx = i % m_maxBars;
|
| int prevIdx = (i > 0) ? (i-1) % m_maxBars : idx;
|
|
|
| m_array[idx].value = value;
|
|
|
| if(i > m_fullPeriod)
|
| {
|
| int halfBackIdx = (i - m_halfPeriod) % m_maxBars;
|
| int fullBackIdx = (i - m_fullPeriod) % m_maxBars;
|
|
|
| m_array[idx].wsum1 = m_array[prevIdx].wsum1 + value * m_halfPeriod - m_array[prevIdx].lsum1;
|
| m_array[idx].lsum1 = m_array[prevIdx].lsum1 + value - m_array[halfBackIdx].value;
|
| m_array[idx].wsum2 = m_array[prevIdx].wsum2 + value * m_fullPeriod - m_array[prevIdx].lsum2;
|
| m_array[idx].lsum2 = m_array[prevIdx].lsum2 + value - m_array[fullBackIdx].value;
|
| }
|
| else
|
| {
|
| m_array[idx].wsum1 = m_array[idx].wsum2 =
|
| m_array[idx].lsum1 = m_array[idx].lsum2 = m_weight1 = m_weight2 = 0;
|
| for(int k=0, w1=m_halfPeriod, w2=m_fullPeriod; w2>0 && i>=k; k++, w1--, w2--)
|
| {
|
| int backIdx = (i - k) % m_maxBars;
|
| if(w1>0)
|
| {
|
| m_array[idx].wsum1 += m_array[backIdx].value * w1;
|
| m_array[idx].lsum1 += m_array[backIdx].value;
|
| m_weight1 += w1;
|
| }
|
| m_array[idx].wsum2 += m_array[backIdx].value * w2;
|
| m_array[idx].lsum2 += m_array[backIdx].value;
|
| m_weight2 += w2;
|
| }
|
| }
|
|
|
| m_array[idx].value3 = 2.0 * m_array[idx].wsum1 / m_weight1 - m_array[idx].wsum2 / m_weight2;
|
|
|
| if(i > m_sqrtPeriod)
|
| {
|
| int sqrtBackIdx = (i - m_sqrtPeriod) % m_maxBars;
|
| m_array[idx].wsum3 = m_array[prevIdx].wsum3 + m_array[idx].value3 * m_sqrtPeriod - m_array[prevIdx].lsum3;
|
| m_array[idx].lsum3 = m_array[prevIdx].lsum3 + m_array[idx].value3 - m_array[sqrtBackIdx].value3;
|
| }
|
| else
|
| {
|
| m_array[idx].wsum3 =
|
| m_array[idx].lsum3 = m_weight3 = 0;
|
| for(int k=0, w3=m_sqrtPeriod; w3>0 && i>=k; k++, w3--)
|
| {
|
| int backIdx = (i - k) % m_maxBars;
|
| m_array[idx].wsum3 += m_array[backIdx].value3 * w3;
|
| m_array[idx].lsum3 += m_array[backIdx].value3;
|
| m_weight3 += w3;
|
| }
|
| }
|
|
|
| return(m_array[idx].wsum3 / m_weight3);
|
| }
|
| };
|
|
|
| CHull iHull1, iHull2, iHull3, iHull4;
|
|
|
| //+------------------------------------------------------------------+
|
| //| Custom indicator initialization function |
|
| //+------------------------------------------------------------------+
|
| int OnInit()
|
| {
|
| SetIndexBuffer(0, hull1Buffer, INDICATOR_DATA);
|
| SetIndexBuffer(1, color1Buffer, INDICATOR_COLOR_INDEX);
|
| SetIndexBuffer(2, hull2Buffer, INDICATOR_DATA);
|
| SetIndexBuffer(3, color2Buffer, INDICATOR_COLOR_INDEX);
|
| SetIndexBuffer(4, hull3Buffer, INDICATOR_DATA);
|
| SetIndexBuffer(5, color3Buffer, INDICATOR_COLOR_INDEX);
|
| SetIndexBuffer(6, hull4Buffer, INDICATOR_DATA);
|
| SetIndexBuffer(7, color4Buffer, INDICATOR_COLOR_INDEX);
|
|
|
| iHull1.init(InpPeriod1, InpDivisor, InpMaxBars);
|
| iHull2.init(InpPeriod2, InpDivisor, InpMaxBars);
|
| iHull3.init(InpPeriod3, InpDivisor, InpMaxBars);
|
| iHull4.init(InpPeriod4, InpDivisor, InpMaxBars);
|
|
|
| IndicatorSetString(INDICATOR_SHORTNAME,
|
| StringFormat("UnifiedHullMA(%d,%d,%d,%d)",
|
| InpPeriod1, InpPeriod2, InpPeriod3, InpPeriod4));
|
| IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
|
|
|
| return INIT_SUCCEEDED;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Custom indicator iteration function |
|
| //+------------------------------------------------------------------+
|
| 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[])
|
| {
|
| int i = prev_calculated - 1;
|
| if(i < 0) i = 0;
|
|
|
| for(; i < rates_total && !_StopFlag; i++)
|
| {
|
| double price = close[i];
|
|
|
| hull1Buffer[i] = iHull1.calculate(price, i, rates_total);
|
| hull2Buffer[i] = iHull2.calculate(price, i, rates_total);
|
| hull3Buffer[i] = iHull3.calculate(price, i, rates_total);
|
| hull4Buffer[i] = iHull4.calculate(price, i, rates_total);
|
|
|
| if(i > 0)
|
| {
|
| color1Buffer[i] = (hull1Buffer[i] > hull1Buffer[i-1]) ? 1 :
|
| ((hull1Buffer[i] < hull1Buffer[i-1]) ? 2 : color1Buffer[i-1]);
|
| color2Buffer[i] = (hull2Buffer[i] > hull2Buffer[i-1]) ? 1 :
|
| ((hull2Buffer[i] < hull2Buffer[i-1]) ? 2 : color2Buffer[i-1]);
|
| color3Buffer[i] = (hull3Buffer[i] > hull3Buffer[i-1]) ? 1 :
|
| ((hull3Buffer[i] < hull3Buffer[i-1]) ? 2 : color3Buffer[i-1]);
|
| color4Buffer[i] = (hull4Buffer[i] > hull4Buffer[i-1]) ? 1 :
|
| ((hull4Buffer[i] < hull4Buffer[i-1]) ? 2 : color4Buffer[i-1]);
|
| }
|
| else
|
| {
|
| color1Buffer[i] = color2Buffer[i] = color3Buffer[i] = color4Buffer[i] = 0;
|
| }
|
| }
|
| return rates_total;
|
| }
|
| //+------------------------------------------------------------------+ |