Синтаксис і типи
Як влаштований LootScript: виконання по барах, оголошення скрипта, п’ять форм присвоєння, типи й кваліфікатори, na, історія x[n], оператори, умови, switch, цикли, функції й власні типи.
Ця сторінка — короткий опис мови: як виконується скрипт, як оголошувати змінні, які є типи, що таке na, як читати минулі бари, як писати умови, цикли й функції. Всі приклади — повні робочі скрипти: їх можна вставити в редактор і додати на графік.
Як виконується скрипт
Усе, що написане на верхньому рівні файлу, виконується один раз на кожен бар — від найстарішого завантаженого до поточного, рядок за рядком. Тому змінна в LootScript зберігає не одне число, а значення на кожному барі, і до минулих значень можна звертатися.
indicator("Тело свечи")
body = close - open // считается заново на каждом баре
plot(body, "Тело", style = plot.style_columns)На поточному, ще не закритому барі скрипт перераховується з кожним новим тіком, починаючи зі стану, яке було на закритті попереднього бара. Закриті бари при цьому не перераховуються.
Оголошення
Перша інструкція скрипта — рівно одне оголошення: indicator(...), strategy(...) або library(...). Вище нього можуть стояти тільки коментарі і необов’язковий рядок версії #version 1. Без оголошення скрипт не скомпілюється з помилкою «Немає оголошення скрипта — почніть з indicator(“Назва”)».
#version 1
// Комментарии и строка версии могут стоять выше объявления.
indicator("Размах бара", shortTitle = "Размах", overlay = false)
plot(high - low, "Размах", style = plot.style_histogram)Головні параметри індикатора: title — назва в списку скриптів, shortTitle — коротке ім’я для легенди, overlay — малювати поверх ціни (true) або в окремій панелі (false, за замовчуванням). Параметри стратегії описані на сторінці Стратегии и тестер.
Змінні і присвоєння
| Форма | Що робить |
|---|---|
x = выражение |
Оголошує змінну. На кожному барі вона обчислюється заново. |
x := выражение |
Присвоює нове значення вже оголошеній змінній. Тип має співпадати. |
var x = выражение |
Оголошує змінну один раз, на першому барі. Значення переходить на наступні бари. |
varip x = выражение |
Як var, але значення зберігається й між тіками поточного бара. |
[a, b, c] = функция(...) |
Розкладає кілька результатів функції по змінних. |
Для зміни вже оголошеної змінної є й короткі форми: +=, -=, *=, /=, %=. Тип можна вказати явно: float level = 0. Імена вбудованих рядів і просторів (close, volume, ta та інших) зайняті — назвати так свою змінну не можна.
Головна відмінність від звичних мов — звичайна змінна не накопичує значення між барами. Для лічильників і сум потрібен var:
indicator("Счётчик зелёных свечей")
var greens = 0 // объявляется один раз и переживает переход к следующему бару
if close > open {
greens += 1
}
today = 0 // а эта переменная равна 0 на каждом новом баре
today := today + 1 // поэтому здесь всегда 1
plot(greens, "Зелёных с начала истории")varip потрібен рідко — наприклад, щоб рахувати тіки всередині бара. На історії тиків немає, тому таке значення на минулих барах буде іншим, ніж було вживу. Якщо значення varip потрапляє на графік, редактор попередить про перерисовку.
Типи
| Тип | Що зберігає | Приклад |
|---|---|---|
int |
ціле число | 14, 1_000_000, 0xFF |
float |
дробове число | 2.5, .5, 1e-9 |
bool |
істина або неправда | true, false |
string |
текст | "BTC", 'BTC' |
color |
колір з прозорістю | #FF9800, #FF980080, color.teal |
Є й складні типи: масиви array<float>, словники map<string, float>, матриці matrix<float>, а також ваші власні типи й перерахування (див. нижче) і графічні об’єкти line, box, label, table.
Правила, через які найчастіше спотикаються:
- Тип фіксується при оголошенні. Після
x = 1присвоїтиx := 2.5не можна:x— ціле. Пишітьx = 1.0абоfloat x = 1. - Ціле перетворюється в дробове само, назад — тільки явно:
math.round,math.floor,math.ceil,math.trunc. - Ділення цілих — ціле:
7 / 2рівно3. Потрібна дробь — зробіть одне з чисел дробовим:7.0 / 2. - Піднесення до степеня
^завжди дає дробове число:2 ^ 3рівно8.0. - Рядок не можна додати до числа.
"Цена: " + closeне скомпілюється — пишіть"Цена: " + str.tostring(close). - Умова — тільки
bool.if volume { … }— помилка; пишітьif volume > 0 { … }.
Кваліфікатори: наскільки значення постійне
Окрім типу в кожного значення є кваліфікатор — наскільки воно постійне під час роботи скрипта. Від слабкого до сильного:
| Кваліфікатор | Коли відоме значення | Приклад |
|---|---|---|
const |
вже під час компіляції | 14, "EMA" |
input |
після зчитування налаштувань, далі не змінюється | input.int(14, "Период") |
simple |
при запуску, далі не змінюється | крок ціни інструмента |
series |
може змінюватися на кожному барі | close, bar_index |
На практиці це важливо в одному місці: періоди функцій ta.* повинні бути постійними — літералом або налаштуванням. Інакше функціям довелося б перебудовувати свій стан на кожному барі.
indicator("Период из настроек")
len = input.int(14, "Период", minval = 2)
plot(ta.rsi(close, len), "RSI")
// ta.rsi(close, bar_index) не скомпилируется:
// «length» в «ta.rsi» требует значение не слабее input, а передано seriesna — відсутнє значення
na означає «значення відсутнє»: не вистачає історії для розрахунку, біржа не віддає показник, у даних дірка. na є в будь-якому типі.
- Арифметика з
naдаєna:na + 1— цеna. - Будь-яке порівняння з
naхибне, томуif x > 0приx = naне виконається. - Перевіряйте відсутність тільки через
na(x). Записx == naкомпілятор відхилить з помилкою «Сравнение с na всегда ложно — используйте na(x)». nz(x)підставляє 0 замістьna,nz(x, v)— значенняv. Те саме коротше:x ?? v.naна графіку — розрив лінії, а не нуль.
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)Історія: x[n]
x[n] — значення x на барі, який був n барів тому; x[0] — поточне. Працює з будь-яким рядом, у тому числі з вашими змінними.
indicator("Наклон средней")
ma = ta.ema(close, 20)
slope = ma - ma[1] // изменение за один бар
plot(slope, "Наклон", style = plot.style_histogram,
color = slope >= 0 ? color.teal : color.red)- Якщо
nбільше числа барів до початку історії, результат —na, а не помилка. nможе змінюватися від бара до бара:close[len]при змінномуlenдопустимо.- Негативний індекс — помилка: заглядати в майбутні бари не можна.
- Налаштовувати глибину історії не потрібно: скрипту доступна вся історія, завантажена на графік.
Оператори
Від найвищого пріоритету до найнижчого:
| Пріоритет | Оператори |
|---|---|
| 1 | x[n], вызов f(…), доступ через точку a.b |
| 2 | ^ — степень, вычисляется справа налево |
| 3 | унарні -, +, not |
| 4 | *, /, % |
| 5 | +, - |
| 6 | <, >, <=, >= |
| 7 | ==, != |
| 8 | and |
| 9 | or |
| 10 | ?? — заміна na |
| 11 | ? : — умовний вираз |
- Порівняння не ланцюжаться: замість
a < b < cпишітьa < b and b < c. andіorне обчислюють праву частину, якщо відповідь уже ясна по лівій.- Степінь вище унарного мінуса:
-2 ^ 2рівне-4. Потрібно4— пишіть(-2) ^ 2. - Дробові числа порівнюйте з допуском:
math.approx(a, b), бо0.1 + 0.2 == 0.3хибне.
Умови
Блоки завжди в фігурних дужках. if буває інструкцією і виразом; для простого вибору з двох є ? :.
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 вибирає першу підходящу гілку. Форма з об’єктом порівнює його зі значеннями гілок, форма без об’єкта перевіряє умови по черзі. Гілка => без умови — гілка за замовчуванням. Провалювання в наступну гілку немає. Коли switch повертає значення, гілка за замовчуванням обов’язкова.
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)Цикли
for i in a .. b перебирає числа від a включно до b не включно. Крок задається словом by, у тому числі від’ємний. Змінна циклу існує тільки всередині циклу і не змінюється в тілі.
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(), "Средняя из массива")Бескінечний цикл термінал не зависне: у скрипта є бюджет операцій на бар і ліміт часу. Перевищивши їх, скрипт зупиниться з повідомленням «Скрипт исчерпал бюджет операций на бар — упростите цикл» або «Скрипт превысил лимит времени на проход».
Функції
Функція оголошується словом func. Коротка форма — вираз після =>, довга — тіло в дужках з return. Типи параметрів можна не вказувати — вони виводяться з викликів, а у параметрів можуть бути значення за замовчуванням. Аргументи передаються по порядку або по імені.
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), "Полразмаха")- Рекурсії немає. Функція не може викликати саму себе, ні прямо, ні через іншу функцію. Повторення пишеться циклом.
- Стан — у кожного виклику свій. Якщо всередині функції є
ta.*абоvar, два виклики однієї функції ведуть незалежні розрахунки:
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)Функції, потрібні в кількох скриптах, виносять у бібліотеку: скрипт з оголошенням library("Имя", 1), перед функціями — слово export, в іншому скрипті — import Имя/1 as имя. Бібліотека береться з вашої папки скриптів.
Кілька результатів
Деякі функції повертають одразу кілька рядів: ta.bb, ta.macd, ta.kc, ta.dmi, ta.supertrend та інші. Їх результат розкладається по змінних у квадратних дужках; число імен має збігатися з числом результатів.
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)Власні типи та перерахування
type збирає кілька полів в одну структуру. Об’єкт створюється через Имя.new(...), поля читаються і змінюються через крапку.
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 — закритий список варіантів із підписами. Зручніше рядками: опечатка в імені варіанта — помилка компіляції, а не тихо неспрацювавша умова. Разом з input.enum перерахування перетворюється в випадаючий список у налаштуваннях — див. Настройки и отрисовка.
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.* всередині умов
Функції на кшталт ta.ema, ta.rsi або ta.atr пам’ятають свій стан з минулого бара. Якби такий виклик виконувався тільки на частині барів — всередині if або однієї гілки ? : — його розрахунок пропускав би бари й тихо давав некоректні числа.
LootScript цього не допускає: компілятор сам виносить кожен виклик ta.* з умови і виконує його на кожному барі, а умова лише вибирає, використовувати чи результат. Тож такий код рахує RSI правильно:
indicator("RSI только на зелёных барах")
r = close > open ? ta.rsi(close, 14) : na
plot(r, "RSI", style = plot.style_circles)Винести виклик не можна, лише якщо його аргумент сам народжується всередині гілки. Тоді компілятор не вгадає й повідомить: «Аргумент «len» нельзя вычислить вне ветки — вынесите вызов ta.* на верхний уровень». Рішення — порахувати аргумент до умови:
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)Коментарі та перенесення рядків
//— коментар до кінця рядка,/* … */— блочний, блоки можна вкладати один в одного.- Інструкція закінчується з кінцем рядка, крапка з комою не потрібна.
- Довгий вираз продовжується на наступному рядку, якщо рядок обірвано всередині дужок, закінчується оператором або наступний рядок починається з оператора,
?або:.
indicator("Перенос строк")
typical = high
+ low
+ close
signal = close > open
? typical / 3
: na
plot(signal, "Типичная цена на зелёных барах", style = plot.style_circles)