Every serious trader hits the same wall: you want RSI in one pane, volume in another, and moving averages on the price chart โ but TradingView limits how many indicators you can load (3 on the free plan, as of 2026-04). The solution? Build one Pine Script indicator that outputs to multiple chart panels simultaneously.
This tutorial walks through every technique available in Pine Script v5 and v6 to create multi-panel layouts from a single script. By the end, you'll have a working "Swiss Army Knife" indicator that plots across the main chart and its own separate pane at the same time.
> Try TradingView โ the platform used throughout this guide.
> Need more indicator slots? Upgrade your TradingView plan to unlock up to 25 indicators per chart (as of 2026-04) โ essential for complex multi-panel setups.
The Problem: One Indicator, One Pane?
By default, a Pine Script indicator lives in exactly one place:
overlay = trueโ plots on the main price chartoverlay = falseโ plots in a separate pane below the chart
//@version=6
indicator("My Indicator", overlay = false) // separate pane only
plot(ta.rsi(close, 14), "RSI")
This means if you want RSI below *and* an EMA on the price chart, you traditionally needed two separate indicators โ eating into your indicator limit.
That changed with force_overlay.
Step 1: Understanding force_overlay โ The Key to Multi-Panel Output
Introduced in mid-2024 and available in both Pine Script v5 and v6, force_overlay lets individual plot elements display on the main chart pane even when the script itself occupies a separate pane.
Here's the core concept:
//@version=6
indicator("Multi-Panel Demo", overlay = false)
// This plots in the indicator's own pane (below the chart)
plot(ta.rsi(close, 14), "RSI", color.purple)
// This plots on the MAIN price chart, despite overlay = false
plot(ta.ema(close, 21), "EMA 21", color.orange, force_overlay = true)
What's happening:
- The indicator lives in its own pane (
overlay = false) - RSI renders in that pane normally
- The EMA "escapes" to the main chart via
force_overlay = true
Which Functions Support force_overlay?
Almost every visual function in Pine Script supports it:
| Function | force_overlay Support |
|---|---|
plot() | โ |
plotshape() | โ |
plotchar() | โ |
plotarrow() | โ |
plotcandle() | โ |
plotbar() | โ |
bgcolor() | โ |
hline() | โ |
fill() | โ |
label.new() | โ |
line.new() | โ |
box.new() | โ |
table.new() | โ |
Step 2: Build a Combined RSI + EMA + Volume Indicator
Let's build something practical โ a single indicator that shows:
1. Main chart: EMA crossover lines + buy/sell signals
2. Separate pane: RSI with overbought/oversold zones 3. Separate pane: Volume (normalized) in the same pane as RSI
//@version=6
indicator("Multi-Panel Swiss Knife", overlay = false)
// โโโ INPUTS โโโ
int rsiLen = input.int(14, "RSI Length")
int emaFast = input.int(9, "Fast EMA")
int emaSlow = input.int(21, "Slow EMA")
// โโโ CALCULATIONS โโโ
float rsiVal = ta.rsi(close, rsiLen)
float emaF = ta.ema(close, emaFast)
float emaS = ta.ema(close, emaSlow)
bool bullCross = ta.crossover(emaF, emaS)
bool bearCross = ta.crossunder(emaF, emaS)
// โโโ PANE: RSI (this indicator's own pane) โโโ
plot(rsiVal, "RSI", color.new(color.purple, 0), 2)
hline(70, "Overbought", color.red, hline.style_dashed)
hline(30, "Oversold", color.green, hline.style_dashed)
hline(50, "Midline", color.gray, hline.style_dotted)
// Color the RSI pane background on extremes
bgcolor(rsiVal > 70 ? color.new(color.red, 90) : rsiVal < 30 ? color.new(color.green, 90) : na)
// โโโ MAIN CHART: EMA lines (force_overlay) โโโ
plot(emaF, "Fast EMA", color.new(color.blue, 0), 2, force_overlay = true)
plot(emaS, "Slow EMA", color.new(color.orange, 0), 2, force_overlay = true)
// โโโ MAIN CHART: Buy/Sell signals (force_overlay) โโโ
plotshape(bullCross, "Buy Signal", shape.triangleup,
location.belowbar, color.green, size = size.small,
force_overlay = true)
plotshape(bearCross, "Sell Signal", shape.triangledown,
location.abovebar, color.red, size = size.small,
force_overlay = true)
// โโโ MAIN CHART: Highlight background on crossover โโโ
bgcolor(bullCross ? color.new(color.green, 85) : bearCross ? color.new(color.red, 85) : na,
force_overlay = true)
Result: One indicator uses one slot, but you see:
Like what you're reading? Try it yourself โ this link supports ChartedTrader at no cost to you.
Get TradingView Pro โ unlock more indicators per chart โ- RSI oscillator in its own pane below
- EMA lines rendered directly on the price chart
- Buy/sell arrows on the price candles
Step 3: Add a Multi-Symbol Comparison Panel
Want to compare multiple assets in the same pane? Use request.security() to pull data from other symbols:
//@version=6
indicator("Multi-Symbol RSI Comparison", overlay = false)
// โโโ INPUTS โโโ
string sym1 = input.symbol("BINANCE:BTCUSDT", "Symbol 1")
string sym2 = input.symbol("BINANCE:ETHUSDT", "Symbol 2")
string sym3 = input.symbol("BINANCE:SOLUSDT", "Symbol 3")
int len = input.int(14, "RSI Length")
// โโโ FETCH RSI FROM EACH SYMBOL โโโ
float rsi1 = request.security(sym1, timeframe.period, ta.rsi(close, len))
float rsi2 = request.security(sym2, timeframe.period, ta.rsi(close, len))
float rsi3 = request.security(sym3, timeframe.period, ta.rsi(close, len))
// โโโ PLOT ALL IN ONE PANE โโโ
plot(rsi1, "BTC RSI", color.orange, 2)
plot(rsi2, "ETH RSI", color.blue, 2)
plot(rsi3, "SOL RSI", color.purple, 2)
hline(70, "OB", color.red, hline.style_dashed)
hline(30, "OS", color.green, hline.style_dashed)
// โโโ MAIN CHART: Label current values โโโ
var table infoTable = table.new(position.top_right, 3, 2,
bgcolor = color.new(color.black, 70),
force_overlay = true)
if barstate.islast
table.cell(infoTable, 0, 0, "BTC RSI", text_color = color.orange)
table.cell(infoTable, 1, 0, "ETH RSI", text_color = color.blue)
table.cell(infoTable, 2, 0, "SOL RSI", text_color = color.purple)
table.cell(infoTable, 0, 1, str.tostring(rsi1, "#.0"), text_color = color.white)
table.cell(infoTable, 1, 1, str.tostring(rsi2, "#.0"), text_color = color.white)
table.cell(infoTable, 2, 1, str.tostring(rsi3, "#.0"), text_color = color.white)
This gives you a single pane comparing RSI across three symbols, plus a floating info table on the main chart.
Step 4: Simulate Multiple Sub-Panes with Visual Zones
Pine Script doesn't support creating multiple separate panes from one indicator. But you can visually divide a single pane into zones using hline() boundaries and scaled data:
//@version=6
indicator("Dual Zone Panel", overlay = false)
// โโโ RSI: Scaled to upper zone (50โ100 range) โโโ
float rsiRaw = ta.rsi(close, 14)
float rsiScaled = 50 + (rsiRaw / 100) * 50 // Maps 0โ100 โ 50โ100
// โโโ Stochastic: Scaled to lower zone (0โ50 range) โโโ
float stochRaw = ta.stoch(close, high, low, 14)
float stochScaled = (stochRaw / 100) * 50 // Maps 0โ100 โ 0โ50
// โโโ Visual separator โโโ
hline(50, "โโโ Separator โโโ", color.new(color.white, 30), hline.style_solid)
// โโโ Upper zone: RSI โโโ
plot(rsiScaled, "RSI", color.purple, 2)
hline(85, "RSI OB", color.new(color.red, 60), hline.style_dotted)
hline(65, "RSI OS", color.new(color.green, 60), hline.style_dotted)
// โโโ Lower zone: Stochastic โโโ
plot(stochScaled, "Stoch %K", color.teal, 2)
hline(40, "Stoch OB", color.new(color.red, 60), hline.style_dotted)
hline(10, "Stoch OS", color.new(color.green, 60), hline.style_dotted)
// โโโ Labels for clarity โโโ
var label rsiLabel = label.new(na, na, "RSI Zone โ", style = label.style_none,
textcolor = color.purple, size = size.small)
var label stochLabel = label.new(na, na, "Stoch Zone โ", style = label.style_none,
textcolor = color.teal, size = size.small)
if barstate.islast
label.set_xy(rsiLabel, bar_index + 3, 90)
label.set_xy(stochLabel, bar_index + 3, 25)
Result: One pane, two visually separated zones โ RSI on top, Stochastic on the bottom, divided by a line at 50.
Step 5: The Full Dashboard โ Putting It All Together
Here's a production-ready indicator that combines everything:
//@version=6
indicator("๐ Multi-Chart Dashboard", overlay = false, max_labels_count = 50)
// โโโ INPUTS โโโ
int rsiLen = input.int(14, "RSI Length", group = "Oscillators")
int stochLen = input.int(14, "Stoch Length", group = "Oscillators")
int emaFast = input.int(9, "Fast EMA", group = "Trend")
int emaSlow = input.int(21, "Slow EMA", group = "Trend")
string sym2 = input.symbol("", "Compare Symbol", group = "Multi-Symbol")
// โโโ CALCULATIONS โโโ
float rsiVal = ta.rsi(close, rsiLen)
float stochK = ta.stoch(close, high, low, stochLen)
float stochD = ta.sma(stochK, 3)
float emaF = ta.ema(close, emaFast)
float emaS = ta.ema(close, emaSlow)
bool bullX = ta.crossover(emaF, emaS)
bool bearX = ta.crossunder(emaF, emaS)
// โโโ PANE: RSI (upper zone, 50โ100) โโโ
float rsiDisplay = 50 + (rsiVal / 100) * 50
plot(rsiDisplay, "RSI", color.new(color.purple, 0), 2)
// โโโ PANE: Stochastic (lower zone, 0โ50) โโโ
float stochDisplay = (stochK / 100) * 50
float stochDDisplay = (stochD / 100) * 50
plot(stochDisplay, "%K", color.new(color.teal, 0), 2)
plot(stochDDisplay, "%D", color.new(color.orange, 0), 1, plot.style_line)
// โโโ PANE: Zone separator and levels โโโ
hline(50, "โโโโโโโโโโโ", color.new(color.white, 40), hline.style_solid)
hline(85, "RSI OB", color.new(color.red, 70), hline.style_dotted)
hline(65, "RSI OS", color.new(color.green, 70), hline.style_dotted)
hline(40, "Stoch OB", color.new(color.red, 70), hline.style_dotted)
hline(10, "Stoch OS", color.new(color.green, 70), hline.style_dotted)
// โโโ MAIN CHART: EMAs โโโ
plot(emaF, "Fast EMA", color.new(color.blue, 0), 2, force_overlay = true)
plot(emaS, "Slow EMA", color.new(color.orange, 0), 2, force_overlay = true)
// โโโ MAIN CHART: Signals โโโ
plotshape(bullX, "Buy", shape.triangleup, location.belowbar,
color.green, size = size.small, force_overlay = true)
plotshape(bearX, "Sell", shape.triangledown, location.abovebar,
color.red, size = size.small, force_overlay = true)
// โโโ MAIN CHART: Info table โโโ
var table dash = table.new(position.top_right, 2, 4,
bgcolor = color.new(color.black, 75), border_width = 1,
border_color = color.new(color.gray, 60),
force_overlay = true)
if barstate.islast
table.cell(dash, 0, 0, "RSI", text_color = color.purple, text_size = size.small)
table.cell(dash, 1, 0, str.tostring(rsiVal, "#.0"),
text_color = rsiVal > 70 ? color.red : rsiVal < 30 ? color.green : color.white,
text_size = size.small)
table.cell(dash, 0, 1, "Stoch %K", text_color = color.teal, text_size = size.small)
table.cell(dash, 1, 1, str.tostring(stochK, "#.0"), text_color = color.white,
text_size = size.small)
table.cell(dash, 0, 2, "EMA Trend", text_color = color.blue, text_size = size.small)
table.cell(dash, 1, 2, emaF > emaS ? "โฒ Bullish" : "โผ Bearish",
text_color = emaF > emaS ? color.green : color.red,
text_size = size.small)
// Optional second symbol comparison
if sym2 != ""
float sym2Close = request.security(sym2, timeframe.period, close)
float sym2Rsi = request.security(sym2, timeframe.period, ta.rsi(close, rsiLen))
table.cell(dash, 0, 3, sym2, text_color = color.yellow, text_size = size.small)
table.cell(dash, 1, 3, str.tostring(sym2Rsi, "#.0"),
text_color = color.white, text_size = size.small)
v5 vs v6: What Changed for Multi-Chart Layouts?
| Feature | Pine Script v5 | Pine Script v6 |
|---|---|---|
force_overlay | โ Available | โ Available |
request.security() | โ | โ (same behavior) |
request.footprint() | โ | โ New in Jan 2026 |
| Line wrapping | Strict 4-space rules | โ Relaxed (Dec 2025) |
| AI code assistant | โ | โ Built-in |
| Migration required? | No | Recommended for new features |
force_overlay works in both v5 and v6. If you're writing new code, use v6 โ future features will only land there. If you have existing v5 scripts, they'll keep working without changes.
Tips and Limitations
What You Can Do
- โ
Plot on the main chart from a pane indicator (
force_overlay = true) - โ Draw labels, lines, boxes, and tables on the main chart from a pane
- โ
Compare multiple symbols in one pane using
request.security() - โ Visually split a single pane into zones with scaled data
What You Can't Do (Yet)
- โ Create multiple *separate* panes from one indicator
- โ Control pane ordering or height from Pine Script
- โ Use
force_overlayto plot on *another indicator's* pane - โ Plot from an overlay indicator into a separate pane (only pane โ main chart works)
Performance Tips
- As of 2026-04, each
request.security()call counts toward TradingView's limit of 40 per script force_overlayplots consume the same resources as regular plots- Use
max_labels_countandmax_lines_countto avoid memory limits - Group related inputs using the
groupparameter for cleaner Settings UI
Conclusion
The force_overlay parameter is the single most important feature for building multi-panel Pine Script indicators. Combined with data scaling for visual sub-zones and request.security() for multi-symbol data, you can pack a full trading dashboard into one indicator slot.
This matters most on TradingView's free plan (3 indicators, as of 2026-04) but is useful even on paid plans โ fewer indicators means faster chart loading and a cleaner workspace.
Ready to build your own multi-panel indicators? Start your free TradingView trial to get access to the Pine Script editor, more indicator slots, and real-time data across global markets.---
*This tutorial uses Pine Script v6 syntax. All force_overlay examples are backward-compatible with v5 โ just change the version comment to //@version=5.*
---
*Affiliate Disclosure: This article contains affiliate links. If you sign up through these links, I may earn a commission at no extra cost to you. I only recommend products and services I personally use and trust. All opinions are my own.*
---
Want to build multi-chart layouts like this? Try TradingView โ it is the platform used for every example in this guide.