# kdscript reference (v1.5)

kdscript is kdraigo's language for chart indicators. A script runs once per bar, oldest bar first,
and draws on a board chart, over the candles or in a pane below: plots, and drawn objects (lines,
boxes, labels) for zones and levels. It reads like Pine Script
v6, so most indicator code carries over, but it is a separate, smaller language. What it
leaves out is listed at the end.

```
indicator("EMA spread", overlay=false)
len = input.int(20, "Length", minval=2)
spread = close - ta.ema(close, len)
plot(spread, "Spread", style=plot.style_histogram)
hline(0)
```

Every script calls `indicator(...)` once and draws something: a plot, a shape or a drawn object. Saving a script checks it
and never runs it. Errors are given as `line 4:12: message`; fix them and save again.

## Layout

- One statement per line. Blocks (the body of `if`, `for`, `while` or a function) are indented
  4 spaces (a tab counts as 4).
- `//` starts a comment.
- A long line continues on the next one when it is inside brackets, when it ends with an
  operator or a comma, or when the next line is indented by a width that is not a multiple of 4.

## Values

| Type | Examples | Notes |
|---|---|---|
| int | `14`, `bar_index` | `/` always gives a float; `int(x)` truncates |
| float | `2.5`, `1e-3`, `close` | |
| bool | `true`, `close > open` | never na: na reads as false |
| color | `#2962ff`, `#2962ff80`, `color.red`, `color.new(color.blue, 80)`, `color.rgb(41, 98, 255)`, `color.from_gradient(rsi, 30, 70, color.red, color.green)` | `color.new` takes a transparency of 0–100; a colour may change from bar to bar |
| string | `"Length"`, a string input, `"BOS " + str.tostring(close, "#.##")` | joined with `+`, compared with `==`; see Strings |
| line, box, label | `line.new(…)`, `box.new(…)`, `label.new(…)` | drawn objects; see Drawing objects |
| array | `array.new_float()`, `array.from(1, 2, 3)`, `array.new_box()` | ints, floats, bools, lines, boxes or labels; see Arrays |

`na` is a missing value. Arithmetic with na gives na; comparisons with na are false; dividing by
zero gives na. Test for it with `na(x)`; replace it with `nz(x)` (0) or `nz(x, y)`; carry the last
value forward with `fixnan(x)`.

Named colours: red, green, blue, orange, purple, teal, gray, white, black, yellow, aqua,
fuchsia, lime, maroon, navy, olive, silver.

## Variables

```
x = close - open          // declare; the value is computed again on every bar
float y = na              // a typed declaration (needed when the value is na)
var count = 0             // var: set on the first bar only, then kept across bars
count := count + 1        // change a variable that exists
count += 1                // also -=, *=, /=, %=
[macd, signal, hist] = ta.macd(close, 12, 26, 9)   // several values
```

A variable must be declared before it is used and can't be declared twice in the same block.
Built-in names (close, ta, color, plot, …) can't be reused.

## History

`x[n]` is the value of `x` n bars ago: `close[1]` is the previous close. It works on any variable
or expression (`(close - open)[2]`, `ta.sma(close, 5)[1]`). Before the first bar it is na. n
can change from bar to bar but can't exceed 5000, or the script's `max_bars_back`.

## Control flow

```
if close > open
    up := true
else if close < open
    up := false
else
    up := up[1]

level = if bar_index < 100
    1.0
else
    2.0                    // an if gives its last line's value; without else, na

side = close > open ? 1 : -1

total = 0.0
for i = 0 to 9             // both ends included; counts down when from > to
    total += close[i]
for i = 0 to 20 by 5       // step 5
    …
while cond
    …                      // break and continue work in for and while

avg = switch maType        // a switch on a value: the first arm that equals it
    "SMA" => ta.sma(close, len)
    "EMA" => ta.ema(close, len)
    => ta.wma(close, len)  // => alone is the default (last); none: na

switch                     // a switch on conditions
    close > open => up := true
    close < open =>
        up := false
```

Conditions must be bools: write `x != 0`, not `x`.

## Functions

```
spread(a, b) => a - b

zscore(src, len = 20) =>
    m = ta.sma(src, len)
    (src - m) / ta.stdev(src, len)

minmax(src, len) =>
    [ta.lowest(src, len), ta.highest(src, len)]
```

A function's last line is its value; `[a, b]` returns several. Functions are declared at the
top level, can't call themselves, and can read but not change top-level variables. Each place a
function is called keeps its own `var` variables and its own ta.* state, as in Pine.

## Inputs

Inputs are values a person changes per chart without editing the script. Assign each one to a
variable at the top level; the variable's name is the input's name in the widget.

| Call | Value |
|---|---|
| `input.int(defval, title, minval, maxval)` | a whole number |
| `input.float(defval, title, minval, maxval)` | a number |
| `input.bool(defval, title)` | true or false |
| `input.color(defval, title)` | a colour, set as `"#RRGGBB"` |
| `input.source(defval, title)` | a bar series by name: open, high, low, close, volume, hl2, hlc3, ohlc4, hlcc4, taker_buy_volume, trades, quote_volume |
| `input.string(defval, title, options=["SMA", "EMA"])` | one of the options; compare it with `ma == "EMA"` or `switch ma` |
| `input.timeframe(defval, title, options)` | a timeframe to choose: `1m` … `12h`, `1D`, `1W`, `1M` (or only those `options` lists); give it to `request.security`, or compare it like a string input |
| `input(defval, title)` | the kind follows defval |

Lengths of ta.* functions must be fixed: a number, an input, or arithmetic on those. A value that
changes from bar to bar can't be a length.

## Higher timeframes

```
ema4h = request.security(syminfo.tickerid, "4h", ta.ema(close, 50))
trend(len) => ta.ema(close, len) > ta.ema(close, len * 2)
dailyUp = request.security(syminfo.tickerid, "1d", trend(20))
[h, l] = request.security(syminfo.tickerid, "1w", hl())   // hl() => [high, low]
```

- `request.security(syminfo.tickerid, timeframe, expression)` runs the expression on a higher
  timeframe of this chart's pair. Timeframes: `1m 3m 5m 15m 30m 1h 2h 3h 4h 6h 8h 12h 1d 1w 1M`
  (Pine's `"240"`, `"D"`, `"W"`, `"M"` too), the same as the chart's or higher, and a whole number
  of its bars; `1M` is the calendar month (UTC) and needs a chart of a day or less. The timeframe
  is text in quotes or an `input.timeframe`: `tf = input.timeframe("1D", "Levels")` then
  `request.security(syminfo.tickerid, tf, high[1])`. Inside the expression `timeframe.*` is the
  higher timeframe, as in Pine. On a chart where it isn't (a 4h level on a 1d chart), the call gives na and the run
  warns once; the rest of the script runs, so optional higher-timeframe levels can stay in a
  script used on any chart.
- **Closed bars only:** a chart bar reads the last higher bar that has closed by the end of that
  chart bar. The higher bar still forming never shows, so nothing repaints. `gaps=barmerge.gaps_on`
  gives the value only on the chart bar where a higher bar closed (na between; the default
  `barmerge.gaps_off` holds it). `lookahead=barmerge.lookahead_off` is the default and the only
  one: `barmerge.lookahead_on` is refused.
- The higher bars are built from the chart's own bars (UTC boundaries, weeks from Monday); a
  bucket the loaded history starts inside is left out, so the first values need enough bars.
- The expression may use bar data, ta.*, math, inputs, constants and the script's functions,
  not the script's variables: write it as a function (`trend(20)` above). Other pairs are not
  in this version.

## Arrays

```
var closes = array.new_float()      // var: one array kept across bars
closes.push(close)                  // a.f(x) is array.f(a, x)
if closes.size() > 20
    closes.shift()
med = closes.avg()
for [i, x] in closes                // or: for x in closes
    …
```

| Function | Value |
|---|---|
| `array.new_float(size, initial_value)`, `array.new_int(…)`, `array.new_bool(…)`, `array.new_string(…)`, `array.new_line()`, `array.new_box()`, `array.new_label()`, `array.from(a, b, …)`, `array.copy(a)` | a new array |
| `array.push(a, x)`, `array.unshift(a, x)`, `array.insert(a, i, x)`, `array.set(a, i, x)`, `array.clear(a)` | change it |
| `array.pop(a)`, `array.shift(a)`, `array.remove(a, i)` | take a value out |
| `array.get(a, i)`, `array.first(a)`, `array.last(a)`, `array.size(a)`, `array.includes(a, x)`, `array.indexof(a, x)` | read it |
| `array.sum(a)`, `array.avg(a)`, `array.min(a)`, `array.max(a)` | over its values, na skipped |
| `array.join(a, separator)` | its values as text, joined |

Indexes start at 0; one outside the array stops the script with an error. An array made without
`var` is made again on every bar. Arrays have no history (`a[1]`), and their values count against
the run's memory.

## Strings

```
txt = "BOS " + str.tostring(close, "#,###.##")     // "BOS 78,482.5"
var string last = ""                                 // a string variable that changes
last := close > open ? "up" : "down"
msg = str.format("{0} at {1,number,#.##}", last, close)
```

`+` joins two strings; a number becomes one with `str.tostring`. `==` and `!=` compare text.

| Function | Value |
|---|---|
| `str.tostring(value, format)` | a number or bool as text. format: a pattern (`"#"`, `"#.##"` up to two decimals, `"#.00"` exactly two, `"#,###"` grouped, `"$#,###.00"`, `"#.#%"` times 100), or `format.percent`, `format.volume` (1.23K, 4.5M), `format.price` and `format.mintick` (the script's `precision`, else as many decimals as needed, up to 8). Left out: as many as needed |
| `str.format(pattern, a, b, …)` | the pattern with `{0}`, `{1}`, … replaced; `{0,number,#.##}` formats a number, also `{0,number,integer}` and `{0,number,percent}` |
| `str.length(s)`, `str.contains(s, x)`, `str.startswith(s, x)`, `str.endswith(s, x)` | about the text |
| `str.upper(s)`, `str.lower(s)`, `str.replace_all(s, target, replacement)`, `str.substring(s, begin, end)` | new text |
| `str.tonumber(s)` | the number it spells (commas allowed), or na |

A string is at most 4096 bytes. Each different string the script builds counts against its
memory, so build labels' text where a label is made or changed, not on every bar.

## Bar data

| Name | Value |
|---|---|
| `open`, `high`, `low`, `close`, `volume` | the candle (volume in the base asset) |
| `hl2`, `hlc3`, `ohlc4`, `hlcc4` | (h+l)/2, (h+l+c)/3, (o+h+l+c)/4, (h+l+2c)/4 |
| `taker_buy_volume` | base volume bought by takers; `2 * taker_buy_volume - volume` is the bar's delta |
| `trades` | number of trades |
| `quote_volume` | volume in the quote asset |
| `funding` | the funding rate in force on a perpetual (0.0001 = 0.01%); na on spot |
| `time` | the bar's open time, Unix milliseconds |
| `bar_index` | 0 on the first bar |
| `year`, `month`, `dayofmonth`, `dayofweek`, `hour`, `minute` | parts of the open time in UTC; dayofweek 1 = Sunday |

## Bar state and timeframe

```
var label last = na
if barstate.islast                      // once, on the newest bar
    label.delete(last)
    last := label.new(bar_index, high, timeframe.period + " close " + str.tostring(close))
newDay = timeframe.change("D")          // the first bar of each UTC day
```

| Name | Value |
|---|---|
| `barstate.isfirst` | the first bar |
| `barstate.islast` | the newest bar the run has been given: the chart's last closed bar, or the forming bar while it updates |
| `barstate.islastconfirmedhistory` | the newest closed bar |
| `barstate.isconfirmed`, `barstate.isnew`, `barstate.ishistory` | a closed bar (true) or the forming one (false) |
| `barstate.isrealtime` | the forming bar |
| `last_bar_index`, `last_bar_time` | the newest bar's `bar_index` and open time (Unix ms) |
| `timeframe.period` | the chart's timeframe as Pine writes it: `"15"`, `"60"`, `"240"`, `"D"`, `"W"`, `"M"` |
| `timeframe.multiplier` | minutes on an intraday chart, else the number of days, weeks or months |
| `timeframe.isseconds`, `timeframe.isminutes`, `timeframe.isintraday`, `timeframe.isdaily`, `timeframe.isweekly`, `timeframe.ismonthly`, `timeframe.isdwm` | what kind of timeframe the chart is |
| `timeframe.in_seconds(timeframe)` | a timeframe's length in seconds (`"4h"` is 14400; a month 2628003); the chart's when left out |
| `timeframe.change(timeframe)` | true on the bar that opens a new bar of the timeframe: `timeframe.change("W")` on each Monday |

A chart that gets newer bars moves "the newest" on, so draw once at the end by deleting the
previous object and making a new one on `barstate.islast`, as above. `barstate.*` and
`last_bar_*` are the chart's: not inside `request.security`.

## Built-in functions

Moving averages and smoothing (source defaults to close where shown as `src`):

| Function | Value |
|---|---|
| `ta.sma(src, length)` | simple average of the last length non-na values |
| `ta.ema(src, length)` | exponential average, seeded with the SMA |
| `ta.rma(src, length)` | Wilder's average (RSI, ATR) |
| `ta.wma(src, length)` | linearly weighted average |
| `ta.vwma(src, length)` | volume-weighted average |
| `ta.hma(src, length)` | Hull average |
| `ta.linreg(src, length, offset)` | the least-squares line through the last length values, read offset bars back from the newest (0: at this bar); na while an na is in the window |
| `ta.vwap`, `ta.vwap(src)`, `ta.vwap(src, anchor)` | VWAP from the start of each UTC day (src defaults to hlc3), or from each bar where the bool anchor is true |

Oscillators and volatility:

| Function | Value |
|---|---|
| `ta.rsi(src, length)` | relative strength index, 0–100 |
| `ta.macd(src, fastlen, slowlen, siglen)` | `[line, signal, histogram]` |
| `ta.stoch(src, high, low, length)` | %K, 0–100 |
| `ta.cci(src, length)` | commodity channel index |
| `ta.dmi(diLength, adxSmoothing)` | `[+DI, −DI, ADX]` |
| `ta.tr`, `ta.tr(handle_na)` | true range (na on the first bar unless handle_na) |
| `ta.atr(length)` | average true range |
| `ta.stdev(src, length, biased=true)`, `ta.variance(src, length, biased=true)` | standard deviation, variance |
| `ta.dev(src, length)` | mean absolute deviation from the SMA |
| `ta.bb(src, length, mult)` | Bollinger `[basis, upper, lower]` |
| `ta.supertrend(factor, atrPeriod)` | `[line, direction]`; direction −1 is an uptrend |
| `ta.correlation(src1, src2, length)` | Pearson correlation of the two over length bars, −1 to 1; 0 when they barely co-vary (covariance within 1e-10 of zero, as when one is flat) |

Windows, changes and events:

| Function | Value |
|---|---|
| `ta.highest(src, length)`, `ta.lowest(src, length)` | extreme of the window (`ta.highest(length)` reads high, `ta.lowest(length)` low) |
| `ta.highestbars(src, length)`, `ta.lowestbars(src, length)` | how far back the extreme is, as 0 or a negative number |
| `ta.change(src, length=1)`, `ta.mom(src, length)` | src − src[length] |
| `ta.roc(src, length)` | percent change over length bars |
| `ta.percentrank(src, length)` | percent of the previous length values at or below this one, 0–100 |
| `ta.cum(src)` | running total (na adds nothing) |
| `math.sum(src, length)` | sum of the last length non-na values |
| `ta.crossover(a, b)`, `ta.crossunder(a, b)`, `ta.cross(a, b)` | a crossed above, below, or either way |
| `ta.valuewhen(cond, src, occurrence)` | src when cond was last true (occurrence 0), or the time before (1), … |
| `ta.barssince(cond)` | bars since cond was true; na before the first time |
| `ta.pivothigh(src, leftbars, rightbars)`, `ta.pivotlow(…)` | the swing's value, reported rightbars bars after it (the two-number form reads high/low) |

ta.* functions keep their state per call: call them on every bar (not only inside an if) when
you want them to see every bar. Their warm-up, seeding and na rules follow TradingView's.

Math: `math.abs`, `math.min(a, b, …)`, `math.max(a, b, …)`, `math.avg(a, …)`, `math.sqrt`,
`math.pow(base, exponent)`, `math.log`, `math.log10`, `math.exp`, `math.round(x)` (or
`math.round(x, precision)`), `math.floor`, `math.ceil`, `math.sign`, `math.sin`, `math.cos`,
`math.tan`, `math.atan`; constants `math.pi`, `math.e`, `math.phi`. Conversions `int(x)`,
`float(x)`. Also `na(x)`, `nz(x, y)`, `fixnan(x)`.

## Drawing

These calls are at the top level of the script (not in a block or function). At most 64 plots,
fills and shapes per script; drawn objects (below) don't count.

| Call | Draws |
|---|---|
| `indicator(title, shorttitle, overlay=false, precision, max_bars_back)` | overlay=true draws over the candles; false gives the script a pane named by shorttitle or title |
| `plot(series, title, color, linewidth, style, linestyle, display, offset)` | a line; `style=plot.style_stepline`, `plot.style_histogram` (or `plot.style_columns`), `plot.style_area`, `plot.style_circles`, `plot.style_linebr` (a line that stops at na bars; `plot.style_line`, the default, joins across them); `linestyle=plot.linestyle_dashed`/`_dotted`; `display=display.none` hides it (for fill). `color` may change per bar: `color = mom > 0 ? color.green : color.red` |
| `hline(price, title, color, linestyle)` | a horizontal level, drawn with the script's first plot |
| `fill(plot1, plot2, color, title)` | the band between two plots or two hlines (assign them: `p = plot(…)`) |
| `plotshape(series, title, style, location, color, text, offset)` | a marker on each bar where the bool series is true (or a number series is neither na nor 0); `style=shape.triangleup`, `triangledown`, `arrowup`, `arrowdown`, `circle`, `square`, …; `location=location.abovebar`, `belowbar` or `absolute` (at the series' value); `color` may change per bar |
| `plotchar(series, title, char, location, color, text)` | a character as the marker |
| `bgcolor(color, offset, title)` | shades each bar's background (the script's pane, or the candles' for an overlay); na leaves a bar unshaded |
| `barcolor(color, offset, title)` | colours each candle; na leaves it as it is. In Pine's order the second argument is `offset`; a string there is taken as the title (`barcolor(c, "Trend")`), or write `title=` |

`offset` draws each value that many bars to the right, or with a negative number to the left: a
swing high found 5 bars late (`ta.pivothigh(high, 5, 5)`) is drawn on the bar where it formed
with `offset=-5` (or `offset=-len` with an input). It is a whole number from -5000 to 500, for `plot`, `plotshape`, `plotchar`,
`bgcolor` and `barcolor` (not for the plots of a `fill`). The value is still known only on the
later bar, and alerts see it there.

Na values leave gaps in histograms, markers and `plot.style_linebr` lines; a plain line joins
across them. A level that jumps (a stepped line with no slanted joins) is drawn by leaving the
bar where it changes out: `plot(lvl == lvl[1] ? lvl : na, style=plot.style_linebr)`. Histograms without a colour are green above zero and red below. `fill` and `hline`
take one colour each.

## Drawing objects

Lines, boxes and labels are objects the script makes and changes as it runs: an order block is a
box, a break of structure a line from the swing to the break with a label on it. Unlike plots
they can be made anywhere (in an `if`, a loop or a function), kept in variables and arrays,
moved, extended and deleted. The chart shows those that exist after the last closed bar.

```
indicator("Breaks", overlay=true, max_boxes_count=100)
var zones = array.new_box()
ph = ta.pivothigh(high, 5, 5)
if not na(ph)
    zones.push(box.new(bar_index - 5, ph, bar_index, ph * 0.998, border_color=color.red,
         bgcolor=color.new(color.red, 85), extend=extend.right))
if zones.size() > 0
    for i = zones.size() - 1 to 0
        b = zones.get(i)
        if close > b.get_top()
            line.new(b.get_left(), b.get_top(), bar_index, b.get_top(), color=color.green, style=line.style_dashed)
            label.new(bar_index, b.get_top(), "BOS " + str.tostring(b.get_top(), "#.##"))
            b.delete()
            zones.remove(i)
```

`x` is a bar index (`bar_index`, `bar_index - 5`; `xloc=xloc.bar_index`, the default) or, with `xloc=xloc.bar_time`, a time; at most
500 bars right of the bar being run. `b.f(x)` is `box.f(b, x)` on a variable holding one, as for
arrays. A deleted object, or na, is skipped by the setters and reads na from the getters.

| Function | Draws or does |
|---|---|
| `line.new(x1, y1, x2, y2, xloc, extend, color, style, width)` | a line; `style=line.style_solid`, `line.style_dashed`, `line.style_dotted`, `line.style_arrow_left`, `line.style_arrow_right`, `line.style_arrow_both`; `extend=extend.none`, `extend.left`, `extend.right`, `extend.both` |
| `line.set_x1`, `line.set_y1`, `line.set_x2`, `line.set_y2`, `line.set_xy1(id, x, y)`, `line.set_xy2`, `line.set_xloc(id, x1, x2, xloc)`, `line.set_color`, `line.set_width`, `line.set_style`, `line.set_extend` | change it |
| `line.get_x1`, `line.get_y1`, `line.get_x2`, `line.get_y2`, `line.get_price(id, x)` | read it; `get_price` is its price at bar x |
| `box.new(left, top, right, bottom, border_color, border_width, border_style, extend, xloc, bgcolor, text, text_size, text_color)` | a rectangle; `bgcolor` fills it, `text` is written inside |
| `box.set_left`, `box.set_right`, `box.set_top`, `box.set_bottom`, `box.set_lefttop(id, left, top)`, `box.set_rightbottom(id, right, bottom)`, `box.set_xloc(id, left, right, xloc)`, `box.set_bgcolor`, `box.set_border_color`, `box.set_border_width`, `box.set_border_style`, `box.set_extend`, `box.set_text`, `box.set_text_color`, `box.set_text_size` | change it |
| `box.get_left`, `box.get_right`, `box.get_top`, `box.get_bottom` | read it |
| `label.new(x, y, text, xloc, yloc, color, style, textcolor, size)` | a text label; `yloc=yloc.price` (at y), `yloc.abovebar`, `yloc.belowbar`; `style=label.style_label_down` (a bubble pointing down at y), `label.style_label_up`, `label.style_label_left`, `label.style_label_right`, `label.style_label_center`, `label.style_none` (the text alone), …; `size=size.tiny`, `size.small`, `size.normal`, `size.large`, `size.huge` |
| `label.set_x`, `label.set_y`, `label.set_xy(id, x, y)`, `label.set_xloc(id, x, xloc)`, `label.set_text`, `label.set_color`, `label.set_textcolor`, `label.set_style`, `label.set_size`, `label.set_yloc` (`label.set_tooltip` is accepted and not shown) | change it |
| `label.get_x`, `label.get_y`, `label.get_text` | read it |
| `line.delete(id)`, `box.delete(id)`, `label.delete(id)` | delete it |
| `line.copy(id)`, `box.copy(id)`, `label.copy(id)` | a new one like it |

Each kind keeps at most 50 objects; `indicator(…, max_lines_count=…, max_boxes_count=…,
max_labels_count=…)` raises it up to 500. Making one more deletes the oldest of its kind, as in
Pine, so keep the ones you track in an array and delete them yourself when they are done.
Drawn objects can't be made inside `request.security`: return the values and draw outside it.
A forming bar's changes to them are not shown until the bar closes.

## Alerts

`alertcondition(condition, title, message)` declares a condition you can arm as an alert on a
chart (a pair and timeframe). The platform checks it after each closed bar and sends the message
to your Telegram while it is true. It sits at the top level, draws nothing and fires nothing by
itself. Up to 20 per script; each needs its own title, which is what you arm.

```
indicator("Squeeze momentum", "SQZ")
basis = ta.sma(close, 20)
dev = ta.stdev(close, 20)
squeeze = dev < ta.sma(dev, 50)
mom = ta.linreg(close - basis, 20, 0)
plot(mom, "Momentum", style=plot.style_histogram)
alertcondition(squeeze[1] and not squeeze, "Squeeze fired", '{{ticker}} {{interval}}: squeeze fired, momentum {{plot("Momentum")}}')
```

The message may use `{{ticker}}`, `{{exchange}}`, `{{interval}}`, `{{time}}` (the bar's open,
UTC), `{{open}}`, `{{high}}`, `{{low}}`, `{{close}}`, `{{volume}}` and `{{plot("Title")}}` (that
plot's value on the bar). Write it in single quotes when it names a plot, or escape the inner
quotes (`\"`). Left out, the message is the title. Conditions are checked on closed bars only,
so an alert never fires on a bar that later changes its mind. It fires on every closed bar
while the condition is true; for "only when it turns true", write `cond and not cond[1]`.

## Limits

- Source up to 64 KB; 64 drawn outputs (plots, fills, shapes, chars, bgcolor, barcolor); 500
  each of lines, boxes and labels; history up to 5000 bars; ta.* lengths 1–5000; arrays up to
  100,000 values; strings up to 4096 bytes.
- Each run has a step budget and a time limit. A loop that runs too long stops the script with
  an error naming the loop's line, and only that chart tile shows it.
- A script never reaches the network, files, the clock or the page: it sees the bars and inputs.

## Not in this version

`request.security` for other pairs and lower timeframes, reading the script's variables inside
`request.security`, `strategy.*` (strategies are Go programs on dev_sdk), tables, polylines and
`chart.point`, matrices and maps, user types and methods (other than the array and drawing
methods), free-text string inputs, `varip`, `alert()` (use `alertcondition`) and libraries.

## Examples

### bollinger

```
indicator("Bollinger", overlay=true)
len = input.int(20, "Length", minval=2)
mult = input.float(2.0, "Deviations", minval=0.5, maxval=5)
[basis, upper, lower] = ta.bb(close, len, mult)
plot(basis, "Basis")
u = plot(upper, "Upper")
l = plot(lower, "Lower")
fill(u, l, color=color.new(color.blue, 90))
```

### cvd

```
indicator("Cumulative volume delta", "CVD")
// Taker buys minus taker sells, summed from the first bar.
delta = 2 * taker_buy_volume - volume
plot(ta.cum(delta), "CVD")
plot(delta, "Delta", style=plot.style_histogram)
```

### ema-cross

```
indicator("EMA cross", overlay=true)
fastLen = input.int(9, "Fast", minval=1)
slowLen = input.int(21, "Slow", minval=2)
fast = ta.ema(close, fastLen)
slow = ta.ema(close, slowLen)
plot(fast, "Fast")
plot(slow, "Slow")
plotshape(ta.crossover(fast, slow), "Up", shape.triangleup, location.belowbar, text="up")
plotshape(ta.crossunder(fast, slow), "Down", shape.triangledown, location.abovebar, text="down")
```

### funding-z

```
indicator("Funding z-score", "Funding z")
// How unusual funding is against its last 3 days (perpetuals; na on spot).
len = input.int(72, "Bars", minval=10)
f = funding * 100
z = (f - ta.sma(f, len)) / ta.stdev(f, len)
plot(z, "z", style=plot.style_histogram)
hline(2, "High")
hline(-2, "Low")
```

### htf-trend

```
indicator("4h trend", overlay=true)
len = input.int(50, "EMA length", minval=2)
tf = input.string("4h", "Higher timeframe", options=["1h", "4h", "1d"])
ema(n) => ta.ema(close, n)
e1h = request.security(syminfo.tickerid, "1h", ema(len))
e4h = request.security(syminfo.tickerid, "4h", ema(len))
e1d = request.security(syminfo.tickerid, "1d", ema(len))
htf = switch tf
    "1h" => e1h
    "4h" => e4h
    => e1d
plot(htf, "Higher EMA", color=close > htf ? color.green : color.red, style=plot.style_stepline)
bgcolor(close > htf ? color.new(color.green, 90) : color.new(color.red, 90))
```

### ma-choice

```
indicator("Moving average", overlay=true)
kind = input.string("EMA", "Type", options=["SMA", "EMA", "WMA", "HMA"])
len = input.int(21, "Length", minval=2)
ma = switch kind
    "SMA" => ta.sma(close, len)
    "EMA" => ta.ema(close, len)
    "WMA" => ta.wma(close, len)
    => ta.hma(close, len)
plot(ma, kind, color=color.from_gradient(ta.rsi(close, 14), 30, 70, color.red, color.green))
barcolor(close > ma ? color.teal : color.gray)
```

### momentum-shades

```
indicator("Momentum shades", "Momentum")
len = input.int(20, "Length", minval=2)
mom = close - ta.sma(close, len)
// Four shades: rising or falling, above or below zero.
col = mom >= 0 ? (mom > mom[1] ? color.lime : color.green) : (mom < mom[1] ? color.red : color.maroon)
plot(mom, "Momentum", color=col, style=plot.style_histogram)
// Squeeze dots on the zero line: one series, coloured per bar.
squeeze = ta.stdev(close, len) < ta.sma(ta.stdev(close, len), 100)
plot(0, "Squeeze", color=squeeze ? color.orange : color.gray, style=plot.style_circles)
bgcolor(squeeze ? color.new(color.orange, 92) : na)
```

### pivot-memory

```
indicator("Average of recent swing highs", overlay=true)
keep = input.int(5, "Swings", minval=1)
var highs = array.new_float()
ph = ta.pivothigh(5, 5)
if not na(ph)
    highs.push(ph)
    if highs.size() > keep
        highs.shift()
plot(highs.size() > 0 ? highs.avg() : na, "Swing high average", style=plot.style_stepline)
```

### rsi

```
indicator("RSI", "RSI", overlay=false, precision=1)
len = input.int(14, "Length", minval=2)
r = ta.rsi(close, len)
plot(r, "RSI")
upper = hline(70, "Overbought")
lower = hline(30, "Oversold")
fill(upper, lower, color=color.new(color.purple, 90))
```

### supertrend

```
indicator("SuperTrend", overlay=true)
factor = input.float(3.0, "Factor")
atrLen = input.int(10, "ATR length", minval=1)
[st, dir] = ta.supertrend(factor, atrLen)
plot(dir < 0 ? st : na, "Up", color=color.green, style=plot.style_linebr)
plot(dir > 0 ? st : na, "Down", color=color.red, style=plot.style_linebr)
```

### volume-spikes

```
indicator("Volume spikes", overlay=true)
len = input.int(50, "Average over", minval=2)
k = input.float(3.0, "Times the average")
avg = ta.sma(volume, len)
spike = volume > k * avg
plotshape(spike and close > open, "Buy spike", shape.circle, location.belowbar, color=color.green)
plotshape(spike and close < open, "Sell spike", shape.circle, location.abovebar, color=color.red)
```
