Как стать автором
Поиск
Написать публикацию
Обновить

Массивы в Pine Script: что это такое, как создавать, использовать и исправлять ошибки

Уровень сложностиСредний
Время на прочтение12 мин
Количество просмотров878

Всех приветствую!

Продолжаем знакомиться с 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))
Ошибка Index xx is out of bounds. Array size is yy
Ошибка Index xx is out of bounds. Array size is yy

В данном примере последний проход цикла пытается установить значение по индексу 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))
Ошибка Cannot call array methods when ID of array is ‘na’
Ошибка Cannot call array methods when ID of array is ‘na’

Чтобы избежать этого, необходимо создать пустой массив явно:

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, о которых мы еще будем говорить в дальнейшем.

Подписывайтесь, делитесь мнениями, задавайте вопросы в комментариях и до новых встреч!

Теги:
Хабы:
+3
Комментарии4

Публикации

Ближайшие события