Built-in variables
OHLCV series, derived sources, time, bar_index, bars, bar-state accessors, plus the constants pi, e, na. Everything available without an import.
Every Delta DSL script starts with the chart's data already in scope. You don't import OHLCV, you don't subscribe to a feed, you don't poll — the runtime binds these names before the first line of your script runs and keeps them in sync with whatever symbol / timeframe the chart has loaded.
OHLCV series
| Name | Type | Description |
|---|---|---|
open | series<number> | Open price of each bar |
high | series<number> | High price of each bar |
low | series<number> | Low price of each bar |
close | series<number> | Close price of each bar |
volume | series<number> | Volume of each bar (base-currency units, exchange-reported) |
All five are arrays of length bars. close[0] is the most recent bar's close; close[1] is one bar back; close[bars-1] does not exist as such — use close[bars-1] only via at(close, 0) for the oldest available bar (see at(...) below).
Derived sources
Three convenience derivations the runtime computes for you:
| Name | Formula | When to use |
|---|---|---|
hl2 | (high + low) / 2 | Mid-bar reference, quieter than close |
hlc3 | (high + low + close) / 3 | Typical price — used by CCI, MFI |
ohlc4 | (open + high + low + close) / 4 | Smoother than close for very noisy series |
These are real series, not lazy expressions — using them is exactly as fast as using close.
Derivatives feed
Bar-aligned series for futures-only market microstructure. Each series has length bars and aligns 1-to-1 with open / high / low / close. Use these to write Pine-style "request.security" indicators (CVD divergence, OI build, funding-rate stress, ΔOI vs price) without leaving the sandbox.
| Name | Type | Description |
|---|---|---|
taker_buy_volume | series<number> | Per-bar aggressive buy volume (Binance kline k[9]). Source: futures. |
taker_sell_volume | series<number> | volume - taker_buy_volume. Per-bar aggressive sell volume. |
delta_volume | series<number> | taker_buy_volume - taker_sell_volume. Per-bar net order-flow delta. |
cvd | series<number> | Cumulative Volume Delta = running sum of delta_volume from chart start. Same series the built-in CVD pane renders when source = futures. |
open_interest | series<number> | Bar-aligned Open Interest (sumOpenInterest from Binance, forward-filled between OI samples). |
oi_delta | series<number> | open_interest - open_interest[1]. Per-bar ΔOI. The first bar is NaN. |
funding_rate | series<number> | Bar-aligned Binance funding rate in percent (0.01 == 0.01 %). Forward-filled between 8-hour settlement events. |
Source-data requirements
These series are populated by the same fetchers that drive the built-in CVD / OI / Funding-Rate indicators. The fetcher only runs when at least one consumer of that data is active:
| Series | Requires (enable any one) |
|---|---|
taker_buy_volume, taker_sell_volume, delta_volume, cvd | CVD indicator |
open_interest, oi_delta | Open Interest indicator, or OI × CVD Pattern, or Liquidation Heatmap |
funding_rate | Funding Rate indicator |
Without the source indicator enabled, the series is populated with NaN (length bars, every element NaN). Your script keeps running; the math just propagates NaN until the user enables the data source. Use na(...) (or isna(...)) to detect the empty state and either skip your compute or draw a hint label.
Note on
na: the bare identifier is polymorphic. In a value position it resolves to the NaN constant (iff(cond, value, na)masks a bar to a gap). In a call positionna(x)is the Pine-compat NaN predicate (same semantics asisna(x)— scalar → boolean, series → boolean series). Use whichever spelling matches the script you are porting; the interpreter dispatches based on whethernais followed by(.
Patterns
CVD-vs-price divergence (bullish setup at the live bar):
@name "CVD Divergence (bull)"
@input lookback = input.int(50, "Lookback", min=10, max=200)
if isna(at(cvd, last_bar_index))
// CVD indicator not enabled yet — no data to work with.
plotLabel(last_bar_time, at(close, last_bar_index),
"Enable CVD indicator to use this script",
color="#fff", bg="rgba(246,70,93,0.7)", anchor="left")
else
price_low = lowest(low, lookback)
cvd_low = lowest(cvd, lookback)
bull_div = low[0] <= price_low[0] and cvd[0] > cvd_low[0]
plotMarkerUp(bull_div, low, color="#0ECB81", size=10)
end
Open Interest spike + price up = trend build:
@name "OI Build"
@input oi_z_threshold = input.float(2.0, "OI z-score threshold")
if isna(at(open_interest, last_bar_index))
plotLabel(last_bar_time, at(close, last_bar_index),
"Enable Open Interest",
color="#fff", bg="rgba(246,70,93,0.7)", anchor="left")
else
oi_z = zscore(oi_delta, 50)
price_up = close > shift(close, 1)
build = oi_z > oi_z_threshold and price_up
plotMarkerUp(build, low, color="#F0B90B", size=8)
end
Funding rate stress (long-crowded → short bias):
@name "Funding Stress"
@input long_threshold = input.float(0.05, "FR threshold (%)", min=0)
stressed = funding_rate > long_threshold
plotBgColor(stressed, color="rgba(246,70,93,0.06)")
Notes
- These series are computed once per
evaluate()pass — using them is exactly as fast asclose. - Forward-fill is intentional:
funding_rateonly changes every 8 hours, but every bar carries the latest known value. This matches the built-in pane's behaviour. - Spot vs futures: the CVD series here is futures-source. The built-in CVD pane can also show spot-source delta, but that path is not exposed to scripts (would require a per-script source-mode selector — open an issue if you need it).
Time series
| Name | Type | Description |
|---|---|---|
time | series<number> | Bar timestamps in epoch seconds (UTC) — same units as the engine's world X axis. Drawing primitives (lineNew, boxNew, labelNew, plotVLine) consume seconds; calendar helpers (year/month/dayofmonth/…) auto-rescale seconds → ms internally, so dayofmonth(time) works without a manual * 1000. See pitfall §12. |
bar_index | series<number> | [0, 1, 2, …, bars - 1] |
time is what world-coordinate primitives (plotLineXY, plotBox, plotLabel, lineNew, boxNew, …) anchor to. time[0] is the timestamp of the right-most bar; time[5] is five bars to the left.
bar_index is a series — useful for "since-bar" gating:
plotBgColor(bar_index >= bars - 50, color="rgba(240,185,11,0.04)")
paints the last 50 bars with a dim amber strip.
If the OHLCV doesn't carry timestamps (rare — every live exchange feed includes them), time falls back to bar_index so world-coord primitives still work without crashing.
Bar state
A short list of Pine-flavoured accessors that map cleanly onto Delta DSL's bulk-vector model. Use these to anchor draws at the live bar without writing index boilerplate.
| Name | Type | Value |
|---|---|---|
last_bar_index | scalar | bars - 1 (-1 if no bars are loaded yet) |
last_bar_time | scalar | time[bars - 1] — timestamp (ms) of the rightmost bar |
first_bar_time | scalar | time[0] — timestamp (ms) of the oldest loaded bar |
barstate_islast | series<bool> | true only at the rightmost bar, false everywhere else |
When you'd reach for each
Anchor a one-off label at the live bar:
@input show_last = true
if show_last
plotLabel(last_bar_time, at(close, last_bar_index),
"Last close: " + tostring(at(close, last_bar_index), "0.00"),
color="#fff", bg="rgba(0,0,0,0.7)", anchor="left")
end
Mark only the live bar with a triangle (the trader-friendly shape for "this is where you act now"):
buy_setup = crossover(close, sma(close, 20))
plotMarkerUp(buy_setup and barstate_islast, low, color="#0ECB81", size=10)
Draw a vertical line at the first bar of the loaded window (handy when the indicator depends on a clear "since" anchor):
plotVLine(first_bar_time, color="rgba(255,255,255,0.20)", width=1)
Notes
last_bar_index/last_bar_time/first_bar_timeare scalars, not series. Use them where the call expects a scalar (plotLabel,lineNew,plotVLine). Inside aforloop with a counter, read the underlying series withat(time, i)instead.barstate_islastis a series — the canonical place to gate per-bar logic withand(e.g.crossover(...) and barstate_islast).
What is NOT exposed (and why)
Pine has a wider barstate.* family — isconfirmed, isrealtime, ishistory, isnew, islastconfirmedhistory, isfirst. Delta DSL deliberately ships a smaller subset because:
- The
isconfirmed/isrealtime/ishistoryfamily requires a "last bar is still forming" signal that Pine's per-bar execution model provides naturally; in our bulk-vector model the script always sees the full closed-history snapshot, so the distinction would need a separate compute-time hint that very few scripts actually use. isnewwould betrueon every bar in everyevaluate()pass (each pass recomputes the whole series) — exposing it would mislead users about what it gates.islastconfirmedhistoryis an edge case rarely needed when porting Pine scripts.isfirstis justbar_index == 0— shorter than the namespaced form, no API surface to maintain.
If you have a concrete use case that one of the omitted identifiers would unlock, open an issue with the script you're trying to write and we'll consider adding it.
Scalar utilities
| Name | Value | Type |
|---|---|---|
bars | The number of loaded bars in the visible window | scalar |
pi | Math.PI ≈ 3.141592653589793 | scalar |
e | Math.E ≈ 2.718281828459045 | scalar |
na | NaN | scalar |
bars is scalar (not a series) — it's the same number on every "bar". Use it as a guard:
if bars < 200
warn := "Need at least 200 bars for this indicator"
end
pi and e are mathematical constants. na is the canonical "no value" placeholder; pass it to iff to mask bars to NaN, which the renderer treats as a gap:
filtered = iff(close > sma(close, 50), close, na)
plotLine(filtered, color="#F0B90B") // line breaks where condition is false
Reading the latest bar
The right edge of the visible window is [0]:
lastClose = close[0]
lastVolume = volume[0]
prevHigh = high[1] // one bar before the right edge
gapPct = (open[0] - close[1]) / close[1] * 100
series[k] returns a scalar at the right edge for ANY k. Inside a for loop iterating over bar indices, use at(series, i) instead — it returns the scalar at absolute index i, not a relative shift.
Scope and shadowing
Built-in names are not reserved — you CAN reassign them — but doing so usually indicates a bug. The two acceptable cases:
- Bind a different source under a generic name:
@input src = input.source("close", "Source") ma = sma(src, 20) // src is open/high/low/close/etc., bound by input.source - Apply a transform you want named for clarity:
ohlc4_smoothed = ema(ohlc4, 5)
If you find yourself writing close = some_other_thing, you almost always want a new variable instead.
Behaviour at the chart edges
- The right edge is the most recent bar. On a live chart this bar updates as ticks arrive; the script re-runs on every redraw.
- The left edge is the oldest bar in the loaded window. The user can scroll back further; when they do, the engine fetches more history and re-runs the script with a longer
bars. - Indicators with a warmup period (e.g.
sma(close, 200)) return NaN for the firstperiod - 1bars. Usenz(value, default)orfixnan(value)to fill those if your downstream math can't tolerate NaN. - All series are aligned to the same
barslength. You can never mix series from different timeframes — the runtime works with one timeframe at a time.
What's NOT pre-bound
For comparison, things you might expect from other charting DSLs that do not exist in Delta DSL:
vwap— not pre-bound. Compute viasum(close * volume, period) / sum(volume, period).syminfo.*(TradingView) — symbol metadata is not exposed to scripts.timeframe.*— the script doesn't know which timeframe it's running on.position.*— broker / position data is not visible to scripts.request.security(...)— multi-symbol / multi-TF fetch is not exposed. The derivatives series above (cvd/open_interest/funding_rate) are the only "non-OHLCV" data piped into the sandbox; they ride the same fetcher the built-in panes use.
These limits are deliberate: the script must run identically against any chart instance, so anything the engine can't guarantee is not exposed.
Next
- Math & format — element-wise math, branching helpers,
tostring, time helpers. - Series operations —
shift,change,crossover,valuewhen, … - Indicators — moving averages, oscillators, structure.