Syntax and Types
1Syntax and Types
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.
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")”.
#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:
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 = 1you cannot assignx := 2.5:xis an integer. Writex = 1.0orfloat 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 / 2equals3. For a fraction, make one operand fractional:7.0 / 2. - Exponentiation
^always yields a float:2 ^ 3equals8.0. - A string cannot be concatenated with a number.
"Цена: " + closewon’t compile — write"Цена: " + str.tostring(close). - A condition must be
bool.if volume { … }is an error; writeif 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.
indicator("Период из настроек")
len = input.int(14, "Период", minval = 2)
plot(ta.rsi(close, len), "RSI")
// ta.rsi(close, bar_index) не скомпилируется:
// «length» в «ta.rsi» требует значение не слабее input, а передано seriesna — 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
nayieldsna:na + 1isna. - Any comparison with
nais false, soif x > 0won’t execute whenx = na. - Check absence only via
na(x). Writingx == nais rejected by the compiler with the error “Comparison with na is always false — use na(x)”. nz(x)substitutes 0 forna,nz(x, v)substitutesv. The same, shorter:x ?? v.naon a chart is a gap in the line, not zero.
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.
indicator("Наклон средней")
ma = ta.ema(close, 20)
slope = ma - ma[1] // изменение за один бар
plot(slope, "Наклон", style = plot.style_histogram,
color = slope >= 0 ? color.teal : color.red)- If
nis greater than the number of bars before history starts, the result isna, not an error. ncan vary from bar to bar:close[len]with a variablelenis 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 < cwritea < b and b < c. andandordon’t evaluate the right operand if the answer is already determined by the left one.- Exponentiation binds tighter than unary minus:
-2 ^ 2equals-4. For4, write(-2) ^ 2. - Compare fractional numbers with a tolerance:
math.approx(a, b), because0.1 + 0.2 == 0.3is false.
Conditionals
Blocks always use curly braces. if can be a statement and an expression; for a simple binary choice there is ? :.
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.
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.
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)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.
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.*orvar, two calls to the same function maintain independent calculations:
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.
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.
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.
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:
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:
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:.
indicator("Перенос строк")
typical = high
+ low
+ close
signal = close > open
? typical / 3
: na
plot(signal, "Типичная цена на зелёных барах", style = plot.style_circles)Next
https://docs.lootx.trade/en/loot/syntax