Howwise RSS
Trading Software7 min read

Pine Script Overlay and Separate Pane in One Script

Short answer

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.

Key takeaways
  • 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")
Tip: Put force_overlay=true on every output that belongs to the price scale: plot, plotshape, plotchar, plotarrow, plotcandle, plotbar, bgcolor, fill, label.new, line.new and box.new. Anything you leave out stays in the sub-pane and rescales it.

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.

SituationBase settingWhat to do
Mostly price-scale plots (MAs, bands, levels), no oscillatoroverlay=trueNothing special; no force_overlay needed
Price overlays plus one oscillator (MACD, RSI)overlay=falseforce_overlay=true on every price-scale output
Two or more oscillators with different scalesoverlay=falseThey share one pane; split into two scripts if scales clash
Indicator needing three or more distinct panesNot possible in one scriptOne script per pane
Strategy where you only care about order markersEitherMarkers 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.

Warning: Back up the old version of a v4 script before converting it. Automatic conversion rewrites function names and can change behavior, for example in security()/request.security calls and in how some defaults are handled.

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.

  1. In the indicator, plot the value to share, for example plot(macdLine, "MACD", display=display.data_window) so it stays out of the way.
  2. In the strategy, declare src = input.source(close, "External signal").
  3. Add both scripts to the same chart and, in the strategy's Inputs tab, choose the indicator's plot in the External signal dropdown.
  4. Use src in the strategy logic. Only one value per plot is passed, so share each series you need as its own plot.
Tip: Plots from another script are chart-timeframe values at close. If the indicator uses request.security with higher timeframes, repainting behavior is inherited by the strategy.

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.

SymptomLikely causeFix
MACD appears as a flat line on the candlesoverlay=true on the declarationSwitch to overlay=false and force_overlay=true on price plots
Bollinger Bands float in a separate paneMissing force_overlay=trueAdd 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_overlayScript is v4 or old v5Update to v6 or split in two scripts
Fill shows nothingPlots and fill are in different panesMatch force_overlay on all three
Chart squashed after adding scriptScript shares the price scale with a different rangeSet Scale to No scale or move it to a new pane
  1. 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.
  2. 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.
  3. Open Settings, then Scale, and confirm it is not set to share the price scale unintentionally.
  4. 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().

Related topics:pine scripttradingviewforceoverlayindicatorsstrategy