FX NOOKAI CODE LAB
Loading

MT5 Non-Repaint Indicator: How to Check and Build One

By FXNook Lab team

An MT5 indicator is non-repainting in the useful sense when a signal on a closed candle stays in the same place and keeps the same value as new ticks arrive. To build or assess one, define when a signal becomes final, avoid using unfinished or future data for that signal, and compare the same closed bars after reloads and during forward observation. A signal that changes while its candle is still forming is not, by itself, proof of repainting.

What “non-repainting” means in MT5

Indicators usually draw from buffers: arrays of values that MetaTrader displays as lines, histograms, arrows, or other plots. A repaint happens when a value or signal that was already displayed for a past bar later changes, disappears, or moves to a different bar. That may happen because the calculation uses information that was not available when the signal first appeared, or because the indicator recalculates its history differently.

Bar 0 is the current, unfinished candle. Its high, low, close, and indicator values can change with every incoming tick, so a condition based on bar 0 can appear and disappear before the candle closes. That is an intrabar change. It may be an intentional early signal, but it should not be presented as a final closed-bar signal.

Bar 1 is the most recently closed candle when arrays use series indexing, where index 0 means the latest bar. A rule that waits for bar 1 to close can prevent an arrow from flickering during the formation of bar 0. It does not, on its own, guarantee that the indicator never changes historical values: the code might still use future bars, revise earlier buffer values, or read a higher-timeframe candle that is not complete.

It helps to set a precise promise before testing: for example, “an arrow is final after its source candle closes, unless the underlying price history or indicator settings change.” That definition separates normal live-bar movement from changes to signals that were already supposed to be final.

How to check whether an MT5 indicator changes closed-bar signals

A finished-chart screenshot shows only the result after the calculations have run. It does not show when each arrow first appeared, whether it moved, or whether earlier values were removed. Test identifiable bars over time instead of judging the final picture.

  1. Choose a symbol, timeframe, and test period. Record the chart settings and indicator inputs. Pick several historical bars with signals and several without them, and note each bar’s time and plotted value or arrow.
  2. Wait for candles to close before recording. Do not count an arrow on bar 0 as a final signal. Once the next candle begins, record the signal on the now-closed bar, using the Data Window or the indicator’s buffer values if available.
  3. Reload the indicator. Remove it from the chart and attach it again with the same settings. Compare the same bar times, not just the apparent spacing between arrows. Note any signal that appeared, disappeared, or moved.
  4. Change the chart context and return. Switch timeframe or symbol, then return to the original chart. After MetaTrader has loaded the relevant history, compare the same bars again. Different charts can load or refresh data, so allow the chart to finish updating before drawing conclusions.
  5. Restart and observe forward. Restart the terminal, revisit the same history, and then watch new signals as candles close. Keep a simple log of bar time, signal state, and value. This tests both historical stability and how the indicator behaves when data arrives live.

If you can inspect source code, look at how the indicator sets its buffers in OnCalculate and how it indexes price data. In MT5, ArraySetAsSeries can set series-style indexing for arrays, while CopyBuffer retrieves indicator values from an indicator handle. The official documentation for OnCalculate, ArraySetAsSeries, and CopyBuffer explains these interfaces.

Also keep the data context consistent. If the broker supplies revised price history, or you change an input, a recalculated indicator may differ even when its code does not deliberately repaint. Record the symbol, timeframe, inputs, and approximate test period so you can tell a changed calculation from a changed chart setup.

Common causes of repainting and misleading signals

CauseWhat you may seeWhat to do
Calculations use bar 0A signal appears and vanishes before the current candle closes, or its plotted value moves with price.Decide whether the signal is meant to be live or final. For a closed-bar rule, evaluate the condition on a completed bar and plot only after that bar closes.
Buffers are backfilled, shifted, or cleared and recalculatedArrows appear on older bars after a delay, move to earlier candles, or disappear after reload.Trace which buffer index receives each value and whether the code intentionally writes to bars other than the one just confirmed. Compare buffer values by bar time.
Higher-timeframe values come from an unfinished candleA lower-timeframe signal changes while the higher-timeframe candle is still forming, then looks settled in history.Use the last completed higher-timeframe candle for a final signal. Map each chart bar to the correct completed source bar rather than always reading the higher-timeframe bar at shift 0.
Pivot or fractal logic needs future barsA turning point is drawn on an earlier bar only after later candles form.Make the confirmation delay explicit. Plot the signal when the required right-side bars have closed, or clearly label a back-plotted point as a delayed confirmation rather than a live signal.

Backfilling needs a little care in its description. A pivot confirmed by later candles may be stable once confirmed, but drawing it on the original pivot candle makes the historical chart look as though the signal was available earlier. That is a problem for judging live timing, even if the confirmed pivot does not subsequently move.

A closed-bar MQL5 signal example

This minimal custom indicator marks a simple one-bar reversal condition. It checks closed candles only: a buy arrow is plotted when a candle closes above the preceding candle after that preceding close was no higher than the close before it. The sell condition mirrors that rule. The example is deliberately simple so the indexing and timing are easy to inspect.

// MQL5 custom indicator: signals are calculated on closed bars.
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2

#property indicator_label1  "Buy"
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrLimeGreen
#property indicator_width1  1

#property indicator_label2  "Sell"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrTomato
#property indicator_width2  1

double BuyBuffer[];
double SellBuffer[];

int OnInit()
{
   SetIndexBuffer(0, BuyBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, SellBuffer, INDICATOR_DATA);

   ArraySetAsSeries(BuyBuffer, true);
   ArraySetAsSeries(SellBuffer, true);

   PlotIndexSetInteger(0, PLOT_ARROW, 233);
   PlotIndexSetInteger(1, PLOT_ARROW, 234);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   return INIT_SUCCEEDED;
}

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[])
{
   if(rates_total < 3)
      return 0;

   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);

   for(int i = 0; i < rates_total; i++)
   {
      BuyBuffer[i] = EMPTY_VALUE;
      SellBuffer[i] = EMPTY_VALUE;
   }

   // Bar 0 is deliberately left without a signal.
   for(int i = rates_total - 3; i >= 1; i--)
   {
      if(close[i] > close[i + 1] && close[i + 1] <= close[i + 2])
         BuyBuffer[i] = low[i] - (high[i] - low[i]) * 0.2;

      if(close[i] < close[i + 1] && close[i + 1] >= close[i + 2])
         SellBuffer[i] = high[i] + (high[i] - low[i]) * 0.2;
   }

   return rates_total;
}

Because the buffers and price arrays use series indexing, index 0 is the current candle, index 1 is the last closed candle, and larger indexes refer to older candles. The loop stops at index 1, so it never writes a signal to the live candle. It uses index 2 as well, which is why it requires at least three bars.

The signal for a candle can be plotted only once its close is available. In live use, that usually means the terminal processes a tick after the candle boundary; the closed candle then appears at index 1. This trades early intrabar notification for a condition based on completed candle data.

This example recalculates and clears the full buffers on each call to keep the indexing clear. For a large indicator, optimize the calculation using prev_calculated and the newly available bars, while still handling the most recent closed bar and any history refresh correctly. The example prevents this particular rule from flickering on bar 0; it cannot make an algorithm non-repainting if that algorithm relies on future data or deliberately rewrites prior values.

Higher-timeframe and future-data pitfalls

A multi-timeframe indicator has two clocks: the chart timeframe and the timeframe used to calculate its source values. If a five-minute chart reads a moving average from the current hourly candle, that hourly value can change until the hourly candle closes. A five-minute arrow based on it may therefore change several times during the hour, even though the five-minute candle itself has closed.

For a signal that should be final, use a completed higher-timeframe candle. In a handle-based MQL5 indicator, CopyBuffer can retrieve a value at a chosen shift; shift 0 is the current source bar and may be unfinished, while a completed-bar calculation should select the appropriate earlier source bar. For historical mapping, do not apply one fixed shift to every chart bar: find the higher-timeframe bar corresponding to each chart-bar time, then select the latest source candle that was complete at that chart time. iBarShift can help locate the source bar, but the completion timing still has to be handled by the indicator’s logic.

Future-data calculations create a different trap. A centered moving average, a pivot requiring right-side candles, or a pattern that needs later bars cannot be confirmed at the original point in real time. If the code places the eventual result back on that earlier candle, the completed chart can imply earlier knowledge than the live calculation had. A fair test records when the condition became confirmed, not only where the indicator eventually draws it.

Some indicators are designed to show provisional values and then replace them as new information arrives. That can be useful for analysis, but it is not the same contract as a final, closed-bar signal. The key is to know whether the indicator promises a live estimate, a delayed confirmation, or an unchanging historical signal.

Test in live-style conditions

Use the same rule each time and keep a record. A repeatable checklist makes it easier to separate an intrabar change from a historical rewrite and to reproduce a result after changing a setting.

When source code is available, buffer logging can make comparisons more precise than screenshots. Record the bar time alongside the value: buffer index 10 on one chart load may not identify the same candle after new history is added, but a candle’s timestamp gives you a stable reference for that comparison.

A clean compile only tells you that MetaEditor accepted the code; it does not prove the signal timing or historical behavior. Past or tested results also do not guarantee future results. Treat this as a calculation and verification task, separate from judging whether the signal is useful for a trading decision.

Mistakes to avoid when choosing or coding an indicator

Do not rely on a product label such as “no repaint” without defining what it means. Ask whether signals can change before the candle closes, whether old arrows can be backfilled after confirmation, and whether higher-timeframe values are taken from completed bars. Then run the same reload and forward-observation checks on the exact settings you plan to use.

Do not mistake a delayed signal for an instant signal that has failed. A fractal or pivot may need several later candles before confirmation; the delay is part of its rule. The misleading part is showing a confirmed signal on an earlier bar without making clear that it was not available there in real time.

For custom work, specify the finalization rule before writing the indicator: for example, “evaluate at the close of each chart candle and never move a signal after it is confirmed.” The FXNook AI Indicator Builder can build MT4 or MT5 indicators with buffers and arrows, including signals on closed bars or the live bar. Whatever tool you use, test the finished indicator against that stated rule rather than relying on its name or a polished historical chart.

The practical next step is to select a few closed bars, record their timestamps and buffer values, and run the reload, restart, and forward checks above. If you are building an indicator, write down exactly when each signal becomes final first; that definition is what makes the code and the test meaningful.

Questions

Can an MT5 indicator repaint after a candle closes?

Yes. An indicator can change a closed-bar signal if it recalculates earlier buffers, uses future bars, or depends on a higher-timeframe candle that was unfinished when the signal appeared. A closed candle alone does not guarantee that every value drawn on it is permanent.

How do I test whether an MT5 indicator repaints?

Record signals and buffer values for specific closed-bar timestamps, then reload the indicator and terminal and compare those same bars with unchanged settings. Also observe new candles as they close, because a static chart cannot show when an arrow first appeared or whether it moved.

Does using bar 1 instead of bar 0 prevent repainting?

Using bar 1 avoids basing the signal on the current unfinished candle, so it can prevent intrabar flicker for that rule. It does not prevent changes caused by future-data logic, higher-timeframe mapping, buffer backfilling, or revised price history.

Do higher-timeframe indicators repaint in MT5?

They can appear to repaint when they read values from the current, unfinished higher-timeframe candle, since that candle’s values can change before it closes. A final signal should use the correct completed higher-timeframe bar for each chart-bar time and be tested as new data arrives.