Tutorial

Pine Script barcolor and bgcolor: Color Bars by Condition

Learn how barcolor and bgcolor work in Pine Script v6, with complete examples for trend bars, zone shading, sessions, and flicker-free layers.

•9 min read

You have an indicator that already computes something useful, maybe a trend filter, an RSI zone, or a volume spike. The numbers are in the Data Window, but you want to see the state of the market at a glance, without squinting at a line that crosses another line. The usual answer is color: paint the candles when momentum is strong, or shade the chart background when a filter is active. In Pine Script that job belongs to two small functions, barcolor() and bgcolor().

They are simple to call and easy to misuse. People hit a wall of solid color that hides the candles, a background that flickers on every bar, or a color rule that quietly fights with another indicator on the chart. This guide walks through how both functions behave, how to build clean conditions for them, and how to layer several rules so the chart stays readable.

What barcolor and bgcolor actually do

Both functions work per bar. On every bar the script runs, you hand them a color, and TradingView applies it to that bar's candle (barcolor) or to that bar's slice of the chart background (bgcolor). Neither function draws a new object. They recolor something that already exists, which is why they cost almost nothing compared with labels or boxes.

The signatures you will use most are short. barcolor(color, offset, editable, show_last, title, display) recolors the main series candles. bgcolor(color, offset, editable, show_last, title, display, force_overlay) shades the background. The first argument is a series color, so it can change from bar to bar. That is the whole trick: you pass a ternary expression that returns a color on bars you care about and na on the rest.

One detail matters before any code. Both functions are global-scope calls. You cannot place them inside an if block or a function and expect them to render conditionally the way you might in other languages. The condition lives inside the color argument, and the call itself sits at the top level of the script. This is the same rule that produces the "cannot use plot in local scope" error, which we cover in Cannot Use plot in Local Scope.

Your first barcolor: color bars by a condition

Start with the smallest useful example. We color a candle green when the close is above a moving average and the average is rising, red when the close is below it and the average is falling, and leave every other bar alone.

pine
//@version=6
indicator("Trend Bar Colors", overlay = true)

length = input.int(50, "MA length", minval = 2)
ma = ta.ema(close, length)

bullish = close > ma and ma > ma[1]
bearish = close < ma and ma < ma[1]

barColor = bullish ? color.new(color.teal, 0) : bearish ? color.new(color.orange, 0) : na

plot(ma, "EMA", color = color.gray)
barcolor(barColor, title = "Trend bars")

Read the color line carefully. The nested ternary returns one of three things: a teal, an orange, or na. Passing na to barcolor means "do not recolor this bar", so the candle keeps whatever colors the chart style or your TradingView settings give it. That is how you recolor only the bars that matter.

The two flags bullish and bearish are defined as named booleans before the color is chosen. That is not decoration. When the rule grows to five conditions, you will want to read the logic and the colors separately. If you want more control over averages, see Pine Script Moving Average Types for the differences between SMA, EMA, RMA and WMA.

Making barcolor visible: the chart settings trap

A common complaint is "my barcolor does nothing". Often the script is fine and the chart is the cause. barcolor recolors the candle body, border, and wick only when the chart's candle style allows it. If you have manually set a body, border, or wick color in the chart's Symbol settings, those manual choices can read as the same hues you are trying to apply. Open the chart settings, check the Symbol tab, and confirm the candle colors are the defaults.

Another cause is the overlay setting. barcolor always acts on the main series of the chart, so it does not matter whether your indicator is an overlay or not, but if the script is in a separate pane, the bars you recolor are still the ones on the main pane. That surprises people who expect the oscillator pane's own plot to change. The pane has no candles, so there is nothing for barcolor to recolor there.

bgcolor: shading the background

bgcolor is the right tool when the information is about a regime or a window rather than a single candle. A filter that is on, a session that is open, a volatility state, an RSI zone: all of those read better as a band of tint behind the candles than as colored bars.

pine
//@version=6
indicator("RSI Zone Background", overlay = true)

rsiLength = input.int(14, "RSI length", minval = 2)
upper = input.int(70, "Upper zone")
lower = input.int(30, "Lower zone")

rsi = ta.rsi(close, rsiLength)

hot = rsi >= upper
cold = rsi <= lower

bgcolor(hot ? color.new(color.red, 85) : na, title = "RSI high zone")
bgcolor(cold ? color.new(color.blue, 85) : na, title = "RSI low zone")

The second argument to color.new() is transparency from 0 to 100, where 100 is fully see-through. A value around 80 to 90 keeps the background present but quiet. If you leave the transparency at 0, the tint covers the candles and the chart becomes unreadable. When someone says a background shading "hides everything", they almost always used a solid color.

Two separate bgcolor calls are better here than one call with a three-way ternary. Each gets its own title, so it shows up as its own entry in the indicator's Style settings, and you can change or hide either zone without editing code.

Layering several rules without a mess

Real indicators rarely have one rule. You might want bars colored by trend, the background shaded by a volatility state, and a third effect for a volume spike. The way to keep that readable is to separate the layers by purpose.

Give each layer one job. Bars show the direction of the trend. The background shows a regime. Nothing else gets color. When two layers fight for the same visual channel, the reader loses both. If you need a third state, reach for a shape or a label instead of another background tint.

pine
//@version=6
indicator("Layered Context", overlay = true)

fastLen = input.int(20, "Fast EMA", minval = 2)
slowLen = input.int(50, "Slow EMA", minval = 2)
atrLen = input.int(14, "ATR length", minval = 1)
atrBaseLen = input.int(100, "ATR baseline", minval = 2)

fast = ta.ema(close, fastLen)
slow = ta.ema(close, slowLen)
atr = ta.atr(atrLen)
atrBase = ta.sma(atr, atrBaseLen)

trendUp = fast > slow
trendDown = fast < slow
quiet = atr < atrBase * 0.8
volatile = atr > atrBase * 1.5

barcolor(trendUp ? color.new(color.teal, 0) : trendDown ? color.new(color.orange, 0) : na, title = "Trend")
bgcolor(volatile ? color.new(color.purple, 88) : quiet ? color.new(color.gray, 92) : na, title = "Volatility state")

plot(fast, "Fast EMA", color = color.new(color.teal, 40))
plot(slow, "Slow EMA", color = color.new(color.orange, 40))

Notice that the two layers do not overlap in meaning. The trend lives on the candles. The volatility state lives behind them. A reader can take in both without decoding a legend. The thresholds are inputs or named constants you can tune, and they describe how the chart looks, not how any trade will turn out.

Why the background flickers, and how to stop it

If your background tint switches on and off every other bar, the condition is probably unstable rather than the color call being wrong. A threshold test like rsi > 70 flips whenever the value wobbles around 70. The fix is to give the state some memory, so that it turns on at one level and turns off at a different one.

pine
//@version=6
indicator("Sticky Zone Background", overlay = true)

rsi = ta.rsi(close, 14)

var bool inZone = false
if rsi > 70
    inZone := true
else if rsi < 55
    inZone := false

bgcolor(inZone ? color.new(color.red, 88) : na, title = "Sticky zone")

The var keyword keeps inZone between bars, and the two different thresholds create a band where the state does not change. The tint now stays on until RSI falls to 55. If var is unfamiliar, var, varip, and Persistent State in Pine Script explains how state survives from one bar to the next. Notice that the if block assigns a variable and the bgcolor call stays at the top level, which keeps the scope rule from earlier intact.

Real-time bars are the other source of flicker. On the live bar, the condition recomputes on every tick, so a color can appear and vanish before the bar closes. If you only want the tint on finished bars, combine the condition with barstate.isconfirmed, which we cover in barstate.isconfirmed in Pine Script. Colors that change after the fact are a form of repainting, and the five kinds are laid out in Repainting Explained.

Shading a session or a time window

Background color is also the classic way to mark a trading session. You build a boolean from the bar's time and shade while it is true.

pine
//@version=6
indicator("Session Shading", overlay = true)

sessionInput = input.session("0800-1600", "Session")
inSession = not na(time(timeframe.period, sessionInput))

bgcolor(inSession ? color.new(color.blue, 90) : na, title = "Session")

time() returns na when the bar falls outside the session string, so not na(...) is true only inside the window. Pass the timezone as a third argument if you need a specific one. The details of sessions and time filters, including the bugs that appear around midnight, are in Pine Script Session and Time Filters Without Bugs.

Color gradients versus on and off colors

Everything so far is binary: a bar is colored or it is not. Sometimes you want the strength of a reading to show up as intensity. For that you compute the color with color.from_gradient() and feed the result to the same two functions. The gradient math, including how to avoid washed-out ends, is covered in Pine Script Color Gradients. The point to carry over from here is that barcolor and bgcolor accept any series color, so a gradient is just a different way to produce the argument.

Limits and habits worth keeping

Bar and background coloring has few hard limits compared with drawing objects, which is a good reason to prefer it when a drawing is not necessary. The things that go wrong are mostly about clarity.

Keep transparency high on backgrounds, usually above 80. Give every call a title, because untitled calls become hard to tell apart in the Style tab. Do not color every bar. If more than about half the chart is painted, the color has stopped carrying information. Keep your thresholds in inputs so a reader can see why a bar is colored. And remember that another indicator on the same chart may also color bars, so if a color you applied does not show, check what else is running.

If you need an alert when the colored state begins, do not try to read the color back. Alert on the underlying boolean, using the pattern from alertcondition vs alert() in Pine Script.

Letting the AI write the coloring rule

Most of the work above is mechanical once the rule is clear: name the conditions, pick a layer for each, and keep the calls at the top level. The part that takes time is the iteration, such as changing a threshold, swapping the average type, or adding the volatility layer after you already have the trend bars.

That is where PineScripter's AI code editor helps. You describe the coloring rule in plain English, it writes Pine Script v6 and checks the code for compile errors the same way TradingView does, and a follow-up request edits only the lines that change instead of rewriting the script. You can then run the script on the built-in chart, which uses real market data from crypto spot markets, to see how the colors look before you paste it into TradingView. For a closer look at what your conditions did across past data, Indicator analysis lays out how often a condition triggered and when. It describes the past only and does not forecast anything.

Summary

barcolor recolors candles and bgcolor shades the background, both per bar, both taking a series color. Pass na to leave a bar untouched, keep the calls at the top level, give the condition a name, and use high transparency on backgrounds. Separate the layers by purpose so the chart can be read at a glance, add memory with var when a threshold makes the tint flicker, and use barstate.isconfirmed when you only want colors on closed bars.

Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.

Describe your indicator in plain English and get Pine Script that compiles →


Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.