Plots and panes
Lay out a price trend on the chart and CVD, open-interest change and volume delta in three stacked sub-panes from one script.
This script builds a compact order-flow layout. On the price pane you see a fast and a slow EMA with the space between them shaded. Below the chart, three panes show cumulative volume delta that restarts every day, the bar-by-bar change in open interest, and the volume delta of each bar.
It is a good template whenever you want several related series in one indicator: one script, one set of inputs, and every pane removed together when you remove the script.
script "Flow layout"
input (
fastLen = input.int(21, title: "Fast EMA", min: 2, max: 200)
slowLen = input.int(55, title: "Slow EMA", min: 3, max: 400)
dailyReset = input.bool(true, title: "Restart CVD every day")
)
data (
chart = subscribe(data.ohlcv)
oi = subscribe(data.oi)
vd = subscribe(data.vd)
)
pane (
cvdPane = pane(title: "CVD", height: 0.2)
oiPane = pane(title: "OI change", height: 0.15)
vdPane = pane(title: "Volume delta", height: 0.15)
)
plot (
fast = plot.line(title: "Fast EMA", color: color.blue, width: 2)
slow = plot.line(title: "Slow EMA", color: color.gray, width: 2)
cvdLine = plot.line(title: "CVD", color: color.cyan, width: 2, on: cvdPane)
oiBars = plot.histogram(title: "OI change", on: oiPane)
vdBars = plot.histogram(title: "Delta", on: vdPane)
)
fill trendFill = fill.between(fast, slow, color: color.withAlpha(color.blue, 40))
state previousTime: time? = null
// Price pane: the trend lines follow the forming candle.
on chart.update {
let f = ta.ema(chart.close, fastLen)
let s = ta.ema(chart.close, slowLen)
let up = (f ?? 0.0) >= (s ?? 0.0)
fast.plot(f, color: up ? color.green : color.red)
slow.plot(s)
}
// CVD pane: sum each bar's delta, restarting on a new day.
on vd.close {
let t = vd.time ?? time.fromUnixSeconds(0)
let newDay = dailyReset && time.isNewPeriod(t, previousTime, 1D)
cvdLine.plot(ta.cum(vd.close, reset: newDay))
previousTime = t
}
// OI pane: change against the previous confirmed period.
on oi.close {
let change = ta.change(oi.close)
let shade = (change ?? 0.0) >= 0.0 ? color.green : color.red
oiBars.plot(change, color: color.withAlpha(shade, 200))
}
// Delta pane: the close of the volume-delta bar is the bar's delta.
on vd.update {
let delta = vd.close
vdBars.plot(delta, color: (delta ?? 0.0) >= 0.0 ? color.green : color.red)
}How it works
Declare every surface up front
Panes and plots are declarations, not statements. The grouped pane ( ... ) block creates three panes, and each plot names its target with on:. A plot without on: lands on the price pane.
height is a share of the chart’s height. Three panes at 0.2, 0.15 and 0.15 leave half of the space for price. The order of the declarations is the order of the panes from top to bottom; in the chart you can drag the dividers, and the script remembers the sizes.
One handler per source
Each subscription has its own handlers. ta.change(oi.close) in on oi.close compares the confirmed open interest with the one before it; if either side is missing the result is null, and the histogram shows a gap rather than a misleading zero.
Keeping sources in separate handlers means you never have to think about which subscription’s bar is confirmed at a given moment. If you need to combine two sources in one calculation, read data and update events first.
Accumulate with a reset
ta.cum(value, reset:) keeps a running sum and starts again from the current value when reset is true. time.isNewPeriod tells when a bar opens a new day; the previous bar’s time lives in state because the handler needs it on the next call.
Colour per point
Every plot(...) call accepts an optional color:. The fast EMA switches colour by its position against the slow EMA, and the histograms colour each bar by sign. The fill between the two EMAs is declared once with fill.between and needs no handler code.
Adapting it
- Liquidations instead of OI. Subscribe to
data.statand plotstats.sellLiqas a positive andstats.buyLiqas a negative histogram; see data and update events. - Weekly CVD. Replace
1Dwith1Wintime.isNewPeriod. - Hide a series without removing it. Add an
input.booland plotshow ? value : null. The plot stays declared, so the layout does not change.