Blog
MQL5 CopyBuffer Guide: Read Indicator Values Safely in an EA
Quick Answer
In MQL5, functions such as iMA() and iRSI() return indicator handles, not the indicator's current numeric value. Create and validate the handle, then call CopyBuffer() to retrieve values from a specified buffer and bar shift. Check the number of values copied before using them, and decide explicitly whether your EA should read the forming bar or a completed candle.
Key Facts
| Item | What it means |
|---|---|
| Indicator handle | A reference created by an indicator function such as iMA() |
| Buffer number | The indicator output to read; common built-in indicators use buffer 0 for their primary line |
| Start position | 0 is the current, forming bar; 1 is the last closed bar |
| Return value | The count copied, or an error value; compare it with the requested count |
| Cleanup | Release valid handles in OnDeinit() when they are no longer needed |
Pine Script can express an indicator as a series, for example ta.ema(close, 20). MQL5 separates creating an indicator from reading its calculated values. That extra step gives an EA access to indicators on different symbols and timeframes, but it also introduces failure cases: an invalid handle, insufficient history, a not-yet-calculated buffer, or a wrong shift can all lead to missing or misleading signals.
Create the Handle Once, Then Copy Values
Create handles in OnInit() rather than recreating them on every tick. Validate each handle, and release it when the EA exits. This example reads a 20-period EMA for the chart symbol and timeframe:
int emaHandle = INVALID_HANDLE;
int OnInit()
{
emaHandle = iMA(_Symbol, _Period, 20, 0, MODE_EMA, PRICE_CLOSE);
if (emaHandle == INVALID_HANDLE)
{
Print("Could not create EMA handle. Error: ", GetLastError());
return INIT_FAILED;
}
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if (emaHandle != INVALID_HANDLE)
IndicatorRelease(emaHandle);
}
GetLastError() can help diagnose a failed handle creation, but it is not a substitute for checking the handle itself. For a custom indicator, iCustom() must also receive the correct indicator path and input parameters, and the EA must request the correct output buffer index.
Choose Bar Shifts Intentionally
For a closed-bar strategy, shift 1 is generally the last completed candle and shift 2 is the completed candle before it. Shift 0 refers to the live candle, whose indicator value can change as new ticks arrive. Using shift 0 may be appropriate for an explicitly intrabar system, but it can produce a signal that disappears before the candle closes.
double ema[];
ArraySetAsSeries(ema, true);
int copied = CopyBuffer(emaHandle, 0, 1, 2, ema);
if (copied != 2)
{
Print("EMA values are not ready. Copied: ", copied);
return;
}
double lastClosedEma = ema[0];
double priorClosedEma = ema[1];
With ArraySetAsSeries() enabled, logical index 0 refers to the newest requested element. CopyBuffer() places the oldest copied element at the beginning of physical memory; series indexing reverses how the array is accessed. Confirm the indexing for your chosen start position and count, and log timestamps with values while testing. Never assume the copied data belongs to the bar you intended without checking the shift.
For an indicator on another timeframe, pass that timeframe when creating its handle and compare its bar time with the chart's decision time. A higher-timeframe candle may still be forming while several lower-timeframe bars arrive. Decide whether to use its current value or wait for the previous completed higher-timeframe bar. Also check BarsCalculated(handle) when an EA starts or history is loading; a successful handle creation does not mean all requested values are ready.
Common CopyBuffer Problems
- Using the forming candle by accident: A signal based on shift
0may change before bar close. - Ignoring the return count: A copy that returns fewer values than requested must not be treated as complete.
- Reading before history is ready: Newly attached EAs or higher-timeframe indicators may need more data or another tick before calculation is available.
- Requesting the wrong buffer: Multi-line indicators can expose multiple buffers; consult the indicator's buffer mapping.
- Recreating handles on every tick: This adds avoidable work and makes lifecycle management harder.
- Assuming a handle proves correctness: A valid handle says the indicator was created, not that the symbol, timeframe, parameters, or buffer match the intended calculation.
For a Pine-to-MQL5 migration, compare indicator readings at the same symbol, timeframe, and completed-bar timestamps before investigating order logic. See the Pine Script to MQL5 guide and the article on why converted strategies can differ.
When signals use two buffers, copy and validate both before comparing them. A half-updated pair should not create a trade. Keep handle creation, buffer reads, and decision logic separate so logs can show whether the error came from initialization, data readiness, or the strategy condition.
Frequently Asked Questions
Does iMA() return the moving average value?
No. In MQL5, iMA() returns an indicator handle. Call CopyBuffer() with that handle to retrieve calculated moving-average values.
What does shift zero mean in CopyBuffer()?
Start position zero requests values beginning at the current, forming bar. Use shift one when you specifically want the last closed bar.
Why can CopyBuffer() return fewer values than requested?
The series may not have enough history or the indicator may not have finished calculating yet. Check the copied count, wait for data to be ready, and avoid using incomplete results.
Verify Signals Before Orders
Log buffer values with their bar times, compare them to chart values, and test with the same inputs before sending trades. Correct buffer handling does not verify risk or execution behavior. CodeFlowOS can produce an MQL5 draft with an optional platform check; see the verification notes.
A safe read checklist
Before using a copied value in an order condition, validate the handle, check BarsCalculated() where startup history may still be loading, request the intended buffer and shift, compare the returned count with the requested count, and associate the value with the bar timestamp. These checks answer different questions: a valid handle does not guarantee data readiness, and a full copy does not guarantee that the chosen shift matches the strategy.
For two-line conditions such as a moving-average crossover, copy both buffers and confirm both requests succeeded before evaluating either signal. If one buffer is missing, do not compare a current value from one line with an old value from the other. In a multi-timeframe strategy, log the source timeframe's bar time as well as the chart bar time so a delayed higher-timeframe update is visible.
Copy a one-value signal once per new bar
The following pattern combines a closed-bar read with duplicate-decision prevention. It leaves order placement out intentionally; add it only after defining whether a non-empty value means long, short, or something else.
datetime lastDecisionBar = 0;
void OnTick()
{
const datetime currentBar = iTime(_Symbol, _Period, 0);
if (currentBar == 0 || currentBar == lastDecisionBar)
return;
double value[];
ArraySetAsSeries(value, true);
if (CopyBuffer(emaHandle, 0, 1, 1, value) != 1)
return;
lastDecisionBar = currentBar;
Print("Closed-bar EMA at ", TimeToString(currentBar), ": ", value[0]);
// Evaluate a fully specified signal here.
}
Update lastDecisionBar only after the copy succeeds. If the indicator has not calculated yet, this lets a later tick retry the read for the same bar rather than silently skipping it. If your strategy intentionally evaluates intrabar, do not use this guard unchanged; define how changing values and repeated signals should be handled.
Related implementation topics include indicator-to-EA signal contracts, Pine/MQL5 result differences, MQL5 trade-result states, and position filtering. The Pine-to-MQL5 entry guide explains how the same signal feeds an order request.
Try the converter, read the conversion walkthrough, or compare plans. Review buffer and bar semantics, compile the EA, and compare timestamped values in Strategy Tester before live use.