Back to MetaTrader 5 MQL5 for CFD Traders
Part 4

Built-In Indicators via Handles

Stop hand-rolling averages. Create handles, read them with CopyBuffer, release them on exit.

Intermediate11 min readBeginner → AdvancedLesson 14 / 17
Try it in MetaEditor
Read iMA and iATR through handles

Save as MQL5/Experts/Lesson14Handles.mq5. It creates handles in OnInit, reads them in OnTick, and releases them in OnDeinit — the correct lifecycle.

//+------------------------------------------------------------------+
//| Lesson 14 - Indicator handles: iMA + iATR                        |
//| Read-only: this EA prints values, it does not trade.             |
//+------------------------------------------------------------------+
#property copyright "Strategist Academy"
#property version   "1.00"
#property strict

input int MAPeriod  = 20;
input int ATRPeriod = 14;

int maHandle  = INVALID_HANDLE;
int atrHandle = INVALID_HANDLE;
datetime lastBarTime = 0;

int OnInit()
{
   maHandle  = iMA(_Symbol, _Period, MAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   atrHandle = iATR(_Symbol, _Period, ATRPeriod);

   if(maHandle == INVALID_HANDLE || atrHandle == INVALID_HANDLE)
   {
      Print("Failed to create indicator handles. Error: ", GetLastError());
      return(INIT_FAILED);
   }

   Print("Handles created. MA=", maHandle, "  ATR=", atrHandle);
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   // Always release - a leaked handle per backtest pass will stall the tester.
   if(maHandle  != INVALID_HANDLE) IndicatorRelease(maHandle);
   if(atrHandle != INVALID_HANDLE) IndicatorRelease(atrHandle);
}

//+------------------------------------------------------------------+
//| Pull one value out of an indicator handle                        |
//+------------------------------------------------------------------+
bool ReadValue(const int handle, const int buffer, const int shift, double &out)
{
   double buf[];
   ArraySetAsSeries(buf, true);            // 0 = newest

   int need = shift + 3;
   int got  = CopyBuffer(handle, buffer, 0, need, buf);

   if(got < need)
      return false;

   double v = buf[shift];
   if(v == EMPTY_VALUE || v == 0.0)
      return false;

   out = v;
   return true;
}

void OnTick()
{
   datetime t = iTime(_Symbol, _Period, 0);
   if(t == lastBarTime) return;
   lastBarTime = t;

   // shift 1 = last CLOSED bar. shift 0 is still forming and will repaint.
   double ma  = 0.0;
   double atr = 0.0;

   if(!ReadValue(maHandle, 0, 1, ma))
   {
      Print("Could not read MA yet - not enough bars.");
      return;
   }
   if(!ReadValue(atrHandle, 0, 1, atr))
   {
      Print("Could not read ATR yet - not enough bars.");
      return;
   }

   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   Print("bid=", DoubleToString(bid, _Digits),
         "  EMA(", MAPeriod, ")=", DoubleToString(ma, _Digits),
         "  ATR(", ATRPeriod, ")=", DoubleToString(atr, _Digits),
         "  ATR in points=", DoubleToString(atr / _Point, 1),
         "  price is ", (bid > ma ? "ABOVE" : "below"), " the MA");
}
To run it
  1. 1. Open MetaEditor from MT5 (press F4), and pick the folder this program belongs in — Experts, Indicators, or Scripts.
  2. 2. Create a new file, paste this over it, then press F7 to compile. Fix anything the Errors tab reports.
  3. 3. Back in MT5, drag it onto a chart — or open the Strategy Tester if it is an Expert Advisor.

Handles, not values

MQL5 technical indicators are objects you create once and then query. iMA, iATR, iRSI and friends return an int handle — not an array, and not a value. You then pull values out with CopyBuffer.

The three-step pattern

  • Create in OnInitiMA(_Symbol, _Period, 20, 0, MODE_EMA, PRICE_CLOSE). Check for INVALID_HANDLE and return INIT_FAILED if it fails.
  • Read with CopyBuffer — copy into a double array, set series order first, and check the returned count.
  • Release in OnDeinitIndicatorRelease(handle). Non-negotiable.

Why not hand-roll everything?

Lesson 4 had you compute an SMA by hand so you would understand buffers. In production, use the built-ins: they are faster, they handle applied price correctly, and they are already debugged. Write your own only when the built-in does not do what you need.

The handle leak

If you create a handle in OnTick instead of OnInit, you create one per tick and the terminal grinds to a halt. Create once, reuse many, release once. This is the most common performance bug in beginner EAs.

Enough bars, or empty values

Right after creation the indicator has not calculated yet. Copy too early and you get an empty array or EMPTY_VALUE. Either wait for enough bars or check what CopyBuffer actually returned — never assume it filled your array.

Shift 1, not shift 0

Bar 0 is the forming bar. Its indicator value moves until the bar closes. Any signal computed on bar 0 will repaint in hindsight and look better in a backtest than it ever was live. Use shift 1 for decisions unless you specifically want intrabar behaviour.

What you just did

Lesson 14 of 17 in MetaTrader 5 MQL5 for CFD Traders. When you have run the examples or read the section, tick it off and move to the next lesson.