Pine Script Overlay and Separate Pane in One Script
For a Pine Script overlay and separate pane in one script, declare overlay=false and add force_overlay=true to the price-scale plots. Bollinger Bands then draw on the candles while MACD stays in the sub-pane.
- overlay is set once in indicator() or strategy(), so a script has one base pane and cannot create several panes.
- Use overlay=false as the base and add force_overlay=true to every output that must sit on the candles.
- fill() only works between two plots that live in the same pane, so give both band plots and the fill the same force_overlay value.
- There is no way to do the reverse (overlay=true base with a few outputs forced into a sub-pane), so pick the base by which pane holds the non-price plots.
- Strategy order markers draw on the price chart regardless of the pane setting, and the pane never changes backtest results.
Why does one Pine script put everything in a single pane?
The pane is a property of the script declaration, not of each plot. indicator(overlay=true) or strategy(overlay=true) attaches the script to the price pane and shares the price scale. overlay=false (the default) gives the script its own pane with its own scale.
Every plot() call inherits that choice. A script cannot create a second or third pane of its own, so one script means at most the main chart plus one extra pane.
The practical symptom is scale distortion. A MACD line oscillates near 0 while price may sit at 20000, so overlay=true forces the autoscale to include both, and the MACD collapses into a flat line at the bottom. The reverse is also bad: Bollinger Bands in a pane of their own float in an empty scale detached from the candles.
How do I combine a Pine Script overlay and separate pane for Bollinger Bands and MACD?
Make the sub-pane the base (overlay=false) and push the price-scale outputs onto the main pane with force_overlay=true. This is the pattern that solves the usual strategy() case in one script.
The script below is a complete Pine v6 strategy. The trading rule is a placeholder to show where logic lives, not a recommendation.
//@version=6
strategy("BB + MACD panes", overlay=false, initial_capital=10000,
default_qty_type=strategy.percent_of_equity, default_qty_value=10)
len = input.int(20, "BB length", minval=1)
mult = input.float(2.0, "BB mult", minval=0.001, step=0.1)
fast = input.int(12, "MACD fast")
slow = input.int(26, "MACD slow")
sig = input.int(9, "MACD signal")
[basis, upper, lower] = ta.bb(close, len, mult)
[macdLine, signalLine, hist] = ta.macd(close, fast, slow, sig)
// Price-scale plots: drawn on the candles
pBasis = plot(basis, "Basis", color.orange, force_overlay=true)
pUpper = plot(upper, "Upper", color.teal, force_overlay=true)
pLower = plot(lower, "Lower", color.teal, force_overlay=true)
fill(pUpper, pLower, color.new(color.teal, 90), "BB fill", force_overlay=true)
// Oscillator plots: drawn in the script's own pane
plot(hist, "Histogram", hist >= 0 ? color.green : color.red, style=plot.style_columns)
plot(macdLine, "MACD", color.blue)
plot(signalLine, "Signal", color.orange)
hline(0, "Zero", color.gray)
// Placeholder logic only
if ta.crossover(macdLine, signalLine) and close < basis
strategy.entry("L", strategy.long)
if ta.crossunder(macdLine, signalLine)
strategy.close("L")How do I fill between the Bollinger Bands when the plots are forced onto the chart?
fill() takes two plot handles (or two hline handles), so you must store the plot() return values in variables, as pUpper and pLower above. The fill can only join two outputs that live in the same pane.
If the plots have force_overlay=true, the fill needs force_overlay=true as well, otherwise the fill is attached to the sub-pane and nothing sensible appears. If you mix a forced plot and a normal plot in one fill() call, the result is wrong or an error, so keep both ends consistent.
hline() is the exception to the force_overlay family. It has no such parameter and always draws in the script's own pane, so a horizontal level meant for the price chart (a fixed price) should be a plot(level, force_overlay=true) instead.
Which base pane should I choose: overlay=true or overlay=false?
force_overlay only moves outputs from a separate pane onto the price pane. There is no force_overlay=false that moves outputs the other way, so overlay=true with MACD in a sub-pane is not possible in one script.
That makes the choice mechanical: pick the pane that holds the non-price plots as the base, and force the price-scale ones onto the chart.
| Situation | Base setting | What to do |
|---|---|---|
| Mostly price-scale plots (MAs, bands, levels), no oscillator | overlay=true | Nothing special; no force_overlay needed |
| Price overlays plus one oscillator (MACD, RSI) | overlay=false | force_overlay=true on every price-scale output |
| Two or more oscillators with different scales | overlay=false | They share one pane; split into two scripts if scales clash |
| Indicator needing three or more distinct panes | Not possible in one script | One script per pane |
| Strategy where you only care about order markers | Either | Markers draw on the price chart regardless |
What changes for strategy() scripts?
Order fills, the entry and exit arrows, and trade labels are drawn on the main chart whatever the declaration says. With overlay=false the default order arrows therefore remain on the candles, which is why the pattern above works nicely for strategies.
The pane setting is purely visual. It does not alter signals, fills, equity or any Strategy Tester figure, because those depend on the bar data and order logic only. If results change after you flip overlay, something else changed, usually a plot or input edit that was bundled in.
Version matters. force_overlay exists in Pine v5 (added in a later v5 update) and v6. In v4 or older v5 builds it is rejected as an unknown argument, so migrate with the editor's converter or fall back to two scripts.
What is the two-script alternative and how do I link the scripts?
When you need more than one extra pane, or you must stay on an old version, run one script per pane: an indicator with overlay=true for the bands and another with overlay=false for MACD. Each script is calculated separately, so they cannot share variables, and any shared parameter (length, source) has to be set in both.
A strategy cannot read another script's plots by name. The supported link is input.source(), which lets a script pick a plot from another indicator on the chart as its source series. The indicator must expose the value as a plot, and the strategy then treats it like a series.
- In the indicator, plot the value to share, for example plot(macdLine, "MACD", display=display.data_window) so it stays out of the way.
- In the strategy, declare src = input.source(close, "External signal").
- Add both scripts to the same chart and, in the strategy's Inputs tab, choose the indicator's plot in the External signal dropdown.
- Use src in the strategy logic. Only one value per plot is passed, so share each series you need as its own plot.
Why are my plots in the wrong place, and how do I fix them?
Control visibility with the display parameter: display.pane shows the plot only in its pane, display.data_window or display.status_line shows only the number, display.price_scale shows only the scale label, and display.none hides the output entirely (the value is still computable). Combine values with + for example display.pane + display.status_line.
| Symptom | Likely cause | Fix |
|---|---|---|
| MACD appears as a flat line on the candles | overlay=true on the declaration | Switch to overlay=false and force_overlay=true on price plots |
| Bollinger Bands float in a separate pane | Missing force_overlay=true | Add it to plot() and fill() |
| Error: unknown argument overlay in plot() | overlay is not a plot argument; it belongs to indicator()/strategy() | Move it to the declaration; use force_overlay on plot |
| Error: unknown argument force_overlay | Script is v4 or old v5 | Update to v6 or split in two scripts |
| Fill shows nothing | Plots and fill are in different panes | Match force_overlay on all three |
| Chart squashed after adding script | Script shares the price scale with a different range | Set Scale to No scale or move it to a new pane |
- Open the script's three-dot menu on the chart and use Move to, then New pane, to put an overlay=true script in its own pane.
- In the same menu, choose Pin to scale and pick No scale, or the left or right scale, so autoscale stops including the script's values.
- Open Settings, then Scale, and confirm it is not set to share the price scale unintentionally.
- Check the declaration line: overlay=true when you expected a sub-pane is the single most common cause.
Common questions
Can a Pine script create multiple panes?
No. A script has its one base pane (price or separate) plus the ability to force some outputs onto the price pane. For three panes you need three scripts, which you can connect with input.source().
Does force_overlay work with strategy() as well as indicator()?
Yes. The parameter belongs to the output functions, so it works in both declaration types. In a strategy the order markers are already on the price chart, so force_overlay only matters for your own plots, labels and lines.
Can I use overlay=true and force one plot into a separate pane?
No. force_overlay only pushes outputs onto the price pane. If you need a sub-pane plot, the declaration has to be overlay=false, and the price-scale outputs are the ones you force.
Will changing overlay change my strategy's backtest results?
No, the pane setting only affects drawing. Differences after a change come from edited logic, inputs, or a different chart symbol, timeframe or date range.
How do I hide a plot but still use its value?
Set display=display.none on the plot, or use display.data_window to keep the number readable without drawing a line. The series is still available to other scripts through input.source().