TradingCharts

Style Guide

Conventions that make a ChartScript source readable, diffable, and reviewable.

File shape

//@version=1
// <one-line description>
// <author / licence>

indicator("Title", overlay=true)

// ─── Constants ─────────────────────────────────────────────────────────
color BULL = color.lime
color BEAR = color.red

// ─── Inputs ────────────────────────────────────────────────────────────
length  = input.int(14, "Length", minval=1)
source  = input.source(close, "Source")

// ─── Helpers ───────────────────────────────────────────────────────────
smooth(src, len) =>
    ta.ema(src, len)

// ─── Calculations ──────────────────────────────────────────────────────
sm = smooth(source, length)

// ─── Visuals ───────────────────────────────────────────────────────────
plot(sm, "Smoothed", color=color.aqua, linewidth=2)

// ─── Alerts ────────────────────────────────────────────────────────────
alertcondition(ta.crossover(close, sm), "Cross up", "{{ticker}} crossed above its smoothed series")

The six-section layout (Constants / Inputs / Helpers / Calculations / Visuals / Alerts) scales — add Risk / Strategy sections after Visuals for strategies, Imports before Constants for library-consuming scripts.

Naming

  • lowerCamelCase for variables and parameters: kernelLength, isTrendingUp, smoothedRsi.
  • UPPER_SNAKE_CASE for script-level constants: MAX_ZONES, BULL_COLOR.
  • PascalCase for UDT and enum type names: type Pivot, enum Trend.
  • Prefix variables with their unit when they could be confused: len vs lenBars vs lenPct vs lenPrice.
  • Suffix input ids with their intent: showMa (bool), maLen (int), maColor (color).

Formatting

  • 4-space indentation.
  • One blank line between logical sections; two blank lines are unnecessary.
  • Line length under ~110 characters is a target, not a rule.
  • Align = within a block of consecutive declarations if it helps readability — but don't fight the editor to maintain alignment.
length  = input.int   (14,     "Length", minval=1)
source  = input.source(close,  "Source")
mult    = input.float (2.0,    "Multiplier", step=0.1)

Comments

  • Describe why, not what.
  • A comment that restates the code (ma = ta.sma(close, 20) // compute the SMA) is noise.
  • A comment that explains the choice (// We use SMA 20 — not EMA — because the user sees it as "flat".) is signal.

Function design

  • A pure function (depends only on parameters + current-bar built-ins) is almost always a win over a function that reads top-level state.
  • Return a tuple instead of mutating globals: [mid, up, lo] = bollinger(src, len, mult).
  • Keep bodies to one screen's worth; split a long calculation into named helpers.

Inputs

  • Every input has a title=.
  • Related inputs share a group=; booleans + their colour swatches share an inline=.
  • Use tooltip= for anything whose unit or effect isn't obvious.
  • Use active= to grey out dependent controls instead of silently ignoring them.
  • Pin numeric input ranges with minval=/maxval=.

Plots

  • Every plot has a title=.
  • Pick a palette and stick to it — a script that uses 7 different random greens is harder to read than one that uses color.lime + color.green consistently.
  • Use display.data_window when the value is useful but noisy on the pane itself.
  • trackprice=true is for last-value trackers; hline is for constant levels. Don't mix.

Strategies

  • Decide once whether orders fill at bar close (process_orders_on_close=true) or next bar's open (default). State the choice in a header comment.
  • initial_capital, default_qty_type, default_qty_value, commission_type, commission_value, slippage always set explicitly — defaults are easy to miss.
  • Guard entries with barstate.isconfirmed unless you deliberately want tick-level signalling.

Libraries

  • One concept per library (Zones, Pivots, Signal, …) rather than a kitchen sink.
  • Export every type a function signature references — the compiler enforces it for UDTs, but it reads cleaner too.
  • Keep bodies pure and typed.

Example of applying the style

//@version=1
// Supertrend with crossover alerts.
// Signals confirm on bar close; stops are placed `atrMult` × ATR from the mid price.

indicator("Supertrend", overlay=true, max_lines_count=100)

// ─── Inputs ────────────────────────────────────────────────────────────
grpCore   = "Core"
grpStyle  = "Style"

atrLen    = input.int   (10,  "ATR length",     minval=1, group=grpCore)
atrMult   = input.float (3.0, "ATR multiplier", minval=0.1, step=0.1, group=grpCore)

colUp     = input.color (color.lime, "Bullish", group=grpStyle, inline="col")
colDn     = input.color (color.red,  "Bearish", group=grpStyle, inline="col")

// ─── Calculations ──────────────────────────────────────────────────────
[trend, dir] = ta.supertrend(atrMult, atrLen)

// ─── Visuals ───────────────────────────────────────────────────────────
plot(trend, "Supertrend", color = dir > 0 ? colUp : colDn, linewidth = 2)

// ─── Alerts ────────────────────────────────────────────────────────────
alertcondition(ta.change(dir) ==  2, "Flip up",   "{{ticker}} Supertrend flipped bullish")
alertcondition(ta.change(dir) == -2, "Flip down", "{{ticker}} Supertrend flipped bearish")

See also