Master MetaTrader 4/5 Multi-Timeframe Analysis with MQL

Learn to program multi-timeframe analysis in MQL4/MQL5 EAs and indicators. Step-by-step guide with code examples, platform differences, and expert.

master-metatrader-4-5-multi-timeframe-analysis

Introduction: Why Multi-Timeframe Analysis Matters for Your EAs

Single-timeframe EAs are like driving with only the rearview mirror. They see the immediate price action but miss the broader market context. Multi-timeframe (MTF) analysis lets your Expert Advisor check the daily trend before taking a 15-minute entry, or confirm a 1-hour signal with the 4-hour momentum. This dramatically reduces false signals and aligns your trades with the dominant market direction.

In this guide, you will learn how to programmatically access higher timeframe data inside your MQL4 or MQL5 EA or custom indicator. We will cover the core functions, handle the platform-specific differences, and walk through a complete working example. By the end, you will be able to add MTF logic to any automated strategy.

Prerequisites

  • MetaTrader 4 or 5 installed on your computer (any version from build 600+ for MT4, build 1000+ for MT5).
  • MetaEditor (comes with the platform).
  • Basic familiarity with MQL syntax: variables, functions, if statements, and loops.
  • A demo or live trading account to test your EA.
  • A text editor or IDE (optional) – MetaEditor is sufficient.

Step-by-Step Instructions: Building an MTF EA in MQL4/MQL5

1. Understanding Timeframe Constants

MetaTrader uses predefined constants for timeframes. In MQL4, they are PERIOD_M1, PERIOD_M5, PERIOD_H1, PERIOD_H4, PERIOD_D1, etc. In MQL5, the same constants exist but are defined as ENUM_TIMEFRAMES enumeration. Always use these constants instead of numeric values for readability and compatibility.

Constant Value (MT4/MT5) Timeframe
PERIOD_M1 1 1 minute
PERIOD_H1 60 1 hour
PERIOD_D1 1440 Daily

2. Accessing Higher Timeframe Data: The Core Functions

The key is using iClose(), iOpen(), iHigh(), iLow(), or any i***() indicator function with a different timeframe parameter. In MQL4, the syntax is:

double closeH4 = iClose(Symbol(), PERIOD_H4, 0);  // Current H4 close
double rsiH4   = iRSI(Symbol(), PERIOD_H4, 14, PRICE_CLOSE, 1); // H4 RSI value

In MQL5, the approach is similar but uses handles for indicators. For price data, use CopyClose():

double closeH4[];
CopyClose(Symbol(), PERIOD_H4, 0, 1, closeH4);
double currentH4Close = closeH4[0];

For indicators in MQL5, you must create a handle first (e.g., iRSI() returns a handle), then use CopyBuffer() to retrieve values. This is a critical difference from MQL4.

3. Complete MTF Logic Example (MQL4)

Below is a simplified EA that only trades when the H4 trend is bullish (price above 200 EMA) and the M15 RSI is oversold. This demonstrates multi-timeframe confirmation.

//+------------------------------------------------------------------+
//|                                                  MTF_Example.mq4 |
//+------------------------------------------------------------------+
#property strict

input double LotSize = 0.1;
input int      H4_EMA_Period = 200;
input int      RSI_Period = 14;
input int      RSI_Oversold_Level = 30;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   // 1. Get H4 EMA value (higher timeframe trend filter)
   double h4EMA = iMA(Symbol(), PERIOD_H4, H4_EMA_Period, 0, MODE_EMA, PRICE_CLOSE, 0);
   double h4Close = iClose(Symbol(), PERIOD_H4, 0);

   // 2. Get M15 RSI value (entry signal timeframe)
   double rsiM15 = iRSI(Symbol(), PERIOD_M15, RSI_Period, PRICE_CLOSE, 0);

   // 3. Trade logic: only buy if H4 trend is up AND M15 RSI is oversold
   if(h4Close > h4EMA && rsiM15 < RSI_Oversold_Level)
     {
      // Check for existing positions to avoid repeated entries
      if(CountPositions() == 0)
        {
         OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, 0, 0, "MTF Buy", 0, 0, Green);
        }
     }
  }
//+------------------------------------------------------------------+
//| Count open positions                                             |
//+------------------------------------------------------------------+
int CountPositions()
  {
   int count = 0;
   for(int i = OrdersTotal() - 1; i >= 0; i--)
     {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
        {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == 0)
           {
            count++;
           }
        }
     }
   return count;
  }
//+------------------------------------------------------------------+

4. Adapting for MQL5

MQL5 requires handle-based indicators. Here is the same logic in MQL5:

//+------------------------------------------------------------------+
//|                                                  MTF_Example.mq5 |
//+------------------------------------------------------------------+
#property strict

input double LotSize = 0.1;
input int      H4_EMA_Period = 200;
input int      RSI_Period = 14;
input int      RSI_Oversold_Level = 30;

int h4EMA_Handle;
int rsiM15_Handle;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   h4EMA_Handle = iMA(Symbol(), PERIOD_H4, H4_EMA_Period, 0, MODE_EMA, PRICE_CLOSE);
   rsiM15_Handle = iRSI(Symbol(), PERIOD_M15, RSI_Period, PRICE_CLOSE);
   if(h4EMA_Handle == INVALID_HANDLE || rsiM15_Handle == INVALID_HANDLE)
     {
      Print("Handle creation failed!");
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   double h4EMA[], h4Close[], rsiM15[];
   ArraySetAsSeries(h4EMA, true);
   ArraySetAsSeries(h4Close, true);
   ArraySetAsSeries(rsiM15, true);

   // Copy H4 data
   if(CopyBuffer(h4EMA_Handle, 0, 0, 1, h4EMA) < 1) return;
   if(CopyClose(Symbol(), PERIOD_H4, 0, 1, h4Close) < 1) return;

   // Copy M15 RSI
   if(CopyBuffer(rsiM15_Handle, 0, 0, 1, rsiM15) < 1) return;

   // Trade logic
   if(h4Close[0] > h4EMA[0] && rsiM15[0] < RSI_Oversold_Level)
     {
      // Place trade using CTrade class (simplified)
      // ... (trade execution omitted for brevity)
     }
  }
//+------------------------------------------------------------------+

Tips and Best Practices from Experience

  • Always handle handle creation errors in MQL5. Check for INVALID_HANDLE in OnInit() and return INIT_FAILED to prevent crashes.
  • Use ArraySetAsSeries() in MQL5 to ensure index 0 is the most recent bar. Without it, you will get historical data in reverse order.
  • Cache higher timeframe data to avoid redundant calculations. Store the last known H4 close and only update when a new H4 bar forms.
  • Be mindful of bar synchronization. Higher timeframe bars close at different times than the current chart. Use TimeCurrent() and iTime() to check if a new bar has formed on the higher timeframe.
  • Test on multiple symbols. An MTF EA that works on EURUSD may behave differently on JPY pairs due to volatility. Always backtest across different asset classes.

Common Mistakes and Troubleshooting

Mistake 1: Using the Current Chart Timeframe Instead of Higher Timeframe

New developers often accidentally use iClose(Symbol(), 0, 0) inside an MTF EA. The 0 parameter means "current chart timeframe," which defeats the purpose. Always explicitly specify the desired timeframe constant.

Mistake 2: MQL5 Indicator Handle Not Created or Invalid

If your MQL5 EA compiles but does nothing, check the Experts tab. A common error is "Handle creation failed" because the indicator does not exist or the symbol is not visible. Ensure the symbol is in Market Watch.

Mistake 3: Off-By-One Index Errors

When copying data from higher timeframes, the index 0 always refers to the current (still-open) bar. For confirmed signals, use index 1 (the last completed bar). This avoids repainting and ensures your backtest matches live trading.

Mistake 4: Not Checking for New Bars

Without a new-bar check, your EA will fire multiple signals on every tick during the same bar. Use a static datetime variable to track the last processed bar time:

static datetime lastBarTime = 0;
if(Time[0] == lastBarTime) return; // Already processed this bar
lastBarTime = Time[0];

Mistake 5: Not Handling Symbol Visibility in MQL5

In MQL5, if a symbol is not visible in Market Watch, CopyClose() or CopyBuffer() may return zero bytes. Add this check before copying data:

if(!SymbolInfoInteger(Symbol(), SYMBOL_SELECT))
   SymbolSelect(Symbol(), true);

Mistake 6: Mixing MQL4 and MQL5 Syntax

Common confusion: In MQL4, iRSI() returns the RSI value directly. In MQL5, iRSI() returns a handle. Trying to use the MQL4 pattern in MQL5 will cause compilation errors. Always check which platform you are coding for.

Advanced Techniques: Using Custom Indicators Across Timeframes

To call a custom indicator from a higher timeframe, use iCustom() in MQL4 or the handle-based approach in MQL5. For example, to access a ZigZag indicator on H1 from an M15 chart in MQL4:

double zigzagH1 = iCustom(Symbol(), PERIOD_H1, "ZigZag", Depth, Deviation, Backstep, 0, 0);

In MQL5, you create a handle for the custom indicator with the higher timeframe and then copy its buffer:

int zigzagHandle = iCustom(Symbol(), PERIOD_H1, "ZigZag", Depth, Deviation, Backstep);
double zigzagValues[];
ArraySetAsSeries(zigzagValues, true);
CopyBuffer(zigzagHandle, 0, 0, 1, zigzagValues);
double currentZigzag = zigzagValues[0];

Performance Optimization: Caching Higher Timeframe Data

Calling iClose() or CopyClose() on every tick for higher timeframes is inefficient. Cache the values and update only when a new bar forms on that timeframe. Here is a pattern for MQL4:

static double cachedH4Close = 0;
static datetime lastH4Time = 0;

datetime currentH4Time = iTime(Symbol(), PERIOD_H4, 0);
if(currentH4Time != lastH4Time)
  {
   cachedH4Close = iClose(Symbol(), PERIOD_H4, 0);
   lastH4Time = currentH4Time;
  }

For MQL5, use SeriesInfoInteger() to check for new bars:

static datetime lastH4Time = 0;
datetime currentH4Time;
SeriesInfoInteger(Symbol(), PERIOD_H4, SERIES_LASTBAR_DATE, currentH4Time);
if(currentH4Time != lastH4Time)
  {
   // Update cached values
   lastH4Time = currentH4Time;
  }

Summary and Next Steps

You now have a solid foundation for implementing multi-timeframe analysis in your MQL4 and MQL5 EAs. The core principle is simple: use iClose()/CopyClose() and indicator functions with the higher timeframe constant. The devil is

Frequently Asked Questions

Can I use the same MQL4 MTF code in MQL5?

Not directly. MQL5 uses handle-based indicators and requires CopyBuffer() to retrieve values. The price functions like iClose() also differ. You must rewrite the indicator calls for MQL5, but the logic structure remains the same.

Why does my MTF EA give different results in backtest vs live trading?

The most common cause is repainting. Ensure you use confirmed bars (index 1 or higher) for higher timeframe data, not the current (still-forming) bar. Also verify that your backtest uses "Every tick" mode for accurate bar formation.

Do I need special permissions to access higher timeframe data?

No, MetaTrader provides all timeframes for free as long as the symbol is available in Market Watch. No additional data subscriptions are required for standard timeframes like H1, H4, D1. Some brokers may restrict M1/M5 for certain symbols.

Community

Clap for the article and open comments only when you want to read them.

0 claps0 comments

Related articles