Edgoro
Expert Advisor · MQL5

MQL5 iCustom example: read a custom indicator's buffers

Short answer

Create the handle in OnInit() with iCustom(_Symbol, PERIOD_CURRENT, "Examples\\ZigZag", 12, 5, 3): the name is the indicator's path under MQL5/Indicators, without .ex5, and the inputs follow the indicator's own order. Then read a buffer with CopyBuffer(handle, index, shift, count, array), where the index is the one the indicator passes to SetIndexBuffer().

UsesiCustomCopyBufferBarsCalculated#property tester_indicatorIndicatorRelease

The code

An Expert Advisor that trades nothing: it reads the ZigZag indicator that ships with MetaTrader 5 and prints the last two confirmed swing points on each new bar.

Save it as MQL5/Experts/icustom-read-custom-indicator.mq5 and compile it in MetaEditor (F7): 0 errors, 0 warnings.

MQL5
// Ships the indicator to the Strategy Tester's agents. Detected on its own when iCustom() gets a constant name,
// required when the name is built at run time: harmless here, and it keeps working if you make the name an input.
#property tester_indicator "Examples\\ZigZag.ex5"

input int InpDepth     = 12;  // ZigZag depth
input int InpDeviation = 5;   // ZigZag deviation
input int InpBackstep  = 3;   // ZigZag back step
input int InpLookback  = 300; // Bars to search for swing points

int zigzag = INVALID_HANDLE;

int OnInit()
  {
   // Path relative to MQL5\Indicators, without the extension. Inputs in the indicator's own order and types.
   zigzag = iCustom(_Symbol, PERIOD_CURRENT, "Examples\\ZigZag", InpDepth, InpDeviation, InpBackstep);
   if(zigzag == INVALID_HANDLE)
     {
      PrintFormat("iCustom failed: error %d. Is MQL5\\Indicators\\Examples\\ZigZag.ex5 there?", GetLastError());
      return INIT_FAILED;
     }
   return INIT_SUCCEEDED;
  }

void OnDeinit(const int reason)
  {
   if(zigzag != INVALID_HANDLE)
      IndicatorRelease(zigzag);
  }

// The newest `max` swing points, newest first. ZigZag writes 0.0 on every bar that isn't a swing point.
int SwingPoints(double &price[], int &shift[], const int max)
  {
   int count = MathMin(InpLookback, BarsCalculated(zigzag));
   if(count <= 0)
      return 0;                                                // not calculated yet
   double buf[];
   ArraySetAsSeries(buf, true);
   int copied = CopyBuffer(zigzag, 0, 0, count, buf);         // buffer 0: the ZigZag line
   if(copied <= 0)
      return 0;
   int found = 0;
   for(int i = 0; i < copied && found < max; i++)
     {
      if(buf[i] == 0.0 || buf[i] == EMPTY_VALUE)
         continue;
      price[found] = buf[i];
      shift[found] = i;
      found++;
     }
   return found;
  }

void OnTick()
  {
   static datetime lastBar = 0;
   datetime bar = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(bar == 0 || bar == lastBar)
      return;
   double price[3];
   int    shift[3];
   if(SwingPoints(price, shift, 3) < 3)
      return;                                                  // retry on the next tick
   lastBar = bar;
   // Point 0 is the end of the leg still being drawn: it moves until the next swing confirms it.
   // Points 1 and 2 are the last two confirmed swings.
   bool lastIsHigh = price[1] > price[2];
   PrintFormat("Last confirmed swing %s %s (%d bars ago), the one before %s %s",
               lastIsHigh ? "high" : "low", DoubleToString(price[1], _Digits), shift[1],
               lastIsHigh ? "low" : "high", DoubleToString(price[2], _Digits));
  }

How it works

  • "Examples\\ZigZag". The path relative to MQL5/Indicators, with the backslash doubled inside an MQL5 string and no extension. The ZigZag example is installed with every terminal, so the EA runs as it is.
  • The inputs. ZigZag.mq5 declares InpDepth, InpDeviation and InpBackstep, three int inputs, in that order. iCustom() passes values by position: the names don't matter, the order and types do.
  • Buffer 0. ZigZag's source calls SetIndexBuffer(0, ZigZagBuffer, INDICATOR_DATA), then 1 and 2 for two calculation buffers. The index in CopyBuffer() is that number.
  • buf[i] == 0.0. ZigZag writes 0 on every bar that isn't a swing point (it sets PLOT_EMPTY_VALUE to 0). Many other indicators use EMPTY_VALUE instead, so the loop checks both.
  • Point 0 is skipped. The newest point is the end of the leg ZigZag is still drawing: it moves while price extends and can disappear. Points 1 and 2 are confirmed.
  • #property tester_indicator. The Strategy Tester finds indicators named by a constant string by itself; the property is needed when the name is built at run time, and costs nothing here.

Pitfalls

  • The file isn't where iCustom() looks. A wrong path, a missing .ex5 or an indicator only in another terminal's folder gives INVALID_HANDLE and 4802 indicator cannot be created.
  • Inputs in the wrong order or type. Values are matched by position and converted silently: pass a deviation where the depth is expected and you get a different indicator, not an error. Inputs you leave out take their default values.
  • Counting plots instead of buffers. A color line (DRAW_COLOR_LINE) uses two buffers, values and color index, so the buffer after it isn't the next plot. Read the SetIndexBuffer() calls, or print each buffer once and compare with the Data Window.
  • Using the repainting point. Trading ZigZag's newest point means trading a swing that may vanish on the next bar. In the Strategy Tester this looks far better than it is live.
  • Copying before the indicator is ready. Right after start, CopyBuffer() fails with 4806; reading the arrays anyway can stop the EA with array out of range.

Variations

  • Inputs decided at run time. IndicatorCreate() takes the inputs as an array of MqlParam, useful when their number or types vary. It then needs #property tester_indicator for the Strategy Tester.
  • Indicators from the Market. Purchased indicators are installed under MQL5/Indicators/Market, so their name starts with "Market\\".
  • Only the .ex5. iCustom() needs the compiled file, not the source. Without the source, the Data Window shows the plotted values in order, which helps match buffer numbers.
  • Built-in indicators. For the moving average, RSI and the other standard indicators, the dedicated functions are simpler: see reading iMA and iRSI.

Questions

How do I find a custom indicator's buffer numbers?

In the source, look at the SetIndexBuffer() calls: the first argument is the number CopyBuffer() needs. Without the source, print each buffer's value for one bar from the EA and compare it with the Data Window.

Do I need the .mq5 source to use iCustom()?

No. iCustom() loads the compiled .ex5 file. You only need to know its inputs, in order, and which buffer holds the value you want.

Can I call an MT4 indicator from an MT5 EA with iCustom()?

No. MetaTrader 5 can't run .ex4 files: the indicator has to be converted to MQL5 and compiled first. How to convert MQL4 to MQL5 covers the changes indicators need.

This program is a minimal example written to show one technique, not a trading system, and none of it is a recommendation to trade. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.

All MQL5 errors

Want it inside a complete EA?

If your strategy relies on a custom indicator, Edgoro's MT5 EA generator can call it through iCustom() once you give it the indicator's inputs and buffers. A clean build proves the code compiles, not that a strategy works: test it on a demo account.