Docs·Delta DSL Script·Concepts

Debugging

Inspect your DeltaDSL script with logInfo, logWarning and logError. Per-true-bar enumeration, value placeholders, the dedicated Logs panel, click-to-jump navigation and emit caps.

When a strategy doesn't fire when you expect, or fires on bars you don't want, the answer is rarely "stare at the chart harder". DeltaDSL gives you three logging functions — logInfo, logWarning, logError — and a dedicated Logs panel docked next to the editor that mirrors what your script saw at every interesting bar.

Treat them the way you'd treat console.log in a normal program: drop a call at the bar where you expect something to happen, save, then read the panel to see what your script actually did. The functions are bulk-vector aware — they understand series and conditions natively, so a single call can enumerate every event in the chart's history.

Where logs appear

The Logs panel opens on the right of the script editor whenever the editor dialog is open. Every logInfo / logWarning / logError call your script makes streams into this panel in real time.

Each entry shows:

  • Level icon — info, warning, or error.
  • Bar timestamp — when on the chart the entry was emitted.
  • Source line — the line in your script that ran the call. Click it to jump the editor caret there.
  • Bar marker — click the small "scroll to bar" icon next to the timestamp to pan the chart to the exact candle the log fired on. A short-lived bubble pops up on that candle showing the log text.
  • Message — the formatted string.

Panel controls (top bar):

ControlEffect
Pause / ResumeFreezes the buffer. Existing entries stay visible; new emits are dropped until you resume. Use during live re-compiles so the panel doesn't scroll out from under you.
ClearEmpties the buffer. Useful when you want to see only logs from the next save / next bar close.
Level chips (info, warning, error)Toggle individual levels on / off in the visible list. The chips dim when off.
Search boxSubstring filter against the message text.
Countvisible / total — when filters or level toggles hide entries, the dim number shows the underlying total.

The Logs panel is a script-editor feature. Logs are intended for debugging while you write a script. They don't appear in the Chart Terminal's main UI and they don't survive a page reload — open the editor dialog whenever you want to see what your script is logging.

The three functions

Delta DSL
logInfo("text or fmt {0}", arg1, arg2, …)
logWarning("text or fmt {0}", arg1, arg2, …)
logError("text or fmt {0}", arg1, arg2, …)

Identical signatures — the only difference is the severity level shown in the panel. Pick by intent:

FunctionUse when
logInfoRoutine diagnostics. "Trigger fired at X", "RSI snapshot Y", "entry conditions met".
logWarningSomething looks off but the script will continue. "Volume is zero on this bar", "input outside expected range".
logErrorA real failure path. "Required input missing", "computation produced NaN where it shouldn't". The panel highlights error entries in red so they stand out in a long list.

There is no log.info / dotted form — the function names are flat (no member call).

Call shapes

logInfo (and the warning / error siblings) accepts four shapes. Reach for the shape that matches the question you're asking.

1. Plain text

Delta DSL
logInfo("Indicator loaded")

One entry, message verbatim. Fastest, useful for sanity checks ("did this line even run?"). Emitted once at the end of the evaluation.

2. Snapshot of the final bar

Delta DSL
r = rsi(close, 14)
logInfo("Final RSI = {0}", r)

One entry, placeholders resolved at the last bar of the series. This is the "what did the indicator end the chart with?" debug. Numeric series (rsi, sma, close, …) fall into this shape automatically because they are not boolean.

3. Per-true-bar enumeration (implicit gate)

Delta DSL
trigger = crossover(close, sma(close, 50))
logInfo("Cross at bar {0}, price {1}", trigger, close)

When any placeholder argument is a boolean series, that series is treated as the gate. The log emits one entry per true bar, with {N} placeholders resolved at each matching bar.

The example above produces one entry for every bar where crossover fired, each with the bar index and the close price at that bar — exactly the per-event list you want when you ask "did this trigger on the bars I expected?".

Detection is automatic: the runtime peeks at the right edge of each argument, and the first boolean series it finds is the gate. Numeric series (RSI, close, anything that holds numbers) are never treated as a gate, so the snapshot shape in §2 continues to work unchanged.

4. Explicit gate

Delta DSL
volSpike = volume > sma(volume, 20) * 2
logInfo(volSpike, "Vol spike: vol={0}, price={1}", volume, close)

The first argument is the gate, the second is the format string, the rest are placeholder values. Use this shape when:

  • The gate is a different series from the values you want to print.
  • The gate is a numeric expression treated as truthy (close > 0 rather than a pre-cast boolean).
  • You want the gate to be obvious from the call site instead of relying on type inference (§3).

Same per-true-bar behaviour as the implicit form: one entry per bar where the gate is truthy.

{N} placeholders

{0}, {1}, {2}, … are positional substitutions. The N-th placeholder is replaced with the N-th value argument (after the format string), rendered as a short string at the relevant bar.

ValueRendered as
4242
3.141592653589793.141593 (trimmed to a sensible decimal)
na / NaNna
true / falsetrue / false
A series argumentThe value of the series at the bar being logged (last bar for snapshot, the matching bar for per-event).
A stringThe string itself.

Unknown placeholders ({99} when there are only two args) are left in the message literally — they don't crash the call.

For complex formatting (fixed decimals, padding, custom date strings) prefer tostring() (see Math, format & time helpers) and concatenate the result:

Delta DSL
logInfo("RSI = " + tostring(r, "0.00") + " on " + tostring(time, "yyyy-MM-dd HH:mm"))

Worked example: trigger audit

You added a divergence detector that fires "way too often" on EURUSD. Before changing thresholds, log the actual events:

Delta DSL
@name "RSI Bull Div Audit"
@pane "below"

@input rsiPeriod = input.int(14, "RSI period",  minval=2, maxval=200)
@input pivotL    = input.int(5,  "Pivot left",  minval=2, maxval=20)
@input pivotR    = input.int(5,  "Pivot right", minval=2, maxval=20)

r = rsi(close, rsiPeriod)

priceLow = pivotlow(low, pivotL, pivotR)
rsiLow   = pivotlow(r,   pivotL, pivotR)

bullDiv = (low < shift(low, pivotL + pivotR))
        and (r > shift(r, pivotL + pivotR))

paneRange(0, 100)
paneLine(r, color="#F0B90B", width=1.5)
plotShape(bullDiv, low, shape="arrowUp", color="#0ECB81", size=10)

// Snapshot: what did the run end with?
logInfo("Run finished. RSI={0}, lastClose={1}", r, close)

// Per-event enumeration: every bar where the detector fired.
logInfo("Bull div fired: low={0}, RSI={1}, prevLow={2}, prevRSI={3}",
        bullDiv, low, r, shift(low, pivotL + pivotR), shift(r, pivotL + pivotR))

// Audit a suspicious sub-condition separately.
flatRsi = (r - shift(r, 5)) < 0.3
logWarning("Flat-RSI region (may dilute signal): RSI={0}", flatRsi, r)

Open the editor with this script. The Logs panel shows:

  • One INFO — Run finished. … entry at the top of the buffer (the snapshot).
  • One INFO — Bull div fired: … entry per bar where the detector triggered.
  • One WARN — Flat-RSI region … entry per bar in the flat zone.

Click any per-event entry's bar marker to scroll the chart to the candle that fired. The script tells you when, where, and with what values — without you having to read the source by eye.

Caps and limits

DeltaDSL logging has hard caps so a debug session can't accidentally fill the buffer with millions of entries and lock up the panel.

CapValueWhat it means
Per call500 entriesA single logInfo(true_series, …) over a long history emits at most 500 entries, even if the gate is true on thousands of bars. The 500 are the first matching bars in chart order, so you see the start of the pattern instead of just the tail.
Per evaluation2000 entriesThe sum across every logInfo / logWarning / logError call in one script run. Beyond 2000, further emits are dropped.
Per entry2000 charactersLong messages (e.g. accidentally formatting an entire series) are truncated with a … (truncated) suffix.
Buffer size10000 entriesThe panel keeps the most recent 10000 entries across all evaluations of this script. Older entries scroll off the bottom of the ring buffer.
Iteration budgetSharedEach emit counts against the same per-evaluation iteration budget that loops use. Logging cannot be used to side-step the runtime's safety net.

If the panel header shows visible / total with total >> visible, you've hit a cap or a level filter; clear the filter to confirm.

Anti-patterns

Don't ship logInfo in a published script

Logs are a debugging affordance for whoever is writing the script. Once the script is doing what you want and ready to share, remove (or comment out) the log calls. Leaving them in doesn't break anything — every call counts against the per-evaluation budget and clutters the panel for anyone else who opens the editor on the same script.

Don't use logs for live notifications

Delta DSL
// ❌ Logs do not send push / Telegram / in-app notifications.
logError("BUY signal on {{symbol}}", buySignal)

Logs are a chart-editor diagnostic surface only. For real-time delivery (push, Telegram, in-app), use alertcondition — it goes through the backend evaluator and reaches the user regardless of whether the editor is open. {{symbol}} placeholders also only work in alert messages, not in log strings.

Don't log inside a tight loop without a guard

Delta DSL
// ❌ Emits one entry per iteration — hits the per-call cap immediately.
for k = 0 to bars - 1
    logInfo("k = {0}, val = {1}", k, at(close, k))
end

The 500-per-call cap protects you from a runaway buffer, but the first 500 iterations still produce 500 entries you have to scroll through. Add a real condition:

Delta DSL
// ✅ Only log iterations that matter.
for k = 0 to bars - 1
    val = at(close, k)
    if val > extremeLevel
        logInfo("Extreme close at k={0}, val={1}", k, val)
    end
end

Or use the per-true-bar shape (§3) outside the loop, which is usually more idiomatic:

Delta DSL
extreme = close > extremeLevel
logInfo("Extreme close: val={0}", extreme, close)

Don't pass a series where you mean its final value

Delta DSL
// ⚠️ Logs "[series len=…]" if the placeholder is interpreted as a
//     non-boolean series and the implicit-snapshot path doesn't trigger.
//     Use the snapshot shape (§2) or pre-extract with at(series, lastBar).
logInfo("RSI is " + r)               // string concat with series → falls back to "[series len=…]"
logInfo("RSI is {0}", r)             // ✅ snapshot, resolves at the last bar

Use the {N} placeholder form when interpolating a series — it knows how to pick the right bar. String concatenation with + is for scalar values (numbers from at() / valuewhen(), strings, etc.).

Don't expect logs after a refresh

Logs live in a per-script in-memory ring buffer that's owned by the editor dialog. Closing the dialog, reloading the page, or switching to a different script clears the buffer. If you need a permanent record of a condition firing, that's an alert, not a log.

Next

  • Alerts — push the same conditions you debugged with logs into push / Telegram / in-app notifications.
  • Pitfalls — the failure modes that cost the most debugging time, with the log calls that surface them.
  • Recipes — complete scripts you can read as worked examples.