FXTAA Русская версия

MT4 indicators: 3 moving averages for all timeframes

Switching between charts to check whether the H4 average is above or below price is tedious and error-prone. Putting all three on one chart is easy — reading them correctly is the part people get wrong.

Updated: 2026-09-23

A multi-timeframe moving average is not a special indicator. It is the ordinary iMA function called with a different period argument, plus the index arithmetic needed to line the higher-timeframe bars up with the bars on your chart.

The core call

iMA takes the timeframe as its second argument. Passing PERIOD_H4 while sitting on an M15 chart returns the H4 average — the function does not care which chart it is called from.

double maH4 = iMA(Symbol(), PERIOD_H4, 50, 0, MODE_EMA, PRICE_CLOSE, shift);

The difficulty is shift. On an M15 chart, bar 20 is five hours ago. On H4 that is roughly bar 1. You cannot pass the M15 index into an H4 call and expect a sensible answer.

Converting the index

iBarShift does the translation: give it a timeframe and a timestamp, and it returns which bar of that timeframe contains that moment.

// Draw H1, H4 and D1 EMAs on whatever chart this is attached to
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_color1 clrGoldenrod
#property indicator_color2 clrSteelBlue
#property indicator_color3 clrSeaGreen

input int MaPeriod  = 50;
input int MaMethod  = MODE_EMA;
input bool UseClosedBarsOnly = true;

double bufH1[], bufH4[], bufD1[];

int OnInit()
{
   SetIndexBuffer(0, bufH1); SetIndexLabel(0, "EMA H1");
   SetIndexBuffer(1, bufH4); SetIndexLabel(1, "EMA H4");
   SetIndexBuffer(2, bufD1); SetIndexLabel(2, "EMA D1");
   return(INIT_SUCCEEDED);
}

double MtfMA(int tf, datetime t)
{
   int shift = iBarShift(Symbol(), tf, t, false);
   if(shift < 0) return(EMPTY_VALUE);
   if(UseClosedBarsOnly) shift += 1;          // skip the bar still forming
   return(iMA(Symbol(), tf, MaPeriod, 0, MaMethod, PRICE_CLOSE, shift));
}

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 limit = rates_total - MathMax(prev_calculated - 1, 1);

   for(int i = 0; i < limit; i++)
   {
      datetime t = Time[i];
      bufH1[i] = MtfMA(PERIOD_H1, t);
      bufH4[i] = MtfMA(PERIOD_H4, t);
      bufD1[i] = MtfMA(PERIOD_D1, t);
   }
   return(rates_total);
}

The repainting trap

This is the part that matters, and the reason UseClosedBarsOnly exists.

A D1 average calculated during the trading day uses today's close — which is not the close, it is the current price. As the day proceeds, that value changes. Tomorrow, when you look back at today's bar, the line sits somewhere it never actually was while you were trading.

This is why multi-timeframe indicators backtest beautifully and trade badly. The tester sees a line that could not have existed in real time. Shifting by one bar costs you freshness and buys you honesty.

History depth

iMA on D1 with a period of 50 needs fifty daily bars. If the terminal has not downloaded them, the function returns zero and your line drops to the bottom of the chart. Open Tools → History Center and let the data load for every timeframe you reference, not just the one you are watching.

How much history is available is a property of the broker's server rather than of MT4. Some servers serve years of M1 data; others truncate aggressively, which quietly breaks long-period higher-timeframe indicators. Check what your server actually serves before building anything on top of it: open Tools → History Center and see how far the data goes back on each timeframe you reference.

Reading three averages without over-reading them

Moving averages are lagging by construction; three of them are lagging three times over. Their value is in describing the state you are in, not in calling turns.