Всех приветствую!
Продолжаем знакомиться с TradingView, языком Pine Script и мудростями, которые помогут вам создавать собственные пользовательские индикаторы и стратегии. Соответственно, использовать их в торговле на любом рынке – будь то криптоактивы, национальный рынок акций или т.п.
Почитать предыдущие части-статьи можно по ссылкам ниже:
Рекомендую начать именно с них, если вы только начинаете свой путь в алгоритмическом трейдинге или какие-то слова выше кажутся малознакомыми. Ну а мы начинаем.
В предыдущем материале мы учились собирать данные с графика и сохранять их для дальнейшего анализа. Способ удобный, простой и бесплатный – но для работы с историей не годится.
Всё потому, что одна переменная в Pine Script по умолчанию может хранить только текущее значение. А при работе с историей, скажем, для анализа данных за N предыдущих баров, необходимо использовать структуру, способную удерживать больше информации. И с этой задачей справляются массивы (arrays) – один из самых универсальных и в то же время непростых элементов Pine Script.
Сегодня о них и поговорим.

Массивы в Pine Script: устройство
В процессе разработки торговых стратегий и индикаторов часто приходится анализировать значения переменных на множестве предыдущих баров. Особенно при реализации сложных математических моделей, например, метода наименьших квадратов — МНК (когда-нибудь до него доберемся и тоже разберем).
Для вычисления коэффициентов регрессии в МНК необходимо обрабатывать множество чисел – таких, как значения цены, объёма или индикаторов за определённое количество баров. Простейшие переменные Pine Script, вроде close [1], позволяют получить значение на конкретном баре назад, но становятся крайне громоздкими и неудобными при работе с сериями данных.
Массив в Pine Script — это однотипная коллекция данных, то есть структура, способная хранить множество значений одного типа (например, float, int, bool, string и т.д.).
Все элементы массива обязательно должны быть одного типа:
встроенного (float, int, bool и т.п.),
пользовательского,
или enum.
Элементы в массиве индексируются начиная с нуля, как и в большинстве языков программирования. Для работы с массивами применяется система функций: array.get(), array.set(), array.push(), array.size() и другие, позволяющие читать и записывать значения в массив.
Основы использования
Работа с массивами в Pine Script построена через идентификаторы массивов (array IDs) – это особый тип данных, аналогичный line, label и другим объектным типам. Т.е. переменная, представляющая массив, фактически является указателем на структуру данных, хранящую значения. И любые операции с ней идут через специальные функции.
Объявление массива производится с использованием ключевого слова array, либо с типом и символом []. Например:
array<float> prices = na
// или
float[] prices = na
Здесь na означает, что переменная инициализирована, но не содержит массива. Чтобы создать массив сразу с элементами, применяются функции array.new_<тип>(), такие как:
prices = array.new<float>(2, close)
Этот код создаёт массив с двумя элементами, каждый из которых равен текущему close. Если необходимо создать массив с разными значениями, используется array.from():
statesArray = array.from(close > open, high != close)
Все переданные значения должны быть одного типа. Тип массива определяется автоматически по аргументам.
Важно знать о ключевых словах var и varip. Они указывают компилятору Pine Script, что переменная (в данном случае массив) должна быть создана только один раз, на первом баре.
Это необходимо, потому что массивы в Pine Script НЕ сохраняются автоматически между барами, как это происходит с переменными-сериями (series).
С ключевым словом var массив будет существовать на протяжении всего исполнения скрипта:
var float[] prices = array.new<float>(0)
array.push(prices, close)
Без var этот массив бы пересоздавался каждый бар, и его размер всегда был бы равен 1.
Использование varip вместо var позволяет получать обновление значений в режиме реального времени (на тиках), а не только на исторических барах. Но varip применим только к массивам с типами int, float, bool, color, string или к пользовательским типам, содержащим только такие поля.
Массивы в Pine Script обладают динамическим размером, их длина может меняться от бара к бару в зависимости от логики скрипта. Однако есть и ограничение: массив не может содержать более 100 000 элементов. Это стоит учитывать при длительных вычислениях или при использовании массивов в циклах.
Примеры использования функций массивов в Pine Script
Давайте рассмотрим, как можно применять встроенные функции Pine Script для анализа и обработки массивов. Будем использовать небольшой массив из пяти чисел, чтобы примеры оставались понятными и визуально наглядными.
Сначала создадим массив из пяти элементов, содержащий значения от 1 до 5. Сделаем это с помощью array.new<float>() и array.set():
indicator("array.set()")
a = array.new<float>(5, 0)
for i = 0 to 4
array.set(a, i, i + 1)
if barstate.islast
label.new(bar_index, 0, str.tostring(a), size = size.large)
Мы создаём массив a из 5 элементов, изначально заполненных нулями. Затем заменяем значения от 0 до 4 числами от 1 до 5. Визуально покажем массив до и после вставки:

Если же мы хотим вставить какое-либо значение внутрь массива, то это можно сделать через функцию array.insert():
indicator("array.insert()")
a = array.new<float>(5, 0)
for i = 0 to 4
array.set(a, i, i + 1)
if barstate.islast
label.new(bar_index, 0, "До\na: " + str.tostring(a), size = size.large)
array.insert(a, 2, 999)
label.new(bar_index, 0, "После\na: " + str.tostring(a), style = label.style_label_up, size = size.large)
С помощью функции array.insert (a, 2, 999) вставляем в массив число 999 на позицию с индексом 2 (третий элемент). Визуально это выглядит следующим образом:

Теперь рассмотрим такую функцию, как array.reverse(). С ее помощью мы можем менять порядок элементов в массиве в обратном направлении – то есть из массива [1, 2, 3, 4, 5] мы сделаем [5, 4, 3, 2, 1].
indicator("array.reverse()")
a = array.new<float>(5, 0)
for i = 0 to 4
array.set(a, i, i + 1)
b = array.copy(a)
array.reverse(b)
if barstate.islast
label.new(bar_index, 0, "До:\n" + str.tostring(a), size = size.large)
label.new(bar_index, 0, "После:\n" + str.tostring(b), style = label.style_label_up, size = size.large)
Визуальное это выглядит так:

Теперь посмотрим, как рассчитать сумму и среднее значение массива. Для этого будем использовать функцию array.sum() и array.avg(). Код следующий:
indicator("array.sum() и array.avg()")
a = array.new<float>(5, 0)
for i = 0 to 4
array.set(a, i, i + 1)
total = array.sum(a)
average = array.avg(a)
if barstate.islast
label.new(bar_index, 0, "Массив: " + str.tostring(a), size = size.large)
label.new(bar_index, 0, "Сумма: " + str.tostring(total) + "\nСреднее: " + str.tostring(average), style = label.style_label_up, size = size.large)
Наглядно получаем вот такое:

При работе с массивами нам может понадобиться рассчет моды и медианы. Для этого воспользуемся соответствующими формулами:
array.mode(): возвращает наиболее часто встречающееся значение (в данном случае любое из значений, так как они уникальны – мода = минимум).
array.median(): возвращает медианное значение.
Для выполнения этих функций напишем такой код:
indicator("array.mode() и array.median()")
a = array.new<float>(5, 0)
for i = 0 to 4
array.set(a, i, i + 1)
modeVal = array.mode(a)
medianVal = array.median(a)
if barstate.islast
label.new(bar_index, 0, "Массив: " + str.tostring(a), size = size.large)
label.new(bar_index, 0, "Мода: " + str.tostring(modeVal) + "\nМедиана: " + str.tostring(medianVal), style = label.style_label_up, size = size.large)
После чего получим следующие значения:

В массивах также можно находить стандартное отклонение и дисперсию.
Функция array.stdev() вычисляет стандартное отклонение – показатель разброса данных относительно среднего.
Функция array.variance() возвращает дисперсию – квадрат отклонения.
Для выполнения данных функцию используем следующий код:
indicator("array.stdev() и array.variance()")
a = array.new<float>(5, 0)
for i = 0 to 4
array.set(a, i, i + 1)
stdev = array.stdev(a)
variance = array.variance(a)
if barstate.islast
label.new(bar_index, 0, "Массив: " + str.tostring(a), size = size.large)
label.new(bar_index, 0, "Стандартное отклонение: " + str.tostring(stdev) + "\nДисперсия: " + str.tostring(variance), style = label.style_label_up, size = size.large)
Визуально работа кода выглядит следующим образом:

Пока что на этом остановимся, для наглядности хватит. А полный список создания массивов и работы с ними – в таблице ниже.
Скрытый текст
Функция | Описание |
array.abs() | Возвращает массив с абсолютными значениями элементов. |
array.avg() | Возвращает среднее значение элементов массива. |
array.get() | Возвращает значение элемента по индексу. |
array.max() | Возвращает наибольшее (или n-ое наибольшее) значение. |
array.min() | Возвращает наименьшее (или n-ое наименьшее) значение. |
array.pop() | Удаляет последний элемент и возвращает его. |
array.set() | Задаёт значение элемента по указанному индексу. |
array.sum() | Возвращает сумму всех элементов массива. |
array.copy() | Создаёт копию массива. |
array.fill() | Заполняет массив заданным значением (полностью или частично). |
array.from() | Создаёт массив из набора значений. |
array.join() | Объединяет элементы массива в строку с разделителем. |
array.last() | Возвращает последний элемент массива. |
array.mode() | Возвращает наиболее часто встречающееся значение. |
array.push() | Добавляет значение в конец массива. |
array.size() | Возвращает количество элементов в массиве. |
array.some() | Возвращает true, если хотя бы один элемент — true. |
array.sort() | Сортирует элементы массива. |
array.clear() | Удаляет все элементы из массива. |
array.every() | Возвращает true, если все элементы — true. |
array.first() | Возвращает первый элемент массива. |
array.range() | Разница между max и min значениями массива. |
array.shift() | Удаляет и возвращает первый элемент массива. |
array.slice() | Создаёт подмассив (вырезку) из исходного. |
array.stdev() | Возвращает стандартное отклонение массива. |
array.concat() | Объединяет два массива в один. |
array.insert() | Вставляет значение по указанному индексу. |
array.median() | Возвращает медиану массива. |
array.remove() | Удаляет элемент по индексу. |
array.indexof() | Возвращает индекс первого вхождения значения. |
array.new_box() | Создаёт массив для объектов типа box. |
array.new_int() | Создаёт массив целых чисел (int). |
array.reverse() | Переворачивает порядок элементов массива. |
array.unshift() | Добавляет значение в начало массива. |
array.includes() | Проверяет, есть ли значение в массиве. |
array.new_bool() | Создаёт массив булевых значений (bool). |
array.new_line() | Создаёт массив объектов типа line. |
array.variance() | Возвращает дисперсию значений массива. |
array.new_color() | Создаёт массив цветов (color). |
array.new_float() | Создаёт массив чисел с плавающей точкой (float). |
array.new_label() | Создаёт массив объектов типа label. |
array.new_table() | Создаёт массив таблиц (table). |
array.new<type>() | Создаёт массив указанного типа (<type>). |
array.covariance() | Возвращает ковариацию между двумя массивами. |
array.new_string() | Создаёт массив строк (string). |
array.lastindexof() | Возвращает индекс последнего вхождения значения. |
array.percentrank() | Возвращает процентильный ранг элемента по индексу. |
array.standardize() | Стандартизирует значения массива. |
array.new_linefill() | Создаёт массив объектов linefill. |
array.sort_indices() | Возвращает индексы, отсортированные по значениям массива. |
array.binary_search() | Двоичный поиск значения в отсортированном массиве. |
array.binary_search_leftmost() | Индекс слева от искомого или его позиция. |
array.binary_search_rightmost() | Индекс справа от искомого или его позиция. |
array.percentile_linear_interpolation() | Возвращает процентиль с линейной интерполяцией. |
array.percentile_nearest_rank() | Возвращает процентиль по ближайшему рангу. |
Практическое использование массивов
Переходим к практическому использованию массивов в Pine Script, где нужно модифицировать значения внутри. Часто в алгоритмической торговле нужно просмотреть каждый элемент массива и произвести с ним арифметические действия – прибавить или вычесть число, умножить или разделить, а также применить более сложные математические формулы.
Эти навыки понадобятся вам в дальнейшей работе, особенно при стандартизации данных, расчёте индикаторов и других преобразованиях.
Рассмотрим простой пример: у нас есть массив из пяти элементов, содержащих значения от 1 до 5. Далее мы находим среднее значение этого массива (в данном случае это 3), и затем из каждого элемента массива вычитаем среднее значение. В результате у нас получается второй массив, содержащий значения отклонений от среднего: [-2, -1, 0, 1, 2].
Ниже представлен Pine Script, реализующий этот алгоритм:
indicator("Вычитание среднего из элементов массива")
a = array.new<float>(5, 0)
for i = 0 to 4
array.set(a, i, i + 1)
mean = array.avg(a)
b = array.new<float>()
for i = 0 to array.size(a) - 1
value = array.get(a, i)
diff = value - mean
array.push(b, diff)
if barstate.islast
label.new(bar_index, 0, "a = " + str.tostring(a), size = size.large, style = label.style_label_down)
label.new(bar_index, 0, "Среднее = " + str.tostring(mean) + "\nb = " + str.tostring(b), size = size.large, style = label.style_label_up)
Код демонстрирует, как легко можно выполнять арифметические операции с каждым элементом массива в Pine Script.
Распространенные ошибки при работе с массивами в Pine Script
Работа с массивами в Pine Script сопровождается как синтаксическими, так и логическими ограничениями. Синтаксические ошибки выявляются при компиляции и отображаются в консоли Pine Editor. Куда более коварными являются ошибки времени выполнения: они возникают при исполнении кода на графике и обозначаются значком с восклицательным знаком рядом с именем индикатора.
Ниже рассмотрены наиболее распространённые ошибки, возникающие при работе с массивами, а также способы их предотвращения.
Ошибка Index xx is out of bounds. Array size is yy
Наиболее часто встречающаяся ошибка связана с выходом за пределы допустимых индексов массива. В Pine Script индексация начинается с нуля, поэтому при размере массива n допустимыми считаются индексы от 0 до n - 1. Если попытаться обратиться к несуществующему элементу, будет выброшено исключение.
Пример скрипта, вызывающего ошибку:
indicator("Out of bounds index")
a = array.new<float>(3)
for i = 1 to 3
array.set(a, i, i)
plot(array.pop(a))

В данном примере последний проход цикла пытается установить значение по индексу 3, которого в массиве размером 3 не существует.
Для устранения ошибки необходимо ограничить диапазон итерации:
for i = 1 to 2
Если размер массива заранее неизвестен и определяется на основе пользовательского ввода, границы цикла следует вычислять на основе текущего размера массива:
indicator("Protected `for` loop")
sizeInput = input.int(0, "Array size", minval = 1, maxval = 100)
a = array.new<float>(sizeInput)
for i = 0 to (array.size(a) == 0 ? na : array.size(a) - 1)
array.set(a, i, i)
plot(array.pop(a))
Ошибка Cannot call array methods when ID of array is ‘na’
Методы массива не могут быть вызваны, если переменной присвоено значение na, так как в этом случае массив не существует. Ошибка возникает при попытке работать с неинициализированным идентификатором массива.
Пример с ошибкой:
indicator("Array methods on `na` array")
array<int> a = na
array.push(a, 111)
label.new(bar_index, 0, "a: " + str.tostring(a))

Чтобы избежать этого, необходимо создать пустой массив явно:
array<int> a = array.new_int(0)
Ошибка Array is too large. Maximum size is 100000
Размер любого массива в Pine Script не может превышать 100 000 элементов. Нарушение этого ограничения при инициализации или динамическом добавлении элементов приводит к ошибке времени выполнения. При использовании пользовательского ввода допустимо указывать диапазон 1…100000 с помощью аргументов minval и maxval.
Ошибка Cannot create an array with a negative size
Попытка создать массив с отрицательным размером всегда вызывает ошибку. Такое поведение не поддерживается, так как отрицательные размеры массивов не имеют смысла в логике языка.
Ошибки Cannot use shift() if array is empty и Cannot use pop() if array is empty
Функции array.shift() и array.pop() удаляют элементы массива, соответственно – с начала и с конца. Их вызов на пустом массиве невозможен и вызывает ошибку.
Чтобы избежать этого, перед вызовом стоит проверять размер массива с помощью array.size().
Ошибка Index ‘from’ should be less than index ‘to’
Функции, принимающие диапазон индексов, например, array.slice(), требуют, чтобы начальный индекс был строго меньше конечного. Нарушение этого условия приводит к ошибке выполнения.
Ошибка Slice is out of bounds of the parent array
После создания среза с помощью array.slice() массив-источник не должен изменяться таким образом, чтобы нарушить границы, к которым привязан срез. В противном случае попытка обратиться к элементам среза приведёт к выходу за границы исходного массива.
Пример ошибки:
indicator("Slice out of bounds")
a = array.new<float>(5, 0)
b = array.slice(a, 3, 5)
array.remove(a, 0)
c = array.indexof(b, 2)
plot(c)
При вызове данного индикатора получаем не ошибку, а, скорее неверный расчет. Допустим, мы хотели бы получить 0 (потому что в массиве b у нас только два нуля), но у нас есть только два элемента, а индексы начинаются с нуля – поэтому значение с индексом 2 не существует. Код выдает нам значение -1, что неверно для расчетов в будущем.
Несмотря на гибкость массивов в Pine Script, работа с ними требует аккуратности и строгого контроля за границами, условиями инициализации и изменениями размера. Большинство ошибок можно предотвратить, используя проверку индексов, ограничения ввода и корректную инициализацию массивов.
Конец
В этой статье мы подробно познакомились с массивами в Pine Script. Узнали, как создавать массивы с фиксированным или динамическим размером, рассматривали основные функции для работы с ними. На практике посмотрели, как изменять содержимое массивов, выполнять арифметические операции с их элементами и как визуализировать результаты на графике. Также мы разобрали типичные ошибки, которые могут возникать при работе с массивами.
В общем, большие молодцы) Эти знания станут основой для дальнейшего практического применения массивов и точно пригодятся вам в создании пользовательских индикаторов и стратегий в TradingView, о которых мы еще будем говорить в дальнейшем.
Подписывайтесь, делитесь мнениями, задавайте вопросы в комментариях и до новых встреч!