LootX

Syntax and Types

new Updated September 30, 2026 This page as PDF

How LootScript works: bar-by-bar execution, script declaration, five assignment forms, types and qualifiers, na, history x[n], operators, conditionals, switch, loops, functions, and custom types.

This page is a brief language reference: how a script executes, how to declare variables, what types exist, what na means, how to read past bars, how to write conditionals, loops and functions. All examples are complete working scripts: you can paste them into the editor and add them to a chart.

How a Script Executes

Everything at the top level of a file runs once per bar — from the oldest loaded bar to the current one, line by line. So a variable in LootScript stores not a single number but a value per bar; past values are accessible.

Loot
indicator("Тело свечи")

body = close - open          // считается заново на каждом баре
plot(body, "Тело", style = plot.style_columns)

On the current, still-open bar the script recalculates on every new tick, starting from the state it had at the previous bar’s close. Closed bars are not recalculated.

Declaration

The first statement of a script is exactly one declaration: indicator(...), strategy(...) or library(...). Above it, only comments and an optional version string #version 1 may appear. Without a declaration the script won’t compile, showing the error “No script declaration — start with indicator("Name")”.

Loot
#version 1
// Комментарии и строка версии могут стоять выше объявления.
indicator("Размах бара", shortTitle = "Размах", overlay = false)

plot(high - low, "Размах", style = plot.style_histogram)

The main indicator parameters: title — the name in the script list, shortTitle — the short legend name, overlay — draw over price (true) or in a separate panel (false, default). Strategy parameters are described on the Strategies and Tester page.

Variables and Assignment

Form What it does
x = expression Declares a variable. On each bar it is recalculated.
x := expression Assigns a new value to an already declared variable. The type must match.
var x = expression Declares a variable once, on the first bar. Its value carries over to subsequent bars.
varip x = expression Like var, but the value also persists between ticks of the current bar.
[a, b, c] = function(...) Destructures multiple function results into variables.

To modify an already declared variable there are also shorthand forms: +=, -=, *=, /=, %=. The type can be specified explicitly: float level = 0. Names of built-in series and namespaces (close, volume, ta and others) are reserved — you cannot use them for your own variables.

The key difference from ordinary languages is that a normal variable does not accumulate value between bars. For counters and sums you need var:

Loot
indicator("Счётчик зелёных свечей")

var greens = 0          // объявляется один раз и переживает переход к следующему бару
if close > open {
    greens += 1
}

today = 0               // а эта переменная равна 0 на каждом новом баре
today := today + 1      // поэтому здесь всегда 1

plot(greens, "Зелёных с начала истории")

varip is rarely needed — for example, to count ticks inside a bar. There are no ticks in history, so its value on past bars will differ from what it was live. If a varip value is plotted, the editor warns about repainting.

Types

Type Stores Example
int integer 14, 1_000_000, 0xFF
float floating-point number 2.5, .5, 1e-9
bool true or false true, false
string text "BTC", 'BTC'
color color with transparency #FF9800, #FF980080, color.teal

There are also composite types: arrays array<float>, maps map<string, float>, matrices matrix<float>, plus your own types and enums (see below) and drawing objects line, box, label, table.

The rules that trip people up most often:

  • The type is fixed at declaration. After x = 1 you cannot assign x := 2.5: x is an integer. Write x = 1.0 or float x = 1.
  • An integer converts to float automatically, the reverse only explicitly: math.round, math.floor, math.ceil, math.trunc.
  • Integer division is integer: 7 / 2 equals 3. For a fraction, make one operand fractional: 7.0 / 2.
  • Exponentiation ^ always yields a float: 2 ^ 3 equals 8.0.
  • A string cannot be concatenated with a number. "Цена: " + close won’t compile — write "Цена: " + str.tostring(close).
  • A condition must be bool. if volume { … } is an error; write if volume > 0 { … }.

Qualifiers: How Constant a Value Is

Besides its type, each value has a qualifier — how constant it is during script execution. From weakest to strongest:

Qualifier When value is known Example
const already at compile time 14, "EMA"
input after reading settings, then unchanged input.int(14, "Период")
simple at startup, then unchanged step size of the instrument
series may change on every bar close, bar_index

In practice this matters in one place: lengths of ta.* functions must be constant — a literal or a setting. Otherwise the function would have to rebuild its state on every bar.

Loot
indicator("Период из настроек")

len = input.int(14, "Период", minval = 2)
plot(ta.rsi(close, len), "RSI")

// ta.rsi(close, bar_index) не скомпилируется:
// «length» в «ta.rsi» требует значение не слабее input, а передано series

na — No Value

na means “no value”: not enough history for the calculation, the exchange doesn’t provide the metric, or there is a gap in the data. na exists in any type.

  • Arithmetic with na yields na: na + 1 is na.
  • Any comparison with na is false, so if x > 0 won’t execute when x = na.
  • Check absence only via na(x). Writing x == na is rejected by the compiler with the error “Comparison with na is always false — use na(x)”.
  • nz(x) substitutes 0 for na, nz(x, v) substitutes v. The same, shorter: x ?? v.
  • na on a chart is a gap in the line, not zero.
Loot
indicator("Средняя без пустого начала", overlay = true)

avg = ta.sma(close, 50)             // на первых 49 барах — na

line1 = na(avg) ? close : avg       // проверка через na(x)
line2 = avg ?? close                // то же короче

plot(line2, "Средняя или цена", color = color.orange)

History: x[n]

x[n] is the value of x on the bar n bars ago; x[0] is the current one. Works with any series, including your own variables.

Loot
indicator("Наклон средней")

ma    = ta.ema(close, 20)
slope = ma - ma[1]                  // изменение за один бар

plot(slope, "Наклон", style = plot.style_histogram,
     color = slope >= 0 ? color.teal : color.red)
  • If n is greater than the number of bars before history starts, the result is na, not an error.
  • n can vary from bar to bar: close[len] with a variable len is allowed.
  • A negative index is an error: you cannot look into future bars.
  • You don’t need to configure history depth: the script has access to all history loaded on the chart.

Operators

From highest precedence to lowest:

Priority Operators
1 x[n], call f(…), dot access a.b
2 ^ — exponentiation, right-associative
3 unary -, +, not
4 *, /, %
5 +, -
6 <, >, <=, >=
7 ==, !=
8 and
9 or
10 ?? — na replacement
11 ? : — conditional expression
  • Comparisons do not chain: instead of a < b < c write a < b and b < c.
  • and and or don’t evaluate the right operand if the answer is already determined by the left one.
  • Exponentiation binds tighter than unary minus: -2 ^ 2 equals -4. For 4, write (-2) ^ 2.
  • Compare fractional numbers with a tolerance: math.approx(a, b), because 0.1 + 0.2 == 0.3 is false.

Conditionals

Blocks always use curly braces. if can be a statement and an expression; for a simple binary choice there is ? :.

Loot
indicator("Направление бара")

dir = 0
if close > open {
    dir := 1
} else if close < open {
    dir := -1
}

// if как выражение: обе ветки обязательны
bodySize = if close >= open { close - open } else { open - close }

// условный оператор для простых случаев
barColor = dir > 0 ? color.teal : color.red

plot(bodySize, "Тело", style = plot.style_columns, color = barColor)

switch

switch picks the first matching branch. The form with an object compares it against the branch values; the form without an object checks conditions in order. A => branch without a condition is the default branch. There is no fall-through to the next branch. When switch returns a value, the default branch is mandatory.

Loot
indicator("Средняя на выбор", overlay = true)

kind = input.string("EMA", "Тип средней", options = ["EMA", "SMA", "WMA"])

ma = switch kind {
    "EMA" => ta.ema(close, 20)
    "SMA" => ta.sma(close, 20)
    =>       ta.wma(close, 20)
}

zone = switch {
    close > ma * 1.02 => 1
    close < ma * 0.98 => -1
    =>                   0
}

plot(ma, "Средняя", color = zone == 1 ? color.teal : zone == -1 ? color.red : color.gray)

Loops

for i in a .. b iterates over numbers from a inclusive to b exclusive. The step is set with by, including negative. The loop variable exists only inside the loop and cannot be changed in the body.

Loot
indicator("Зелёные свечи за N баров")

n = input.int(10, "Сколько баров", minval = 1, maxval = 200)

greens = 0
for i in 0 .. n {               // i = 0, 1, …, n − 1
    if close[i] > open[i] {
        greens += 1
    }
}

plot(greens, "Зелёных", style = plot.style_columns)
Loot
indicator("Виды циклов")

total = 0
for i in 0 .. 10 by 2 {         // 0, 2, 4, 6, 8
    total += i
}
for i in 10 .. 0 by -1 {        // 10, 9, …, 1
    if i % 3 == 0 {
        continue                // сразу к следующему шагу
    }
    total += i
}

var closes = array.new<float>(0)
closes.push(close)
if closes.size() > 20 {
    closes.shift()
}
sum = 0.0
for v in closes {               // по элементам массива; for [i, v] in … — с индексом
    sum += v
}

k = 0
while k < 100 {
    k += 7
    if k > 50 {
        break                   // выход из цикла
    }
}

plot(sum / closes.size(), "Средняя из массива")

An infinite loop won’t hang the terminal: a script has an operations budget per bar and a time limit. When exceeded, the script stops with the message “Script exhausted per-bar operations budget — simplify the loop” or “Script exceeded per-pass time limit”.

Functions

A function is declared with func. The short form is an expression after =>, the long form is a body in braces with return. Parameter types can be omitted — they are inferred from calls, and parameters may have default values. Arguments are passed by position or by name.

Loot
indicator("Z-оценка объёма")

func barRange(h, l) => h - l

func zscore(series float src, input int len) => float {
    m = ta.sma(src, len)
    s = ta.stdev(src, len)
    return s == 0 ? na : (src - m) / s
}

func scaled(x, k = 2.0) => x * k

z = zscore(volume, 50)
plot(z, "Z объёма", style = plot.style_columns, color = z > 2 ? color.orange : color.gray)
plot(scaled(barRange(high, low), k = 0.5), "Полразмаха")
  • No recursion. A function cannot call itself, directly or through another function. Repetition is written with a loop.
  • State is per call. If a function contains ta.* or var, two calls to the same function maintain independent calculations:
Loot
indicator("Два вызова одной функции", overlay = true)

func smooth(src, len) => ta.ema(src, len)

fast = smooth(close, 10)        // своя средняя
slow = smooth(close, 30)        // другая, независимая средняя

plot(fast, "Быстрая", color = color.teal)
plot(slow, "Медленная", color = color.orange)

Functions needed in multiple scripts are moved into a library: a script with the declaration library("Имя", 1), export before functions, and in another script import Имя/1 as имя. The library is taken from your scripts folder.

Multiple Results

Some functions return multiple series at once: ta.bb, ta.macd, ta.kc, ta.dmi, ta.supertrend and others. Their result is destructured into variables in square brackets; the number of names must match the number of results.

Loot
indicator("Полосы Боллинджера", overlay = true)

[basis, upper, lower] = ta.bb(close, 20, 2.0)

plot(basis, "Середина", color = color.gray)
plot(upper, "Верх", color = color.teal)
plot(lower, "Низ", color = color.teal)

Custom Types and Enums

type collects several fields into one structure. An object is created via Имя.new(...), fields are read and modified via dot.

Loot
indicator("Последний локальный максимум", overlay = true)

type Swing {
    int   barIndex
    float price
}

var Swing last = Swing.new(barIndex = 0, price = high)

ph = ta.pivothigh(high, 5, 5)
if not na(ph) {
    last.barIndex := bar_index - 5
    last.price := ph
}

plot(last.price, "Максимум", style = plot.style_stepline, color = color.orange)

enum is a closed list of options with labels. It is safer than strings: a typo in a variant name is a compile error, not a silently failing condition. Combined with input.enum, an enum becomes a dropdown in settings — see Inputs and Drawing.

Loot
indicator("Режим рынка")

enum Regime {
    trend = "Тренд"
    flat  = "Флэт"
}

regime = math.abs(close - close[20]) > ta.atr(14) * 3 ? Regime.trend : Regime.flat

plot(regime == Regime.trend ? 1 : 0, "Тренд", style = plot.style_stepline)

ta.* Functions Inside Conditionals

Functions like ta.ema, ta.rsi or ta.atr remember their state from the previous bar. If such a call ran only on some bars — inside an if or one branch of ? : — its calculation would skip bars and silently produce wrong numbers.

LootScript does not allow this: the compiler hoists every ta.* call out of the conditional and runs it on every bar, and the condition only selects whether to use the result. So this code computes RSI correctly:

Loot
indicator("RSI только на зелёных барах")

r = close > open ? ta.rsi(close, 14) : na

plot(r, "RSI", style = plot.style_circles)

The call cannot be hoisted only if its argument itself is born inside a branch. Then the compiler does not guess, but reports: “Argument «len» cannot be computed outside the branch — hoist the ta.* call to the top level”. The solution is to compute the argument before the condition:

Loot
indicator("Аргумент до условия")

// Так нельзя — len объявлена внутри ветки:
// if close > open {
//     len = 10
//     e = ta.ema(close, len)
// }

len = 10
e = ta.ema(close, len)

plot(close > open ? e : na, "EMA на зелёных барах", style = plot.style_circles)

Comments and Line Breaks

  • // — comment to end of line, /* … */ — block comment, blocks can be nested.
  • A statement ends at end of line; a semicolon is not needed.
  • A long expression continues on the next line if the line is broken inside parentheses, ends with an operator, or the next line starts with an operator, ? or :.
Loot
indicator("Перенос строк")

typical = high
        + low
        + close

signal = close > open
    ? typical / 3
    : na

plot(signal, "Типичная цена на зелёных барах", style = plot.style_circles)

Next

FAQ

Why does my counter in the script always stay at 1?
A regular variable is reinitialized on every bar. To preserve the value across bars, declare it with var: var count = 0.
How do I check that a value is missing?
Use the function na(x). Writing x == na is rejected by the compiler: any comparison with na is false.
Can I call ta.rsi or ta.ema inside an if?
Yes. The compiler runs those calls on every bar; the if only selects whether to use the result, so the calculation remains correct.
Why won't a variable period work for ta.sma?
The period parameter for ta.* functions must be constant — either a literal number or an input.int. The compiler won't accept a value that changes from bar to bar.