Why your indicator repaints: the OnCalculate array order nobody checks

Most of the repainting complaints I see in this section come down to one assumption, not to the detection logic or the buffer handling. MetaTrader hands OnCalculate its price arrays oldest to newest. Not series, not reversed, not newest first. Index 0 is the oldest bar currently loaded, and index rates_total - 1 is the bar that is still forming. That last one is the problem. It is still moving. On every tick you get a different high and a different low, and both will change again before the bar closes. So the rule that prevents repainting is short: Never derive a permanent value from index rates_total - 1. If you want a gap, a swing point, a breakout level or an order block to exist on the chart and stay there, the decision to create it has to depend only on bars at rates_total - 2 and older. Three consequences that are easy to get wrong: 1. ArraySetAsSeries does not fix this. It applies to arrays you declare and copy yourself. The arrays the terminal passes in are already ordered this way, and reversing them causes confusion rather than fixing anything. 2. If some part of your logic genuinely needs the forming bar, split the work. Detection reads closed bars only. Presentation and provisional warnings may read the forming bar. The state change waits for the close. Keeping those two paths separate is what lets an indicator react to live price without rewriting its own history. 3. Prove it rather than assuming it. On attach, re-derive your objects from scratch using only closed bars, then compare them against what you actually hold within a tolerance. If a level moved, you have found the bug before your users do. The uncomfortable part is that a repainting indicator usually looks perfect in the tester and on a history screenshot. It only misbehaves while you are watching. If your levels look stable while the bar is still forming, that is the defect, not a feature. I would be interested in what patterns people are seeing in practice, especially any case where tester behaviour disagrees with live behaviour for reasons other than spread or slippage.
Jevon Naldo Yoshield:
MetaTrader hands OnCalculate its price arrays oldest to newest.

That has always been the case for the default time series of data in MT5 (for the last 20 something years).

Jevon Naldo Yoshield:
Never derive a permanent value from index rates_total - 1.

If you want to use rates_total - 1 in lieu of rates_total, simply copy the prices, etc. into arrays and set them as series.

If you want all of the data set as series without copying to arrays, you can use the following code inserted at the top of your indicator/EA:

//--- store price, time, and volume data for access as in MQL4
#define SERIES(name,type)                               \
class C##name                                           \
{                                                       \
public:                                                 \
type operator[](const int i){return i##name(NULL,0,i);} \
}name;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
SERIES(Open,double)
SERIES(Low,double)
SERIES(High,double)
SERIES(Close,double)
SERIES(Time,datetime)
SERIES(Volume,long)
SERIES(TickVolume,long)

Note that in an indicator, you'll have to return rates_total -1 from OnCalculate().

Jevon Naldo Yoshield:
Most of the repainting complaints I see in this section come down to one assumption, not to the detection logic or the buffer handling. MetaTrader hands OnCalculate its price arrays oldest to newest. Not series, not reversed, not newest first. Index 0 is the oldest bar currently loaded, and index rates_total - 1 is the bar that is still forming. That last one is the problem. It is still moving. On every tick you get a different high and a different low, and both will change again before the bar closes. So the rule that prevents repainting is short: Never derive a permanent value from index rates_total - 1. If you want a gap, a swing point, a breakout level or an order block to exist on the chart and stay there, the decision to create it has to depend only on bars at rates_total - 2 and older. Three consequences that are easy to get wrong: 1. ArraySetAsSeries does not fix this. It applies to arrays you declare and copy yourself. The arrays the terminal passes in are already ordered this way, and reversing them causes confusion rather than fixing anything. 2. If some part of your logic genuinely needs the forming bar, split the work. Detection reads closed bars only. Presentation and provisional warnings may read the forming bar. The state change waits for the close. Keeping those two paths separate is what lets an indicator react to live price without rewriting its own history. 3. Prove it rather than assuming it. On attach, re-derive your objects from scratch using only closed bars, then compare them against what you actually hold within a tolerance. If a level moved, you have found the bug before your users do. The uncomfortable part is that a repainting indicator usually looks perfect in the tester and on a history screenshot. It only misbehaves while you are watching. If your levels look stable while the bar is still forming, that is the defect, not a feature. I would be interested in what patterns people are seeing in practice, especially any case where tester behaviour disagrees with live behaviour for reasons other than spread or slippage.

That's plain wrong. A repainting indicator is not about the current open bar (whatever the indexing used to access its data).

Forum on trading, automated trading systems and testing trading strategies

Repainting Articles

Alain Verleyen, 2013.07.09 14:20

No, in this case this is no repainting. Of course some people can call this repainting if they want, but there is no problem to trade with such indicator. You have to decide if you trade on closed bars only or with open bar / indicator.

An indicator who change the values of closed bars is another matter, they are calculated on future price. It's very problematic and I don't see how this can be useful, this is why I asked to newdigital to develop his idea to use such indicator as filter. I am very sceptic about that.


Jevon Naldo Yoshield:
Index 0 is the oldest bar currently loaded, and index rates_total - 1 is the bar that is still forming. That last one is the problem. It is still moving. On every tick you get a different high and a different low, and both will change again before the bar closes.

People who've backtested (and who understand OHLC) commonly already know this. People who just get AI agents to do everything (without learning about anything) are probably your only audience, but even then, they probably won't pay attention to what you've written.