Docs·Delta DSL Script·Drawing

Color

Static colors, alpha, theme-aware palettes, and where dynamic per-bar coloring is and is not supported in DeltaDSL.

Every drawing primitive that paints pixels accepts a color argument. Colors are strings in any CSS-recognized format. There is no color type, no constants, no enums — the runtime parses the string when it forwards the draw record to the engine.

Color formats

DeltaDSL accepts every CSS color syntax:

FormatExample
Hex shorthand"#fff", "#0c0"
Hex 6-digit"#0ECB81"
Hex 8-digit (with alpha)"#0ECB8166" (40 % alpha)
rgb()"rgb(14, 203, 129)"
rgba()"rgba(14, 203, 129, 0.4)"
hsl()"hsl(150, 70%, 40%)"
hsla()"hsla(150, 70%, 40%, 0.4)"
Named"red", "transparent", "steelblue"

The 8-digit hex form (#RRGGBBAA) is the most compact way to specify both a hue and a transparency:

Delta DSL
plotBand(upper, lower, color="#0ECB8124")  // green, ~14 % alpha

For readability, rgba(...) is the recommended form when you want anyone reading the script to immediately see what the alpha is:

Delta DSL
plotBand(upper, lower, color="rgba(14, 203, 129, 0.14)")

The project palette

The chart UI uses a fixed brand palette (Binance/Material). When you author a script that should "look native" on this terminal, prefer these hues:

RoleDark themeLight theme
Bullish (BUY direction)#0ECB81#26a69a
Bearish (SELL direction)#F6465D#ef5350
Chrome accent (Binance amber)#D4A60A#A88408
Neutral text#c8c8d8#131722
Pane background#0F1215#ffffff

Tuned alphas you'll want often:

UseHexrgba
Soft band fill (bullish)#0ECB8124rgba(14,203,129,0.14)
Soft band fill (bearish)#F6465D24rgba(246,70,93,0.14)
Bright band fill#0ECB8166rgba(14,203,129,0.40)
Highlight tint (amber)#F0B90B1Frgba(240,185,11,0.12)
Horizontal grid linergba(150,150,150,0.18)

The palette is documented in binance-amber-glass.mdc and lives at src/assets/styles/styles.scss (--mrd-amber-fg, --mrd-bull-fg, --mrd-red-fg). Scripts can't read those CSS variables (the WASM render pass needs literal RGB), so when a script needs the brand palette it inlines the hex.

Theme-aware coloring

Charts run in dark or light theme. Most scripts only need to look right on dark; if you want to support both, pick:

  • Mid-saturation hues that read on both backgrounds — the project palette above is tuned for this.
  • Use a stronger alpha on bands for light-theme backgrounds since the white background washes out 14 % alphas.

DeltaDSL does not expose a "is dark theme?" runtime flag today. If your script absolutely needs to switch palette by theme, expose it as an input:

Delta DSL
@input theme = input.string("Dark", "Theme",
                            options=["Dark","Light"], group="Style")

bullColor = iff(theme == "Dark", "#0ECB81", "#26a69a")
bearColor = iff(theme == "Dark", "#F6465D", "#ef5350")

Note: iff over strings produces a string at script-eval time (each theme comparison resolves once before the bar loop runs), so this pattern is fine for plotLine's static-color requirement.

Static vs dynamic coloring (important)

The engine distinguishes between static colors (one color per output channel) and dynamic per-bar coloring (different colors on different bars).

Static color (default — most primitives)

Functions like plotLine, plotShape, plotBand, paneLine, paneBand, paneFill, etc. take ONE color per call. The color string is resolved once per evaluation, not per bar.

Delta DSL
plotLine(close, color="#0ECB81", width=2)             // one color
plotBand(upper, lower, color="rgba(14,203,129,0.14)") // one color

If you pass an iff(...) expression that returns different colors per bar (a "color series"), only the first bar's color is used for the whole line. This is intentional — drawing tens of thousands of variable-color line segments costs orders of magnitude more in the WASM pipeline than one solid line.

Dynamic per-bar coloring (the mask pattern)

To paint the same shape with different colors on different bars, split the data into multiple series — each masked to where its color applies — then call plotLine (or paneLine) once per color:

Delta DSL
@pane "overlay"

ma   = sma(close, 50)
isUp = close > ma

upPart = mask(close, isUp)
dnPart = mask(close, isUp == false)

plotLine(upPart, color="#0ECB81", width=2)
plotLine(dnPart, color="#F6465D", width=2)

mask(series, predicate) returns a copy of the series where the predicate is false replaced with na. The renderer breaks the polyline at na gaps, so the two segments don't visually connect. The result on the chart: a single line that switches color based on close > ma.

Use this pattern for:

  • Trend ribbon (above MA = green, below = red).
  • Volume bars colored by direction (mask(volume, close > open) + mask(volume, close < open)).
  • Multi-zone oscillator (overbought / oversold / neutral).

For shape-style primitives (plotShape, plotShapeAt, plotMarker, plotChar), the engine accepts a per-bar color via the function's color argument when it's the COLOR for the marker on the bar where the trigger is true. Markers are point-events, not connected lines, so coloring per event is cheap.

Per-pane / per-shape primitives that DO accept dynamic color

These primitives evaluate their color argument on every emit, so passing different colors per bar (or per call) works as expected:

  • plotShape / plotShapeAt / plotMarker / plotChar — one shape per truthy bar; color resolves at emit.
  • paneLabel / plotLabel / panePlotLabel — labels resolve color at draw call.
  • bgcolor — background tint per bar.
  • labelNew / boxNew / lineNew / polylineNew — persistent slots; the color you pass on each frame replaces the slot's previous color (so animating slot colors works).
  • drawingHline / drawingTrendline / drawingFibRet / drawingChannel — drawing-tool shapes; the color updates with every call.

Anywhere else (line / band / fill primitives that produce ONE polyline or area), assume static color.

Common patterns

Two-color trend line

Delta DSL
ma   = sma(close, 50)
isUp = close > ma

plotLine(mask(close, isUp),         color="#0ECB81", width=2)
plotLine(mask(close, isUp == false), color="#F6465D", width=2)

Histogram with positive / negative tinting

Delta DSL
@pane "below"

hist = macd_hist(close, 12, 26, 9)
posHist = mask(hist, hist >= 0)
negHist = mask(hist, hist <  0)

paneFill(posHist, base=0, color="rgba(14,203,129,0.5)")
paneFill(negHist, base=0, color="rgba(246,70,93,0.5)")

Three-zone oscillator (overbought / neutral / oversold)

Delta DSL
@pane "below"

r = rsi(close, 14)
ob = mask(r, r >= 70)
os = mask(r, r <= 30)
mid = mask(r, r > 30 and r < 70)

paneRange(0, 100)
paneLine(mid, color="#F0B90B", width=1.5)
paneLine(ob,  color="#F6465D", width=1.5)
paneLine(os,  color="#0ECB81", width=1.5)

Background pulse on event

Delta DSL
event = volume > sma(volume, 20) * 3
bgcolor(iff(event, "rgba(240,185,11,0.18)", "rgba(0,0,0,0)"))

bgcolor paints behind candles and supports dynamic per-bar colors, so an iff is fine here.

Persistent label that re-tints with state

Delta DSL
ma = sma(close, 50)
labelNew("ma_state",
         time[0], ma[0],
         "MA50: " + tostring(ma[0], "0.00"),
         color = iff(close[0] > ma[0], "#0ECB81", "#F6465D"),
         anchor="left", size=12)

labelNew resolves color on every frame, so flipping by side works naturally.

Color helpers

Most scripts inline color literals. For more dynamic coloring, two helpers exist:

  • color.rgb(r, g, b, a=1.0) — build a CSS rgba string from numeric channels.
  • color.new(base, alpha) — clone a base color with a different alpha (useful when you want one hue but different opacities per zone).
Delta DSL
red = "#F6465D"
softRed = color.new(red, 0.14)  // → "rgba(246,70,93,0.14)"

plotBand(upper, lower, color=softRed)

Anti-patterns

Don't pass an iff returning two colors to a static-color primitive

Delta DSL
// ❌ This compiles, but the engine uses the FIRST bar's color for the
//    entire line. Indistinguishable from a solid line.
plotLine(close, color=iff(close > ma, "#0ECB81", "#F6465D"))

// ✅ Use the mask pattern.
upPart = mask(close, close > ma)
dnPart = mask(close, close <= ma)
plotLine(upPart, color="#0ECB81", width=2)
plotLine(dnPart, color="#F6465D", width=2)

Don't gradient-fill bands with multiple plotBand calls

The engine does NOT support gradient fills today. Two stacked plotBand calls with different alphas produce two opaque bands, not a smooth gradient. If you need depth, use a single band with a tuned alpha (0.10..0.20 range usually).

Don't use the BUY/SELL palette for chrome

The chart's chrome (toolbar, settings, dialogs) uses Binance amber. The bullish / bearish hues are reserved for trade data. Scripts authored for this terminal should follow the same convention: amber for "informational" / neutral signals, green / red strictly for direction.

If your script paints a "bullish setup" annotation, green is correct. If it paints "value level" or "session vwap", amber is correct. If it paints a "high attention zone" without direction, neutral or amber.

Next