Docs·Delta DSL Script·Library

Multi-timeframe (HTF)

Aggregate any chart-TF series into HTF buckets ("1D", "1W", "1M", …) and broadcast back aligned to chart bars. Pine request.security equivalent for same-symbol resampling — no network, no repaint by default.

The HTF helpers let a script READ data as if it were running on a HIGHER timeframe (HTF) than the chart's current TF, then receive the result aligned back to chart bars. Same-symbol only — Phase 3.1 does not cross symbols (see Limitations for the cross-symbol roadmap).

This covers ~80% of what Pine's request.security(syminfo.tickerid, tf, expr) is used for: anchored daily/weekly/monthly levels, HTF indicators overlaid on intraday charts, "new daily bar" gates, "today's high so far" overlays.

At a glance

FunctionReturnsUse case
htf_resampleaggregated seriescore primitive — any series → HTF bucket → broadcast
htf_new_barbool series"fire once per new HTF bar" gate
htf_bucket_startms seriesx-coord for "anchor at bucket boundary" drawings
htf_bars_in_bucketint seriesprogress within current HTF bucket

Timeframe string format

StringMeaningBucket boundary
"1m" / "5m" / "15m" / "30m"Minute windowseconds-since-epoch grid floor
"1h" / "2h" / "4h" / "12h"Hour windowseconds-since-epoch grid floor
"1D" / "3D"Day windowUTC midnight, anchored to epoch day 0
"1W" / "2W"Week windowISO Monday 00:00 UTC, anchored across N-week windows
"1M" / "3M"Month windowUTC 1st of month, anchored to year boundary (so "3M" = Q1/Q2/Q3/Q4 starts)
"1Y"Year windowUTC Jan 1

Lowercase calendar letters (1d / 1w / 1m for month) are rejected — the lowercase m is reserved for minutes. Use the uppercase form for D / W / M / Y.

htf_resample

Delta DSL
htf_resample(series, tf, timestamps, op="last", lookahead=false)

The core primitive. Aggregates series into HTF buckets defined by timestamps and tf, then broadcasts the per-bucket value back to chart bars.

Parameters

  • series — any chart-TF series (close, high, an indicator result, an iff-mask of close, …).
  • tf — HTF string, see format table.
  • timestamps — the built-in time series. Auto-handles seconds-or-ms (matches the policy in lib/time.js).
  • op — how to combine chart bars within a bucket:
    opBucket value at finalizeCommon use
    "first"first finite valueHTF open
    "last"last finite valueHTF close (default)
    "min"min finite valueHTF low
    "max"max finite valueHTF high
    "sum"sum of finite valuesHTF volume / cumulative flow
    "avg"mean of finite valuesHTF vwap-ish (use cautiously — vwap needs weighting)
  • lookaheadfalse (default) returns the LAST COMPLETED bucket's value at each chart bar. true returns the RUNNING value of the CURRENT bucket (intra-bar). Lookahead-off is the repaint-free default.

Returns

A series of the same length as the shorter of series and timestamps. NaN at chart bars where no completed bucket exists yet (no-lookahead mode) or where the current bucket has only NaN samples (lookahead mode).

Example — daily OHLC anchored on a 1h chart

Delta DSL
@version 1
@name "Daily OHLC anchors"

@input show_today = input.bool(true, "Show today's levels")

// Resample chart-TF OHLCV to daily buckets. Lookahead=true so the
// HIGH/LOW/OPEN visible on intraday bars within today track the
// running daily high/low/open (otherwise we'd see yesterday's
// values on every bar of today, which is the repaint-free
// default but not what you want for "today's open" overlays).
d_open  = htf_resample(open,   "1D", time, "first", true)
d_high  = htf_resample(high,   "1D", time, "max",   true)
d_low   = htf_resample(low,    "1D", time, "min",   true)
d_close = htf_resample(close,  "1D", time, "last",  true)
d_vol   = htf_resample(volume, "1D", time, "sum",   true)

plotLine(d_open,  color="#888a90", width=1)
plotLine(d_high,  color="#0ECB81", width=1)
plotLine(d_low,   color="#F6465D", width=1)

// Daily RSI on a 1h chart — compute on the resampled close, plot
// at chart resolution. Same value across all chart bars within
// one daily bucket (Pine semantics).
daily_rsi = rsi(d_close, 14)
plotLine(daily_rsi, pane="below", color="#D4A60A")

Example — weekly support/resistance with completed buckets

Delta DSL
@input weeks_back = input.int(4, "Weeks of S/R", minval=1, maxval=12)

// lookahead=false (default) — at each chart bar, see the LAST
// COMPLETED weekly value. Drawing the weekly high/low overlay
// on an intraday chart that NEVER repaints (the level finalises
// when the week closes and stays put on Monday's first bar
// onward).
weekly_high = htf_resample(high, "1W", time, "max")
weekly_low  = htf_resample(low,  "1W", time, "min")

plotLine(weekly_high, color="#0ECB81", width=2)
plotLine(weekly_low,  color="#F6465D", width=2)

htf_new_bar

Delta DSL
htf_new_bar(timestamps, tf)

Boolean series — true at every chart bar that's the FIRST bar of a new HTF bucket. Equivalent to Pine's ta.change(time(tf)) != 0. Bar 0 always returns false (no pre-history baseline).

Delta DSL
new_day = htf_new_bar(time, "1D")

// Plot one label per daily candle — exactly one fires at the
// first chart bar of each new day.
plotLabel(time, low, iff(new_day, "•", na),
          color="#FFFFFF",
          size=10)

// Or as an alert gate — fire when a new daily bar opens above
// the weekly high:
weekly_high = htf_resample(high, "1W", time, "max")
alertcondition(new_day and open > weekly_high,
               title="Daily open above weekly high",
               kind="bullish")

htf_bucket_start

Delta DSL
htf_bucket_start(timestamps, tf)

Series carrying the UTC ms timestamp marking the start of each chart bar's HTF bucket. Useful as an x-coordinate for lineNew / boxNew / labelNew when you want to anchor a level to the bucket boundary rather than to a chart bar.

Delta DSL
// Today's open line that visually starts at UTC midnight, not at
// whatever 1h bar happens to be the first one inside today:
d_open    = htf_resample(open, "1D", time, "first", true)
d_start   = htf_bucket_start(time, "1D")

// Draw one persistent line that updates each tick. Slot=1 so the
// line PERSISTS across script runs.
lineNew(1, at(d_start, last_bar_index), at(d_open, last_bar_index),
           last_bar_time,                at(d_open, last_bar_index),
           color="#D4A60A", width=2, dash=2, gap=4)

htf_bars_in_bucket

Delta DSL
htf_bars_in_bucket(timestamps, tf)

Series of bar counts within the current HTF bucket — 0 at the first chart bar of a new bucket, 1 at the second, etc. Reset to 0 at every new bucket. Single-pass implementation (cheaper than barssince(htf_new_bar(...))).

Delta DSL
// Fade the "today so far" zone — 50% alpha at the start of the
// daily bar, 8% at the end of the typical 24-bar (1h) daily.
in_day  = htf_bars_in_bucket(time, "1D")
alpha   = 0.50 - in_day * 0.018
alpha   = max(alpha, 0.08)

plotBgColor(true, color=withAlpha("#0ECB81", alpha))

// Or wait at least 4 bars into a new daily bar before allowing
// a new entry — prevents reacting to the open spike:
new_day  = htf_new_bar(time, "1D")
warmup   = in_day < 4
entry_ok = longSignal and not warmup

Repaint policy

Default (lookahead=false) — repaint-free. Chart bar i shows the FINAL value of the LAST COMPLETED HTF bucket. Once a bucket closes, the value never changes — historical bars on the chart show the same value the live bar saw at that point in time. This matches Pine's barmerge.lookahead_off and is the SAFE default for any signal-generation logic.

lookahead=true — live, intra-bar. Chart bar i shows the RUNNING value of the CURRENT HTF bucket. The value at any chart bar can change as more chart bars of the current HTF bucket arrive. Useful for visual overlays ("today's high so far") but DANGEROUS for backtesting: a strategy using htf_resample(high, "1D", time, "max", lookahead=true) would see today's eventual high from the FIRST bar of today (lookahead bias).

ModeWhen to useRepaint?
lookahead=falseSignals, alerts, anything that drives entries/exitsNo
lookahead=trueVisual overlays for "current bucket so far", live HUDsYes (paints differently as bucket evolves)

Limitations

1. Lower-TF requests give nonsense

Calling htf_resample(close, "1m", time) on a 1h chart asks for finer data than the chart has. Each chart bar maps to a single 1m bucket containing one value — the output is structurally valid but semantically meaningless. The DSL doesn't currently detect this misuse. Stick to TFs at-or-coarser-than the chart TF.

A future hardening pass may sniff the chart TF from median(timestamps[i+1] - timestamps[i]) and throw on lower-TF requests.

2. Cross-symbol (Pine request.security("BTC", ...)) — deferred to Phase 4

The DSL sandbox forbids fetch / WebSocket from script-space, so cross-symbol HTF would require BE-side fetching + symbol whitelisting + multi-tab subscription caching. None of that is done in Phase 3.1. To overlay a different symbol today:

  • Add it as a separate indicator on the chart pane (UI-side).
  • Wait for Phase 4 which will introduce a cross_symbol(...) gateway with the necessary BE plumbing.

3. Calendar precision

  • Week buckets are ISO Mondays. Sunday-start weeks are not supported (yet).
  • Month buckets are anchored to year boundary, so "3M" emits Q1/Q2/Q3/Q4 starts — not "rolling 3 months from now".
  • Year buckets are anchored to year-0, so "5Y" groups by half-decade starting 1970/1975/1980/…

4. Timestamp gaps in the chart history

Bad timestamps (NaN, ≤0) are silently passed through as NaN in the output. The accumulator doesn't reset on a bad bar — it just doesn't add to the running stats. Practical effect: a single bad bar in the middle of a daily bucket doesn't break the daily aggregate, but a long gap can mask a missed bucket boundary if both the gap and the missing boundary share a chart bar.

Performance

FunctionComplexityNotes
htf_resampleO(N)Single forward pass over chart bars. Inline Float64Array(7) accumulator — no per-bucket allocations.
htf_new_barO(N)Single pass, compares each bar's bucket to previous.
htf_bucket_startO(N)Single pass + Date.UTC arithmetic per bar (only for calendar units).
htf_bars_in_bucketO(N)Single pass, integer counter.

All four are designed to stay under the HEAVY_COMPUTE_BUDGET_MS = 16ms envelope at 5000 bars (the engine's per-eval budget). For an indicator that calls htf_resample 5 times (O, H, L, C, V) on a 5000-bar chart, expect ~1ms wall-clock total.

Reference recipes

Anchored VWAP on a configurable HTF

Delta DSL
@input tf = input.string("1D", "Anchor", options=["1D","1W","1M"])

// Anchored VWAP — sum(price * volume) / sum(volume) per bucket,
// with lookahead so the running VWAP is visible intra-bucket.
pv      = hlc3 * volume
sum_pv  = htf_resample(pv,     tf, time, "sum", true)
sum_v   = htf_resample(volume, tf, time, "sum", true)
htf_vwap = iff(sum_v > 0, sum_pv / sum_v, na)

plotLine(htf_vwap, color="#D4A60A", width=2)

"First N bars of new daily" exclusion mask

Delta DSL
@input cooldown = input.int(4, "Cooldown bars after new day", minval=0, maxval=20)

in_day   = htf_bars_in_bucket(time, "1D")
trade_ok = in_day >= cooldown

// Combine with any signal — silently skips the open-spike bars.
entries  = longSignal and trade_ok

Higher-TF crossover signal

Delta DSL
fast = ema(htf_resample(close, "1D", time, "last"), 9)
slow = ema(htf_resample(close, "1D", time, "last"), 21)

new_day  = htf_new_bar(time, "1D")
golden   = crossover (fast, slow) and new_day
death    = crossunder(fast, slow) and new_day

alertcondition(golden, title="Daily EMA golden cross", kind="bullish")
alertcondition(death,  title="Daily EMA death cross",  kind="bearish")

Next

  • Series — the chart-TF primitives that HTF resampling composes on top of.
  • Drawing — using htf_bucket_start as an anchor x-coord for persistent levels.
  • Recipes — full scripts combining HTF, arrays, and the rest of the stdlib.