Repainting
Introduction
Repainting is defined as a script behavior where historical and real-time calculations or plots behave differently.
Repainting behavior is quite common and can be caused by a variety of factors. According to our definition, it’s estimated that over 95% of existing indicators exhibit some form of repainting behavior. Indicators like MACD and RSI, for instance, display confirmed values on historical bars but will fluctuate on an unconfirmed real-time chart bar until it closes. Thus, their behavior differs between historical and real-time states.
Not all repainting behavior is inherently problematic or misleading, nor does it hinder experienced traders from using such indicators. For example, a volume profile indicator that updates its values on real-time bars is still useful.
Types of Repainting:
-
Widespread but often acceptable:
- A script may use values that update with real-time price changes on an unconfirmed bar. For example, calculations using the
close
variable on an open bar will reflect the most recent price but won’t commit a new data point to the historical series until the bar closes.
- A script may use values that update with real-time price changes on an unconfirmed bar. For example, calculations using the
-
Potentially misleading:
- Scripts that plot values into the past, calculate results on real-time bars that cannot be replicated on historical bars, or relocate past events may be misleading. Examples include:
- Ichimoku, pivot-based scripts,
intra
,timenow
, and certainbarstate.*
variables.
- Ichimoku, pivot-based scripts,
- These scripts may behave differently between real-time and historical data, making them potentially misleading.
- Scripts that plot values into the past, calculate results on real-time bars that cannot be replicated on historical bars, or relocate past events may be misleading. Examples include:
-
Unavoidable:
- Revisions of data feeds from a provider and changes in the starting bar of a chart’s history can cause repainting behavior that’s beyond the script’s control.
Understanding Repainting
The first two types of repainting are generally acceptable if:
- You are aware of the behavior.
- You are comfortable with it, or
- You can find ways to circumvent it.
It’s clear that not all repainting behavior is inherently harmful or needs to be avoided. In many cases, repainting may be exactly what a script needs. What’s crucial is knowing when repainting behavior is unacceptable for your specific use case. To avoid unacceptable repainting, you need to understand how a tool works or how to design your tools appropriately. If you publish scripts, make sure to highlight any potentially misleading behaviors and limitations in your script’s description.
**Notice - We will not discuss the potential issues of using strategies on non-standard charts, as this problem is unrelated to repainting. For more information on this topic, refer to the *Backtesting on Non-Standard Charts: Caution!* script for a detailed discussion of the subject.
For Script Users
You can choose to use repainting indicators if you understand the behavior and whether that behavior meets your analysis requirements. Avoid simply labeling a script as “repainting” in an attempt to discredit it, as doing so reveals a lack of foundational knowledge on the subject.
Asking whether a script repaints is generally meaningless, given that some forms of repainting behavior are perfectly acceptable in a script. Instead, ask specific questions about a script’s potential repainting behavior, such as:
- Does the script calculate/display in the same way on historical and realtime bars?
- Do signal markers shown by the script wait for the end of a realtime bar before showing?
- Does the script plot/draw values into the past?
What’s most important is understanding how the tools you use work and whether their behavior aligns with your objectives, whether they repaint or not. As you will learn from this page, repainting is a complex subject. It has many causes and manifestations. Even if you don’t program in Lipi Script, this page will help you understand the various factors that lead to repainting and enable more meaningful discussions with script authors.
For Lipi Script Programmers
As discussed earlier, not all forms of repainting behavior must be avoided at all costs, nor is all potential repainting behavior necessarily avoidable. This page aims to help you better understand the dynamics at play, so you can design your trading tools with these behaviors in mind. The content should help you become aware of common coding mistakes that can produce misleading repainting results.
Whatever design decisions you make, if you publish your script, be sure to explain its behavior to traders so they understand how it works.
This page covers three broad categories of repainting causes:
- Historical vs realtime calculations
- Plotting in the past
- Dataset variations
Historical vs Realtime Calculations
Fluid Data Values
Historical data does not include records of intermediary price movements on bars; it only includes open, high, low, and close values (OHLC).
However, on realtime bars (bars that are being generated when the instrument’s market is open), the high, low, and close values are not fixed; they can change many times before the realtime bar closes, at which point the HLC values are fixed. These values are fluid. This leads to a situation where a script may behave differently on historical data compared to real-time data, as only the open price remains constant during the bar.
Any script that uses values such as high, low, and close in realtime is subject to calculations that may not be repeatable on historical bars, resulting in repainting.
Consider the following simple script: It detects crosses of the close value (which in the realtime bar corresponds to the current price of the instrument) over and under an EMA:
indicator("Repainting", "", true);
ma = talib.ema(close, 5);
xUp = talib.crossover(close, ma);
xDn = talib.crossunder(close, ma);
plot(ma, "MA", color.black, 2);
bgcolor(
xUp ? color.new(color.lime, 80) : xDn ? color.new(color.red, 80) : na
);
Note:
Example of Repainting Behavior
The script uses bgcolor()
to color the background green when the close price crosses over the EMA and red when it crosses under the EMA.
In the screenshot, we see the script running in realtime on a 30-second chart. A crossover of the EMA has been detected, so the background of the current realtime bar is green.
However, there is a key issue: nothing guarantees that the condition will hold true until the end of the realtime bar. The arrow points to a timer showing that 21 seconds remain in the bar, and anything could happen during this time.
This is an example of a repainting script. The condition might be true now but could change by the time the bar closes.
Solution to Prevent Repainting
To prevent repainting, we need to rewrite the script to avoid using values that fluctuate during the realtime bar. This can be achieved by using values from a bar that has already closed (typically the preceding bar), or by using the open price, which remains fixed during the bar’s formation.
One simple method to fix this is to add a condition that checks barstate.isconfirmed
. This ensures that the script executes on the last iteration of the bar, when the bar closes and the prices are confirmed. This approach will help avoid repainting behavior.
indicator("Repainting", "", true)
ma = talib.ema(close, 5)
xUp = talib.crossover(close, ma)[1]
xDn = talib.crossunder(close, ma)[1]
plot(ma, "MA", color.black, 2)
bgcolor(xUp ? color.new(color.lime, 0.2) : xDn ? color.new(color.green, 0.2) : na)
This uses the crosses detected on the previous bar:
indicator("Repainting", "", true)
ma = talib.ema(close[1], 5)
xUp = talib.crossover(close[1], ma)
xDn = talib.crossunder(close[1], ma)
plot(ma, "MA", color.black, 2)
bgcolor(xUp ? color.new(color.lime, 0.2) : xDn ? color.new(color.yellow, 0.2) : na)
This uses only confirmed close and EMA values for its calculations:
indicator("Repainting", "", true)
ma = talib.ema(close, 5)
xUp = talib.crossover(open, ma[1])
xDn = talib.crossunder(open, ma[1])
plot(ma, "MA", color.black, 2)
bgcolor(xUp ? color.new(color.lime, 0.2) : xDn ? color.new(color.yellow, 0.2) : na)
Detecting Crosses with Confirmed Values
Issue with Repainting
In this case, the script detects crosses between the realtime bar’s open price and the value of the EMA from previous bars. The issue here is that the EMA is calculated using the close price, which can fluctuate during the formation of the realtime bar, leading to repainting behavior.
Solution to Avoid Repainting
To avoid repainting, we must use a confirmed value for detecting crosses. In this case, the solution is to use ma[1]
in the cross detection logic. By referencing ma[1]
, we ensure that we are comparing the open price of the current bar with a value of the EMA that has already been confirmed, preventing any repainting.
This way, we avoid using the EMA value that is still subject to change during the realtime bar’s formation.
indicator("Repainting", "", true)
ma = talib.ema(close, 5)
xUp = talib.crossover(close, ma)[1]
xDn = talib.crossunder(close, ma)[1]
plot(ma, "MA", color.black, 2)
bgcolor(xUp ? color.new(color.lime, 0.2) : xDn ? color.new(color.yellow, 0.2) : na)
All methods used to prevent repainting have one thing in common: while they ensure that repainting does not occur, they will also trigger signals later than repainting scripts. This delay in signal generation is an inevitable compromise for traders and programmers who wish to avoid repainting.
In other words, you cannot have both real-time accuracy in signals and the elimination of repainting at the same time. This is the trade-off that must be made when designing scripts that aim to provide reliable, non-repainting signals.
intra
Scripts using the intra
declaration mode for variables (see our section on intra
for more information) retain information across realtime updates. This behavior cannot be replicated on historical bars, as they only contain OHLC (Open, High, Low, Close) data. While such scripts are useful in realtime, including for generating alerts, their logic cannot be backtested. Moreover, their plots on historical bars will not reflect the calculations that occur in realtime.
Bar state built-ins
Scripts utilizing bar states may or may not exhibit repainting behavior. As discussed previously, using barstate.isconfirmed is a way to prevent repainting, ensuring the behavior reproduces on historical bars (which are always confirmed). However, using other bar states like barstate.isnew can lead to repainting. This happens because on historical bars, barstate.isnew is true at the close of the bar, while in realtime, it is true at the open of the bar. Consequently, using other bar state variables typically causes discrepancies in behavior between historical and realtime bars.
timenow
The timenow
built-in variable returns the current time. Scripts that use timenow
cannot maintain consistent behavior across historical and realtime bars, leading to inevitable repainting.