ADVANCED

Arrays

Introduction

Pine Script arrays are one-dimensional collections that can store multiple values or references in a single location. Arrays are a more robust alternative to declaring a set of similar variables (e.g., price00, price01, price02, …).

All elements in an array must be of the same built-in type, user-defined type, or enum type.

Similar to lines, labels, and other reference types, arrays and their data are accessed using references, which we often refer to as IDs. Pine Script does not use an indexing operator to access individual array elements. Instead, functions including array.get() and array.set() read and write the elements of the array associated with a specific ID.

Scripts access specific elements in an array by specifying an index in calls to these functions. The index starts at 0 and extends to one less than the number of elements in the array. Arrays in Pine Script can have dynamic sizes that vary across bars, as scripts can change the number of elements in an array on any execution. A single script can create multiple array instances. The total number of elements in any array cannot exceed 100,000.

Declaring arrays

Pine Script uses the following syntax for array declarations:

[var/varip ][array<type> ]<identifier> = <expression>

Where <type> is a type template that defines the type of elements that the array can contain, and <expression> is an expression that returns either the ID of an array or na. See the Collections section of the Type system page to learn about type templates.

When declaring an array variable, programmers can use the array keyword followed by a type template to explicitly define the variable’s type identifier (e.g., array<int> for a variable that can reference an array of “int” values).

Specifying a type identifier for a variable or function parameter that holds array references is usually optional. The only exceptions are when initializing an identifier with an na value, defining exported library functions whose parameters accept array IDs, or declaring user-defined types with fields for storing array IDs. Even when not required, note that specifying an array variable’s type helps promote readability, and it helps the Pine Editor provide relevant code suggestions.

The following line of code declares an array variable named prices that has an initial reference of na. This variable declaration requires a type identifier, because the compiler cannot automatically determine the type that na represents:

array<float> prices = na

Scripts can use the following functions to create new arrays: array.new<type>(), array.from(), or array.copy(). Each of these functions creates a new array and returns a non-na ID for use in other parts of the code. Note that these functions accept “series” arguments for all parameters, meaning the constructed arrays can have dynamic sizes and elements on each call.

The following example creates an empty “float” array and assigns its ID to a prices variable. Specifying a type identifier for the prices variable is not required in this case, because the variable automatically inherits the function’s returned type (array<float>):

prices = array.new<float>(0)

The initial_value parameter of the array.new*() functions enables users to set all initial elements in the array to a specified value or reference. If a call to these functions does not include an initial_value argument, it creates an array filled with na elements.

The following line declares an array variable named prices and assigns it the ID of an array containing two elements. Both elements in the array hold the current bar’s close value:

prices = array.new<float>(2, close)

To create an array without initializing all elements to the same value or reference, use array.from(). This function determines the array’s size and the type of elements it stores based on the arguments in the function call. All arguments supplied to the call must be of the same type.

For example, both lines of code in the following example show two ways to create a “bool” array using array.from() and declare a variable to store its ID:

statesArray = array.from(close > open, high != close) array<bool> statesArray = array.from(close > open, high != close)

Using ​var​ and ​varip​ keywords

Programmers can use the var and varip keywords to instruct a script to declare an array variable on only one bar instead of on each execution of the variable’s scope. Array variables declared using these keywords point to the same array instances until explicitly reassigned, allowing an array and its elements to persist across bars.

When declaring an array variable using these keywords and pushing a new value to the end of the referenced array on each bar, the array will grow by one on each bar and be of size bar_index + 1 (bar_index starts at zero) by the time the script executes on the last bar, as this code demonstrates:

//@version=6 indicator("Using `var`") //@variable An array that expands its size by 1 on each bar. var a = array.new<float>(0) array.push(a, close) if barstate.islast //@variable A string containing the size of `a` and the current `bar_index` value. string labelText = "Array size: " + str.tostring(a.size()) + "\nbar_index: " + str.tostring(bar_index) // Display the `labelText`. label.new(bar_index, 0, labelText, size = size.large)

The same code without the var keyword would reinitialize the a variable with the ID of a new, empty array on every execution. In that case, after execution of the array.push() call, the array.size() method call (a.size()) would return a value of 1.

Reading and writing array elements

Scripts can write values to existing individual array elements using array.set(), and read using array.get(). When using these functions, it is imperative that the index in the function call is always less than or equal to the array’s size (because array indices start at zero). To get the size of an array, use the array.size() function.

The following example uses the set() method to populate a fillColors array with instances of one base color using different transparency levels. It then uses array.get() to retrieve one of the colors from the array based on the location of the bar with the highest price within the last lookbackInput bars:

image

//@version=6 indicator("Distance from high", "", true) lookbackInput = input.int(100) FILL_COLOR = color.green // Declare array and set its values on the first bar only. var fillColors = array.new<color>(5) if barstate.isfirst // Initialize the array elements with progressively lighter shades of the fill color. fillColors.set(0, color.new(FILL_COLOR, 70)) fillColors.set(1, color.new(FILL_COLOR, 75)) fillColors.set(2, color.new(FILL_COLOR, 80)) fillColors.set(3, color.new(FILL_COLOR, 85)) fillColors.set(4, color.new(FILL_COLOR, 90)) // Find the offset to highest high. Change its sign because the function returns a negative value. lastHiBar = - ta.highestbars(high, lookbackInput) // Convert the offset to an array index, capping it to 4 to avoid a runtime error. // The index used by `array.get()` will be the equivalent of `floor(fillNo)`. fillNo = math.min(lastHiBar / (lookbackInput / 5), 4) // Set background to a progressively lighter fill with increasing distance from location of highest high. bgcolor(array.get(fillColors, fillNo)) // Plot key values to the Data Window for debugging. plotchar(lastHiBar, "lastHiBar", "", location.top, size = size.tiny) plotchar(fillNo, "fillNo", "", location.top, size = size.tiny)

Another technique for initializing the elements in an array is to create an empty array (an array with no elements), then use array.push() to append new elements to the end of the array, increasing the size of the array by one on each call. The following code is functionally identical to the initialization section from the preceding script:

// Declare array and set its values on the first bar only. var fillColors = array.new<color>(0) if barstate.isfirst // Initialize the array elements with progressively lighter shades of the fill color. array.push(fillColors, color.new(FILL_COLOR, 70)) array.push(fillColors, color.new(FILL_COLOR, 75)) array.push(fillColors, color.new(FILL_COLOR, 80)) array.push(fillColors, color.new(FILL_COLOR, 85)) array.push(fillColors, color.new(FILL_COLOR, 90))

This code is equivalent to the one above, but it uses array.unshift() to insert new elements at the beginning of the fillColors array:

// Declare array and set its values on the first bar only. var fillColors = array.new<color>(0) if barstate.isfirst // Initialize the array elements with progressively lighter shades of the fill color. array.unshift(fillColors, color.new(FILL_COLOR, 90)) array.unshift(fillColors, color.new(FILL_COLOR, 85)) array.unshift(fillColors, color.new(FILL_COLOR, 80)) array.unshift(fillColors, color.new(FILL_COLOR, 75)) array.unshift(fillColors, color.new(FILL_COLOR, 70))

We can also use array.from() to create the same fillColors array with a single function call:

//@version=6 indicator("Using `var`") FILL_COLOR = color.green var array<color> fillColors = array.from( color.new(FILL_COLOR, 70), color.new(FILL_COLOR, 75), color.new(FILL_COLOR, 80), color.new(FILL_COLOR, 85), color.new(FILL_COLOR, 90) ) // Cycle background through the array's colors. bgcolor(array.get(fillColors, bar_index % (fillColors.size())))

The array.fill() function points all array elements, or the elements within the index_from to index_to range, to a specified value. Without the last two optional parameters, the function fills the whole array, so:

a = array.new<float>(10, close)

and:

a = array.new<float>(10) a.fill(close)

are equivalent, but:

a = array.new<float>(10) a.fill(close, 1, 3)

only fills the second and third elements (at index 1 and 2) of the array with close. Note how the array.fill() function’s last parameter, index_to, must have a value one greater than the last index the function will fill. The remaining elements will hold na values, as the array.new<type>() function call does not contain an initial_value argument.

Looping through array elements

When looping through an array’s element indices and the array’s size is unknown, one can use the array.size() function to get the maximum index value. For example:

//@version=6 indicator("Protected `for` loop", overlay = true) //@variable An array of `close` prices from the 1-minute timeframe. array<float> a = request.security_lower_tf(syminfo.tickerid, "1", close) //@variable A string representation of the elements in `a`. string labelText = "" for i = 0 to (array.size(a) == 0 ? na : array.size(a) - 1) labelText += str.tostring(array.get(a, i)) + "\n" label.new(bar_index, high, text = labelText)

Note that:

  • We use the request.security_lower_tf() function which returns an array of close prices at the 1 minute timeframe.
  • This code example will throw an error if you use it on a chart timeframe smaller than 1 minute.
  • for loops do not execute if the to expression is na. Note that the to value is only evaluated once upon entry.

An alternative method to loop through an array is to use a for…in loop. This approach is a variation of the standard for loop that can iterate over the value references and indices in an array. Here is an example of how we can write the code example from above using a for...in loop:

//@version=6 indicator("`for...in` loop", overlay = true) //@variable An array of `close` prices from the 1-minute timeframe. array<float> a = request.security_lower_tf(syminfo.tickerid, "1", close) //@variable A string representation of the elements in `a`. string labelText = "" for price in a labelText += str.tostring(price) + "\n" label.new(bar_index, high, text = labelText)

Note that:

  • for…in loops can return a tuple containing each index and corresponding element. For example, for [i, price] in a returns the i index and price value for each element in a.

A while loop statement can also be used:

//@version=6 indicator("`while` loop", overlay = true) array<float> a = request.security_lower_tf(syminfo.tickerid, "1", close) string labelText = "" int i = 0 while i < array.size(a) labelText += str.tostring(array.get(a, i)) + "\n" i += 1 label.new(bar_index, high, text = labelText)

Scope

Users can declare arrays within the global scope of a script, as well as the local scopes of functions, methods, and conditional structures. Unlike some of the other built-in types, namely fundamental types, scripts can modify globally-assigned arrays from within local scopes, allowing users to implement global variables that any function in the script can directly interact with. We use the functionality here to calculate progressively lower or higher price levels:

image

//@version=6 indicator("Bands", "", true) //@variable The distance ratio between plotted price levels. factorInput = 1 + (input.float(-2., "Step %") / 100) //@variable A single-value array holding the lowest `ohlc4` value within a 50 bar window from 10 bars back. level = array.new<float>(1, ta.lowest(ohlc4, 50)[10]) nextLevel(val) => newLevel = level.get(0) * val // Write new level to the global `level` array so we can use it as the base in the next function call. level.set(0, newLevel) newLevel plot(nextLevel(1)) plot(nextLevel(factorInput)) plot(nextLevel(factorInput)) plot(nextLevel(factorInput))

History referencing

The history-referencing operator [] can access the history of array variables, allowing scripts to interact with past array instances previously assigned to a variable.

To illustrate this, let’s create a simple example to show how one can fetch the previous bar’s close value in two equivalent ways. This script uses the [] operator to get the array instance assigned to a on the previous bar, then uses an array.get() method call to retrieve the value of the first element (previousClose1). For previousClose2, we use the history-referencing operator on the close variable directly to retrieve the value. As we see from the plots, previousClose1 and previousClose2 both return the same value:

image

//@version=6 indicator("History referencing") //@variable A single-value array declared on each bar. a = array.new<float>(1) // Set the value of the only element in `a` to `close`. array.set(a, 0, close) //@variable The array instance assigned to `a` on the previous bar. previous = a[1] previousClose1 = na(previous) ? na : previous.get(0) previousClose2 = close[1] plot(previousClose1, "previousClose1", color.gray, 6) plot(previousClose2, "previousClose2", color.white, 2)

Inserting and removing array elements

Inserting

The following three functions can insert new elements into an array.

array.unshift() inserts a new element at the beginning of an array (index 0) and increases the index values of any existing elements by one.

array.insert() inserts a new element at the specified index and increases the index of existing elements at or after the index by one.

image

//@version=6 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, "BEFORE\na: " + str.tostring(a), size = size.large) array.insert(a, 2, 999) label.new(bar_index, 0, "AFTER\na: " + str.tostring(a), style = label.style_label_up, size = size.large)

array.push() adds a new element at the end of an array.

Removing

These four functions remove elements from an array. The first three also return the value of the removed element.

array.remove() removes the element at the specified index and returns that element’s value.

array.shift() removes the first element from an array and returns its value.

array.pop() removes the last element of an array and returns its value.

array.clear() removes all elements from an array. Note that clearing an array won’t delete any objects its elements referenced. See the example below that illustrates how this works:

//@version=6 indicator("`array.clear()` example", overlay = true) // Create a label array and add a label to the array on each new bar. var a = array.new<label>() label lbl = label.new(bar_index, high, "Text", color = color.red) array.push(a, lbl) var table t = table.new(position.top_right, 1, 1) // Clear the array on the last bar. This doesn't remove the labels from the chart. if barstate.islast array.clear(a) table.cell(t, 0, 0, "Array elements count: " + str.tostring(array.size(a)), bgcolor = color.yellow)

Using an array as a stack

Stacks are LIFO (last in, first out) constructions. They behave somewhat like a vertical pile of books to which books can only be added or removed one at a time, always from the top. Pine Script arrays can be used as a stack, in which case we use the array.push() and array.pop() functions to add and remove elements at the end of the array.

array.push(prices, close) will add a new element to the end of the prices array, increasing the array’s size by one.

array.pop(prices) will remove the end element from the prices array, return its value and decrease the array’s size by one.

See how the functions are used here to track successive lows in rallies:

image

//@version=6 indicator("Lows from new highs", "", true) var lows = array.new<float>(0) flushLows = false //@function Removes the last element from the `id` stack when `cond` is `true`. array_pop(id, cond) => cond and array.size(id) > 0 ? array.pop(id) : float(na) if ta.rising(high, 1) // Rising highs; push a new low on the stack. lows.push(low) // Force the return type of this `if` block to be the same as that of the next block. bool(na) else if lows.size() >= 4 or low < array.min(lows) // We have at least 4 lows or price has breached the lowest low; // sort lows and set flag indicating we will plot and flush the levels. array.sort(lows, order.ascending) flushLows := true // If needed, plot and flush lows. lowLevel = array_pop(lows, flushLows) plot(lowLevel, "Low 1", low > lowLevel ? color.silver : color.purple, 2, plot.style_linebr) lowLevel := array_pop(lows, flushLows) plot(lowLevel, "Low 2", low > lowLevel ? color.silver : color.purple, 3, plot.style_linebr) lowLevel := array_pop(lows, flushLows) plot(lowLevel, "Low 3", low > lowLevel ? color.silver : color.purple, 4, plot.style_linebr) lowLevel := array_pop(lows, flushLows) plot(lowLevel, "Low 4", low > lowLevel ? color.silver : color.purple, 5, plot.style_linebr) if flushLows // Clear remaining levels after the last 4 have been plotted. lows.clear()

Using an array as a queue

Queues are FIFO (first in, first out) constructions. They behave somewhat like cars arriving at a red light. New cars are queued at the end of the line, and the first car to leave will be the first one that arrived to the red light.

In the following code example, we let users decide through the script’s inputs how many labels they want to have on their chart. We use that quantity to determine the size of the array of labels we then create, initializing the array’s elements to na.

When a new pivot is detected, we create a label for it, saving the label’s ID in the pLabel variable. We then queue the ID of that label by using array.push() to append the new label’s ID to the end of the array, making our array size one greater than the maximum number of labels to keep on the chart.

Lastly, we de-queue the oldest label by removing the array’s first element using array.shift() and deleting the label referenced by that array element’s value. As we have now de-queued an element from our queue, the array contains pivotCountInput elements once again. Note that on the dataset’s first bars we will be deleting na label IDs until the maximum number of labels has been created, but this does not cause runtime errors. Let’s look at our code:

image

//@version=6 MAX_LABELS = 100 indicator("Show Last n High Pivots", "", true, max_labels_count = MAX_LABELS) pivotCountInput = input.int(5, "How many pivots to show", minval = 0, maxval = MAX_LABELS) pivotLegsInput = input.int(3, "Pivot legs", minval = 1, maxval = 5) // Create an array containing the user-selected max count of label IDs. var labelIds = array.new<label>(pivotCountInput) pHi = ta.pivothigh(pivotLegsInput, pivotLegsInput) if not na(pHi) // New pivot found; plot its label `pivotLegsInput` bars behind the current `bar_index`. pLabel = label.new(bar_index - pivotLegsInput, pHi, str.tostring(pHi, format.mintick), textcolor = color.white) // Queue the new label's ID by appending it to the end of the array. array.push(labelIds, pLabel) // De-queue the oldest label ID from the queue and delete the corresponding label. label.delete(array.shift(labelIds))

Negative indexing

The array.get(), array.set(), array.insert(), and array.remove() functions support negative indexing, which references elements starting from the end of the array. An index of -1 refers to the last element in the array, an index of -2 refers to the second to last element, and so on.

When using a positive index, functions traverse the array forwards from the beginning of the array (first to last element). The first element’s index is 0, and the last element’s index is array.size() - 1. When using a negative index, functions traverse the array backwards from the end of the array (last to first element). The last element’s index is -1, and the first element’s index is –array.size():

array<string> myArray = array.from("first", "second", "third", "fourth", "last") // Positive indexing: Indexes forwards from the beginning of the array. myArray.get(0) // Returns "first" element myArray.get(myArray.size() - 1) // Returns "last" element myArray.get(4) // Returns "last" element // Negative indexing: Indexes backwards from the end of the array. myArray.get(-1) // Returns "last" element myArray.get(-myArray.size()) // Returns "first" element myArray.get(-5) // Returns "first" element

Like positive indexing, negative indexing is bound by the size of the array. For example, functions operating on an array of 5 elements only accept indices of 0 to 4 (first to last element) or -1 to -5 (last to first element). Any other indices are out of bounds and will raise a runtime error.

We can use negative indices to retrieve, update, add, and remove array elements. This simple script creates an “int” countingArray and calls the array.get(), array.set(), array.insert(), and array.remove() functions to perform various array operations using negative indices. It displays each array operation and its corresponding result using a table:

image

//@version=6 indicator("Negative indexing demo", overlay = false) //@variable A table that displays various array operations and their results. var table displayTable = table.new( position.middle_center, 2, 15, bgcolor = color.white, frame_color = color.black, frame_width = 1, border_width = 1 ) //@function Initializes a `displayTable` row to output a "string" of an `arrayOperation` and the `operationResult`. displayRow(int rowID, string arrayOperation, operationResult) => //@variable Is white if the `rowID` is even, light blue otherwise. Used to set alternating table row colors. color rowColor = rowID % 2 == 0 ? color.white : color.rgb(33, 149, 243, 75) // Display the `arrayOperation` in the row's first cell. displayTable.cell(0, rowID, arrayOperation, text_color = color.black, text_halign = text.align_left, bgcolor = rowColor, text_font_family = font.family_monospace ) // Display the `operationResult` in the row's second cell. displayTable.cell(1, rowID, str.tostring(operationResult), text_color = color.black, text_halign = text.align_right, bgcolor = rowColor ) if barstate.islastconfirmedhistory //@variable Array of "int" numbers. Holds six multiples of 10, counting from 10 to 60. array<int> countingArray = array.from(10, 20, 30, 40, 50, 60) // Initialize the table's header cells. displayTable.cell(0, 0, "ARRAY OPERATION") displayTable.cell(1, 0, "RESULT") // Display the initial `countingArray` values. displayTable.cell(0, 1, "Initial `countingArray`", text_color = color.black, text_halign = text.align_center, bgcolor = color.yellow) displayTable.cell(1, 1, str.tostring(countingArray), text_color = color.black, text_halign = text.align_right, bgcolor = color.yellow) // Retrieve array elements using negative indices in `array.get()`. displayRow(2, "`countingArray.get(0)`", countingArray.get(0)) displayRow(3, "`countingArray.get(-1)`", countingArray.get(-1)) displayRow(4, "`countingArray.get(-countingArray.size())`", countingArray.get(-countingArray.size())) // Update array elements using negative indices in `array.set()` and `array.insert()`. countingArray.set(-2, 99) displayRow(5, "`countingArray.set(-2, 99)`", countingArray) countingArray.insert(-5, 878) displayRow(6, "`countingArray.insert(-5, 878)`", countingArray) // Remove array elements using negative indices in `array.remove()`. countingArray.remove(-3) displayRow(7, "`countingArray.remove(-3)`", countingArray)

Note that not all array operations can use negative indices. For example, search functions like array.indexof() and array.binary_search() return the positive index of an element if it’s found in the array. If the value is not found, the functions return -1. However, this returned value is not a negative index, and using it as one would incorrectly reference the last array element. If a script needs to use a search function’s returned index in subsequent array operations, it must appropriately differentiate between this -1 result and other valid indices.

Calculations on arrays

While series variables can be viewed as a horizontal set of values stretching back in time, Pine Script’s one-dimensional arrays can be viewed as vertical structures residing on each bar. As an array’s set of elements is not a time series, Pine Script’s usual mathematical functions are not allowed on them. Special-purpose functions must be used to operate on all of an array’s values. The available functions are: array.abs(), array.avg(), array.covariance(), array.min(), array.max(), array.median(), array.mode(), array.percentile_linear_interpolation(), array.percentile_nearest_rank(), array.percentrank(), array.range(), array.standardize(), array.stdev(), array.sum(), array.variance().

Note that contrary to the usual mathematical functions in Pine Script, those used on arrays do not return na when some of the values they calculate on have na values. There are a few exceptions to this rule:

  • When all array elements have na value or the array contains no elements, na is returned. array.standardize() however, will return an empty array.
  • array.mode() will return na when no mode is found.

Manipulating arrays

Concatenation

Two arrays can be merged — or concatenated — using array.concat(). When arrays are concatenated, the second array is appended to the end of the first, so the first array is modified while the second one remains intact. The function returns the array ID of the first array:

image

//@version=6 indicator("`array.concat()`") a = array.new<float>(0) b = array.new<float>(0) array.push(a, 0) array.push(a, 1) array.push(b, 2) array.push(b, 3) if barstate.islast label.new(bar_index, 0, "BEFORE\na: " + str.tostring(a) + "\nb: " + str.tostring(b), size = size.large) c = array.concat(a, b) array.push(c, 4) label.new(bar_index, 0, "AFTER\na: " + str.tostring(a) + "\nb: " + str.tostring(b) + "\nc: " + str.tostring(c), style = label.style_label_up, size = size.large)

Joining

The array.join() function converts an “int”, “float”, or “string” array’s elements into strings, then joins each one to form a single “string” value with a specified separator inserted between each combined value. It provides a convenient alternative to converting values to strings with str.tostring() and performing repeated string concatenation operations.

The following script demonstrates the array.join() function’s behaviors. It requests tuples of “string”, “int”, and “float” values from three different contexts with request.security() calls, creates separate arrays for each type with array.from(), then creates joined strings with the array.join() function. Lastly, it creates another array from those strings with array.from() and joins them with another array.join() call, using a newline as the separator, and displays the final string in the table:

image

//@version=6 indicator("Joining demo") //@function Returns a tuple containing the ticker ID ("string"), bar index ("int"), and closing price ("float"). dataRequest() => [syminfo.tickerid, bar_index, close] if barstate.islast //@variable A single-cell table displaying the results of `array.join()` calls. var table displayTable = table.new(position.middle_center, 1, 1, color.blue) // Request data for three symbols. [ticker1, index1, price1] = request.security("SPY", "", dataRequest()) [ticker2, index2, price2] = request.security("GLD", "", dataRequest()) [ticker3, index3, price3] = request.security("TLT", "", dataRequest()) // Create separate "string", "int", and "float" arrays to hold the requested data. array<string> tickerArray = array.from(ticker1, ticker2, ticker3) array<int> indexArray = array.from(index1, index2, index3) array<float> priceArray = array.from(price1, price2, price3) // Convert each array's data to strings and join them with different separators. string joined1 = array.join(tickerArray, ", ") string joined2 = indexArray.join("|") string joined3 = priceArray.join("\n") //@variable A joined "string" containing the `joined1`, `joined2`, and `joined3` values. string displayText = array.from(joined1, joined2, joined3).join("\n---\n") // Initialize a cell to show the `displayText`. displayTable.cell(0, 0, displayText, text_color = color.white, text_size = 36)

Note that:

  • Each array.join() call inserts the specified separator only between each element string. It does not include the separator at the start or end of the returned value.
  • The array.join() function uses the same numeric format as the default for str.tostring(). See the String conversion and formatting section of the Strings page to learn more.
  • Calls to array.join() cannot directly convert elements of “bool”, “color”, or other types to strings. Scripts must convert data of these types separately.

Sorting

Scripts can sort arrays containing values of the “int”, “float”, or “string” type by using the array.sort() function. The function’s order parameter accepts one of the two order.* constants to specify the sorting order. If the argument is order.ascending (the default), a call to the function rearranges the specified array’s elements in ascending order by value. If the argument is order.descending, the call rearranges the elements in descending order instead.

If an array contains “int” or “float” elements, the array.sort() function sorts the array using each element’s numeric value. If it uses ascending order, the element with the lowest value becomes the array’s first element (at index 0), and the one with the highest value becomes the last element. If it sorts in descending order, the element with the highest value becomes the first element, and the one with the lowest value becomes the last.

The following example script uses the array.from() function to create an array of arbitrary “float” values on the last historical bar. It then sorts the array in ascending order, and then in descending order, using two array.sort() calls. The script creates a string representation of the array after each step, then formats those representations into a single string and displays the result in a label:

image

//@version=6 indicator("Sorting numeric arrays demo") if barstate.islastconfirmedhistory //@variable References an array of arbitrary "float" values. array<float> numbers = array.from(2.1, 0.5, 1.2, 0.1, 1.4, 0.6) //@variable A string representing the array's unsorted, ascending, and descending order. string displayStr = "Unsorted: " + str.tostring(numbers) + "\n" // Sort the array in ascending order. // The `order` argument is optional; `order.ascending` is the default. array.sort(numbers, order = order.ascending) // Concatenate a string representation of the sorted array with the `displayStr` value. displayStr += "Ascending: " + str.tostring(numbers) + "\n" // Sort the `numbers` array again, this time in descending order. numbers.sort(order = order.descending) // Concatenate another string representation of the sorted result. displayStr += "Descending: " + str.tostring(numbers) // Display the final string's text in a label. label.new( bar_index, 0, displayStr, style = label.style_label_center, size = 30, textalign = text.align_left, text_font_family = font.family_monospace )

If an array contains “string” elements, the array.sort() function sorts the elements based on the Unicode values of the strings’ individual characters. The sorting algorithm initially compares the first character in each string, then compares subsequent characters as necessary if multiple strings have matching characters at the same position. The strings that have leading characters with the lowest Unicode values move to the beginning of the array if the order is ascending, or to the end of the array if the order is descending.

The example script below defines an arbitrary literal string, then uses the str.split() function to split the string and construct an array of substrings. Afterward, the script calls the array.sort() function to sort the array’s elements in ascending order. The script displays formatted text representing the original string, and the array’s structure before and after sorting, in a label on the last historical bar:

image

//@version=6 indicator("Sorting string arrays demo") if barstate.islastconfirmedhistory //@variable A literal string to split at each `,` character. string originalStr = "abc,abC,Abc,ABC,{ABC},!,123,12.3, " //@variable References an array of substrings formed by splitting the original string at each comma. array<string> splitStrArray = str.split(originalStr, ",") //@variable A string to represent the original string and the array of substrings. string displayStr = str.format("Original string: ''{0}''\n\nSubstring array: {1}\n", originalStr, splitStrArray) // Sort the array in ascending order, based on the Unicode values of characters in each string. splitStrArray.sort() // Concatenate a string representing the sorted result. displayStr += str.format("Sorted array: {0}\n", splitStrArray) // Display the final `displayStr` value's text in a label. label.new( bar_index, 0, displayStr, style = label.style_label_center, size = 30, textalign = text.align_left, text_font_family = font.family_monospace )

Note that:

  • The " " string appears first in the sorted array because standard whitespace and control characters have the lowest Unicode values (U+0000 - U+0020). The space character’s Unicode value is U+0020.
  • ASCII digits (U+0030 - U+0039) have lower Unicode values than all letter characters. Therefore, the sorted array lists all strings that start with digits before those that start with letters.
  • Uppercase ASCII letters (U+0041 - U+005A) have lower Unicode values than lowercase ASCII letters (U+0061 - U+007A). Therefore, strings that start with A appear before those that start with a in the sorted array.
  • Some ASCII punctuation marks and symbols have lower Unicode values than ASCII letters or digits, and some others have Unicode values that are between or higher than those of such characters. For instance, the sorted array lists the "!" string before other strings except for " " because the Unicode value of ! is U+0021. By contrast, it lists the "{ABC}" string at the end because the { character’s Unicode value is U+007B.

Every array.sort() call directly changes the positions of elements in the original array, as demonstrated above. However, in some cases, a programmer might need to access an array’s elements in a sorted order without rearranging the array itself.

To access an array’s sorted elements without modifying the array, programmers can use the array.sort_indices() function. This function creates a separate “int” array containing the indices of the original array’s elements, organized in the sorted order for those elements. Scripts can use the indices in the resulting array to read the original array’s elements in the specified order (order.ascending by default) while also preserving the original array’s unsorted order for other calculations.

The following example script queues close values into a persistent array across the chart. It calls the array.sort_indices() function on the last historical bar to get the ID of an array containing sorted indices, and constructs a string representation of both arrays. Then, it loops through the array of indices using a for…in loop. On each iteration, the script concatenates the string with another string representing a value from the prices array, that value’s index in the array, and the value’s sorted position. It then displays the final string in a label:

image

//@version=6 indicator("Getting sorted indices demo") //@variable References a persistent array that stores the last 10 `close` values. var array<float> prices = array.new<float>(10) // Push a new value to the end of the array, and remove the oldest (first) element. prices.push(close) prices.shift() if barstate.islastconfirmedhistory //@variable References an "int" array containing the `prices` array indices in ascending order by element value. // The `array.sort_indices()` call maps sorted positions in the array without modifying it. array<int> indices = prices.sort_indices() //@variable A formatted string to display in a label. string displayStr = str.format("Prices: {0}\n\nSorted indices: {1}\n\nSort results:", prices, indices) // Loop through the `indices` array. // The `i` variable stores the current index of the `indices` array's element. // The `index` variable stores that element's value (the index for one of the `prices` array's elements). // Using `index` to retrieve `prices` array's elements accesses those elements in ascending order. for [i, index] in indices // Concatenate the `displayStr` value with a string representing the sorted `prices` array element, // the original position (index) of the element, and the sorted position of that element. displayStr += str.format( "\nPrice: {0,number,0.000}, Original position: {1} -> Sorted position: {2}", prices.get(index), index, i ) // Display the final string's text in a label. label.new( bar_index, 0, displayStr, style = label.style_label_center, size = 20, textalign = text.align_left, text_font_family = font.family_monospace )

Sorting arrays of user-defined types

The array.sort() and array.sort_indices() functions can also sort arrays whose elements refer to objects of user-defined types (UDTs). For such arrays, the functions compare values from one of the “int”, “float”, or “string” fields of each object referenced by the array’s elements, using the sorting rules described in the Sorting section above.

The sort_field parameter of these functions specifies which object field they analyze to sort a UDT array’s elements. The parameter can specify a field using either a “const int” or “const string” argument:

  • A “const int” argument specifies a field by its field index, where a value of 0 refers to the first field listed in the type declaration, 1 refers to the second field, and so on. The value can be any non-negative, non-na number up to one less than the total number of fields.
  • A “const string” argument specifies a field by its identifier (name). The string must literally match one of the field names listed in the type declaration.

The default sort_field value is 0, meaning that an array.sort() or array.sort_indices() call attempts to compare values from the first field of each object referenced by the specified array if no argument is specified.

The following example script demonstrates the sorting behavior for arrays of UDT elements. The script declares a custom type named myType with three fields: field0, field1, and field2. On the last historical bar, it creates five myType objects, stores their IDs in an array, then executes an array.sort() call to sort the array in ascending order using each object’s first, second, or third field, depending on the selected inputs. The script loops through the sorted array using a for…in loop to create a custom string representation of its structure, then displays the resulting string’s text in a label:

image

//@version=6 indicator("Sorting UDT arrays demo") //@type A custom type for creating objects that store "float", "string", and "int" values. type myType float field0 // This field's index is 0. string field1 // This field's index is 1. int field2 // This field's index is 2. //@variable A string to indicate whether the script specifies sorting fields by index or name. string specifyInput = input.string("Index", "Specify a field using its", ["Index", "Name"]) //@variable The index of the field to use for sorting if the `specifyInput` value is `"Index"`. int indexInput = input.int(0, "Field index", 0, 2, active = specifyInput == "Index") //@variable The name of the field to use for sorting if the `specifyInput` value is `"Name"`. string nameInput = input.string("field0", "Field name", ["field0", "field1", "field2"], active = specifyInput == "Name") if barstate.islastconfirmedhistory //@variable References an array that stores the IDs of `myType` objects. array<myType> udtArray = array.from( myType.new(field0 = 2.0, field1 = "D", field2 = 1), myType.new(field0 = 1.0, field1 = "E", field2 = 2), myType.new(field0 = 3.0, field1 = "C", field2 = 3), myType.new(field0 = 5.0, field1 = "A", field2 = 4), myType.new(field0 = 4.0, field1 = "B", field2 = 5) ) // Sort the array in ascending order. Use the field at the specified index if the `specifyInput` value is `"Index"`. if specifyInput == "Index" switch indexInput 0 => udtArray.sort(sort_field = 0) 1 => udtArray.sort(sort_field = 1) 2 => udtArray.sort(sort_field = 2) // Otherwise, sort using the field with the specified name. else switch nameInput "field0" => udtArray.sort(sort_field = "field0") "field1" => udtArray.sort(sort_field = "field1") "field2" => udtArray.sort(sort_field = "field2") //@variable A string representing the structure of the sorted array. string displayStr = switch specifyInput "Index" => str.format("Sorted using field at index {0}\n\n[", indexInput) => str.format("Sorted using field named ''{0}''\n\n[", nameInput) // Concatenate formatted strings to represent the array's structure. for [i, id] in udtArray displayStr += str.format( " (field0: {0,number,0.0}, field1: {1}, field2: {2}),\n", id.field0, id.field1, id.field2 ) // Adjust the final result to align enclosing brackets. displayStr := str.replace(str.substring(displayStr, 0, str.length(displayStr) - 2), "[ ", "[") + "]" // Display the final string's text in a label. label.new( bar_index, 0, displayStr, style = label.style_label_center, size = 30, textalign = text.align_left, text_font_family = font.family_monospace )

Note that:

  • The sort_field parameter accepts only values that have the “const” qualifier; it cannot accept values qualified as “input”, “simple”, or “series”. Therefore, to sort the array using an input-specified field, this script uses a separate array.sort() call for each input combination.

It’s important to emphasize that the array.sort() and array.sort_indices() functions can sort UDT arrays only by referencing object fields of the type “int”, “float”, or “string”. They cannot sort elements using fields of any other type.

For example, the following script declares a custom myColor type whose first field is of the type “color”. It creates an array of myColor IDs, then attempts to sort the array using an array.sort() call. The call does not include a sort_field argument, so it references each object’s first field, which is incompatible with the sorting algorithm. Consequently, a compilation error occurs:

//@version=6 indicator("Incompatible sorting field demo", overlay = true) //@type A custom type for creating objects that contain color information. type myColor color c // Index 0. float r // Index 1. float g // Index 2. float b // Index 3. //@function Creates a new `myColor` instance with pseudorandom field values. randColor() => color c = color.rgb(math.random(0, 128), math.random(128, 255), math.random(128, 255)) myColor.new(c = c, r = color.r(c), g = color.g(c), b = color.b(c)) //@variable References an array of `myColor` IDs. var array<myColor> arr = array.new<myColor>() if barstate.isfirst // Populate the array with 10 `myColor` IDs. for i = 1 to 10 arr.push(randColor()) // Call `array.sort()` using the default `sort_field` argument (0). // This call causes a *compilation error*, because the `array.sort()` function cannot sort "color" values. arr.sort() //@variable The index of the array element to retrieve. int ind = nz(int(math.round(9 * (close - low) / (high - low)))) //@variable The `myColor` ID stored at index `ind`. myColor id = arr.get(ind) // Color the bar using the `id.c` value. barcolor(id.c)

To resolve the error, we can either rearrange the type declaration to list one of the type’s “float” fields as the first one, or include a sort_field argument in the array.sort() call to specify one of those fields. For example:

image

//@version=6 indicator("Changing first field demo", overlay = true) //@type A custom type for creating objects that contain color information. type myColor float g // Moved to index 0. color c // Moved to index 1. float r // Moved to index 2. float b // Moved to index 3. //@function Creates a new `myColor` instance with pseudorandom field values. randColor() => color c = color.rgb(math.random(0, 128), math.random(128, 255), math.random(128, 255)) myColor.new(c = c, r = color.r(c), g = color.g(c), b = color.b(c)) //@variable References an array of `myColor` IDs. var array<myColor> arr = array.new<myColor>() if barstate.isfirst // Populate the array with 10 `myColor` IDs. for i = 1 to 10 arr.push(randColor()) // This call does *not* cause an error, because the default `sort_field` argument now refers // to the type's `g` field ("float"). arr.sort() //@variable The index of the array element to retrieve. int ind = nz(int(math.round(9 * (close - low) / (high - low)))) //@variable The `myColor` ID stored at index `ind`. myColor id = arr.get(ind) // Color the bar using the `id.c` value. barcolor(id.c)

The array.sort and array.sort_indices functions can sort UDT arrays whose referenced objects have “int”, “float”, or “string” fields that contain na values. However, these functions cannot sort UDT arrays that contain na elements. In a UDT array, an na element represents a nonexistent ID, meaning that there is no associated object that contains the field required for sorting. Consequently, attempting to sort a UDT array with one or more na elements causes a runtime error.

For example, the script below declares a type named Number with a single “float” field named value. On the last historical bar, it creates an array containing multiple Number IDs, two of which are na. Calling array.sort() to rearrange that array causes an error, because the na elements in the array do not refer to valid Number objects:

//@version=6 indicator("Cannot sort `na` IDs demo") //@variable A custom type for creating objects that store a single "float" value. type Number float value if barstate.islastconfirmedhistory //@variable References an array of `Number` IDs, two of which are `na`. array<Number> numbers = array.from(Number.new(1.2), na, Number.new(5.4), na, Number.new(3.14)) // This call causes a runtime error. The `na` elements do not refer to valid `Number` objects, so the function // cannot access `value` fields for sorting. numbers.sort() //@variable A string representing the array's structure. string displayStr = "[" // Concatenate a string representing the `value` field from each object referenced by the sorted array. for number in numbers displayStr += str.tostring(number.value) + ", " // Remove the final `", "` sequence and add a closing bracket. displayStr := str.substring(displayStr, 0, str.length(displayStr) - 2) + "]" // Display the resulting string's text in a label. label.new(bar_index, 0, displayStr, style = label.style_label_center, size = 40, textalign = text.align_left)

To prevent such errors, remove all na IDs from a UDT array before using the array.sort() or array.sort_indices() function on it, or replace them with the IDs of new objects that contain na fields instead.

For example, the script version below includes a user-defined function named replaceNa(), which replaces na Number IDs in an array with the IDs of new objects that contain na value fields. Using this function before sorting the array with the array.sort() call prevents the runtime error:

image

//@version=6 indicator("Replacing `na` IDs for sorting demo") //@variable A custom type for creating objects that store a single "float" value. type Number float value //@function Replaces `na` instances in a specified `Number` array with the IDs of new `Number` objects with `na` fields. replaceNa(array<Number> arrID) => if array.includes(arrID, na) // Loop through the array referenced by `arrID`. for [i, objID] in arrID // If the `objID` variable stores `na`, replace the element at index `i` with a new `Number` ID. if na(objID) arrID.set(i, Number.new()) if barstate.islastconfirmedhistory //@variable References an array of `Number` IDs, two of which are `na`. array<Number> numbers = array.from(Number.new(1.2), na, Number.new(5.4), na, Number.new(3.14)) // If we call `replaceNa()` before sorting the array, no error occurs, because all elements now refer // to a valid `Number` object. replaceNa(numbers) numbers.sort() //@variable A string representing the array's structure. string displayStr = "[" // Concatenate a string representing the `value` field from each object referenced by the sorted array. for number in numbers displayStr += str.tostring(number.value) + ", " // Remove the final `", "` sequence and add a closing bracket. displayStr := str.substring(displayStr, 0, str.length(displayStr) - 2) + "]" // Display the resulting string's text in a label. label.new(bar_index, 0, displayStr, style = label.style_label_center, size = 40, textalign = text.align_left)

Note that:

  • This example moves the IDs of all objects with an na value field to the end of the array because the script’s array.sort() call sorts the array’s elements in ascending order. If we use order.descending as the order argument, those elements move to the beginning of the array instead.

Reversing

Use array.reverse() to reverse an array:

//@version=6 indicator("`array.reverse()`") a = array.new<float>(0) array.push(a, 0) array.push(a, 1) array.push(a, 2) if barstate.islast array.reverse(a) label.new(bar_index, 0, "a: " + str.tostring(a))

Copying arrays

A copy of an array is a separate array object initialized to have the same structure as the original. Any changes to the elements stored by the copy do not affect the contents of the original array. Scripts can create shallow copies for arrays of any type by using the array.copy() function. For reference-type arrays, scripts can also use custom logic to create deep copies whose elements reference separate objects.

Shallow copies

The array.copy() function creates a shallow copy of an array of any type. A shallow copy is a new array object that initially has the same size as the original and stores the same values or references (IDs) as elements.

Although a shallow copy initially shares the same structure as the original array, it is an independent object. The script can insert new elements and overwrite, remove, reverse, or sort existing elements in the copied array without affecting the original array’s contents, regardless of the type of elements it contains.

For example, the following script creates an array of “float” values and assigns its ID to a variable named original on the last historical bar. It then uses the array.copy() function to create a shallow copy of the array and assigns its ID to a variable named copy. The script uses the array.push(), array.shift(), array.set(), and array.sort() functions to modify the copied array, then displays a string representation of the original array and the copy in a label. As shown by the label’s text, the copied array’s contents change, while the original array remains unchanged:

image

//@version=6 indicator("Modifying shallow copies demo") if barstate.islastconfirmedhistory //@variable References an array of arbitrary "float" values. array<float> original = array.from(-1.0, 4.0, 3.0, 2.0) //@variable References a shallow copy of the `original` array. array<float> copy = array.copy(original) //#region // The calls in this region modify only the *copied* array. They do not affect the original array, // because the `copy` and `original` variables reference separate objects. // If we change `copy` to `original` in these calls, they affect only the original array, not the copy. // Add a new element to the end of the copy. copy.push(1.0) // Remove the copy's first element. copy.shift() // Assign a new value to the copy's second element. copy.set(1, 3.5) // Sort the elements of the copy in ascending order. array.sort(copy) //#endregion //@variable A string representation of the original array and the copied array after modification. string displayStr = str.format("Original: {0}\nModified copy: {1}", str.tostring(original), str.tostring(copy)) // Display the text from the formatted string in a label. label.new( bar_index, 0, displayStr, style = label.style_label_center, size = 36, textalign = text.align_left, text_font_family = font.family_monospace )

For an array of value types, all changes to the data associated with a shallow copy are completely separate from the original array, because the only way to change the data accessed by an element in either array is by replacing the element.

By contrast, it is possible to modify the data associated with arrays of reference types through a shallow copy. When the array.copy() function copies an array of object IDs, it does not create new copies of the objects referenced by those elements; it creates a new array whose elements store the same IDs as the original. Consequently, if the script modifies the object referenced by one of the elements in the shallow copy, it also modifies the one referenced by the corresponding element in the original array, and vice versa, because both elements refer to the same object that exists elsewhere in memory.

The following example demonstrates this behavior. The script below creates an array containing the ID of a chart point on the last historical bar, then uses the array.copy() function to create a shallow copy of that array. The script uses the -= and := operators to modify the index field of the chart point referenced by the original array and the price field of the one referenced by the copy. Then, it displays formatted text containing the chart coordinates retrieved through both arrays inside labels positioned at those coordinates. Both labels anchor to the same location on the chart and display the same values, because the elements in the original array and the shallow copy access the same chart.point object:

image

//@version=6 indicator("Shallow copy of reference-type array demo") //@function Creates a string representing an array of `chart.point` IDs. Each listed item enclosed in parentheses // represents the `index` and `price` coordinates from the referenced chart point. repr(array<chart.point> source) => string result = "[" for item in source result += str.format("(index: {0,number,#}, price: {1,number,#.#####}), ", item.index, item.price) result := str.substring(result, 0, str.length(result) - 2) + "]" if barstate.islastconfirmedhistory //@variable References an array containing a single `chart.point` ID. array<chart.point> original = array.from(chart.point.from_index(index = bar_index, price = 0)) // The `array.copy()` call below copies the *original* chart point's *ID* into the new array. // It **does not** create a copy of that object for the new array to reference. array<chart.point> shallowCopy = array.copy(original) // Equivalent to: array.from(original.first()) // These variables reference the same chart point as the arrays, not copies of it. chart.point originalPoint = original.first() chart.point pointFromCopy = shallowCopy.first() // These two operations modify the `index` and `price` fields of the **same** chart point, regardless of whether // they access that object using the `originalPoint` or `pointFromCopy` variable: originalPoint.index -= 10 // Equivalent to: pointFromCopy.index -= 10 pointFromCopy.price := 1 // Equivalent to: originalPoint.price := 1 // Create string representations of the two arrays. // Because both arrays reference the *same* object, both strings contain the *same data*. string originalStr = "Original array:\n" + repr(original) string shallowCopyStr = "Shallow copy:\n" + repr(shallowCopy) // Draw labels using the chart point referenced by `originalPoint` and `pointFromCopy`. // The two labels anchor to the same coordinates (x = bar_index - 10, y = 1). label.new(originalPoint, originalStr, size = 36) label.new(pointFromCopy, shallowCopyStr, size = 36, style = label.style_label_up)

Note that:

  • Assigning the IDs retrieved from an array to separate variables does not create copies of the associated objects. For instance, the originalPoint and pointFromCopy variables in this script reference the same chart point as their respective arrays.

Deep copies

A deep copy of a reference-type array is a new array that has the same structure as the original, but stores separate elements. Unlike a shallow copy, which stores the same object IDs as the original array, a deep copy contains the IDs of copies of the original objects. In other words, the deep copy references different objects initialized with the same properties as the original objects. Therefore, modifying the objects referenced by the original array or the deep copy does not affect the objects referenced by the other array.

Creating a deep copy of an array is necessary only if the array stores elements of a reference type, and the script must modify the objects referenced by the copy without modifying those referenced by the original, or vice versa. If the original and copied arrays must reference the same objects, or if the script does not modify the array’s associated objects, creating a shallow copy with the array.copy() function is sufficient.

To create a deep copy of an array, programmers can do either of the following:

  • Create a new array of the same type with the array.new<type>() function. For each element in the original array, create a copy of the referenced object, then insert the copied object’s ID into the new array at the same index.
  • Create a shallow copy of the original array with the array.copy() function, then convert the shallow copy to a deep copy by replacing each element with the ID of a copied object.

In the example below, we modified the second script from the Shallow copies section by adding a user-defined function named deepCopy(). The function uses an array.copy() call to create an initial shallow copy of the array, then loops through the copy’s elements to convert it to a deep copy. Each iteration creates a shallow copy of the object referenced by the current element, then calls the array.set() function to replace that element with the copied object’s ID.

This version of the script calls deepCopy(original) to create a deep copy of the original array rather than creating a shallow copy. With this change, modifying the object referenced by the copied array no longer affects the one referenced by the original, because both arrays now reference different objects:

image

//@version=6 indicator("Deep copies demo") //@function Creates a deep copy of a reference-type array. Each element in the copy refers to a shallow copy of // the object referenced by the corresponding element in the original array. //@param source The ID of an array with elements of the `chart.point` type, a drawing type, or a user-defined type. //@returns The ID of the deep copy. deepCopy(source) => // Create an initial shallow copy of the `source` array. result = source.copy() // Loop through the array elements to convert the new array to a deep copy. for [i, item] in result // Copy each object referenced by `item`, and use the copy's ID as the new element at index `i`. copiedItem = item.copy() result.set(i, copiedItem) // Return the new array's ID. result //@function Creates a string representing an array of `chart.point` IDs. Each listed item enclosed in parentheses // represents the `index` and `price` coordinates from the referenced chart point. repr(array<chart.point> source) => string result = "[" for item in source result += str.format("(index: {0,number,#}, price: {1,number,#.#####}), ", item.index, item.price) result := str.substring(result, 0, str.length(result) - 2) + "]" if barstate.islastconfirmedhistory //@variable References an array containing a single `chart.point` ID. array<chart.point> original = array.from(chart.point.from_index(index = bar_index, price = 0)) //@variable References a deep copy of the original array. The deep copy stores a *separate* `chart.point` ID. array<chart.point> deepCopy = deepCopy(original) // These variables reference the same chart points as their respective arrays, not separate copies. chart.point originalPoint = original.first() chart.point pointFromCopy = deepCopy.first() // Because the deep copy of the array stores the ID of a separate chart point, these two operations no longer // modify the same object. The first modifies the original chart point, and the second modifies a copy. originalPoint.index -= 10 pointFromCopy.price := 1 // Create string representations of the two arrays. // Because both arrays now reference different objects, the two strings contain different data. string originalStr = "Original array:\n" + repr(original) string deepCopyStr = "Deep copy:\n" + repr(deepCopy) // Draw labels using the chart point referenced by `originalPoint` and `pointFromCopy`. // The two labels now anchor to different coordinates: (x = bar_index - 10, y = 0) and (x = bar_index, y = 1). label.new(originalPoint, originalStr, size = 36) label.new(pointFromCopy, deepCopyStr, size = 36, style = label.style_label_up)

Note that:

Slicing arrays

A slice of an array is a separate array that references the original array’s elements across a specific index range. Unlike a copy, which duplicates an original array’s structure and stores the data in a different location, a slice provides direct access to a subset of the original array’s contents. Changing the elements in a slice directly changes the elements in the original array, and vice versa.

Scripts can create slices of arrays of any type by using the array.slice() function. The function’s index_from and index_to parameters specify the index range of the elements that the resulting slice references from the original array. The index_from parameter accepts an “int” value from 0 to one less than the array’s size. The index_to parameter requires an “int” value that is greater than the index_from value and less than or equal to the array’s size. The slice references all the original array’s elements from the index specified by the index_from argument to the index that is one less than the index_to argument.

As with the element indices in a typical array, the indices in a slice range from 0 to one less than the slice’s size. However, these indices do not directly represent the same element indices in the original array. Each element in the slice corresponds to the original array’s element at the index index_from + i, where i is the index of the slice’s element. For instance, if the index_from value is 2, the slice’s element at index 1 refers to the original array’s element at index 3.

Programmers often use slices to directly overwrite, remove, reverse, or sort elements in an array over a specific index range, or to perform calculations that require only a specific subset of an array’s elements.

The following example demonstrates the unique behaviors of array slices. On the last historical bar, the script creates an array of “int” values. Then, it creates a slice of that array to reference the elements from index 1 to index 3. The script modifies the original array to change the slice’s contents, then modifies the slice to change the contents of the original array. The script displays string representations of both arrays after each change in a table in the center of the chart pane:

image

//@version=6 indicator("Slicing demo") //@function Creates a table row to display information about a main array and its slice. method makeRow(table this, int row, string note, array<int> main, array<int> slice) => this.cell(0, row, note, text_color = chart.fg_color) this.cell( 1, row, str.format("Main: {0}\n\nSlice: {1}", main, slice), text_color = chart.fg_color, text_halign = text.align_left ) if barstate.islastconfirmedhistory //@variable References an array of integers. array<int> mainArray = array.from(1, 2, 3, 4, 5, 6, 7) //@variable References a slice of the original array from index 1 to index 3 (index 4 is not included). array<int> slice = array.slice(mainArray, 1, 4) // Initialize the display table and populate its first row. table displayTable = table.new(position.middle_center, 2, 10, border_color = chart.fg_color, border_width = 1) displayTable.makeRow(0, "Initial arrays", mainArray, slice) //#region // Any changes that affect the original array's elements at indices 1 through 3 directly affect the // contents of the slice. // Adding or removing elements in the main array changes the elements in the slice's range. mainArray.unshift(0) displayTable.makeRow(1, "Insert a new element at the start of the main array", mainArray, slice) mainArray.remove(2) displayTable.makeRow(2, "Remove the main array element at index 2", mainArray, slice) // If the original array no longer contains elements in the slice's range, the size of the slice reduces. for i = 1 to 6 mainArray.pop() displayTable.makeRow(3, "Reduce the main array to one element", mainArray, slice) // The slice's size increases again for each element added to the original array in the required index range. for i = 1 to 7 mainArray.push(i) displayTable.makeRow(4, "Push 7 new elements into the main array", mainArray, slice) //#endregion //#region // Modifying the contents of the slice directly modifies the original array's elements in the index range // covered by the slice. // Updating the first element in the slice changes the element at index 2 in the original array. slice.set(0, -1) displayTable.makeRow(5, "Update the first element in the slice", mainArray, slice) // Inserting an element into the slice increases its index range and adds a new element to the original array. slice.push(-3) displayTable.makeRow(6, "Add a new element to the end of the slice", mainArray, slice) // Removing an element from the slice removes the same element from the original array. slice.shift() displayTable.makeRow(7, "Remove the slice's first element", mainArray, slice) // Rearranging the slice also rearranges the original array's elements in the covered range. slice.sort(order.descending) displayTable.makeRow(8, "Sort the slice in descending order", mainArray, slice) //#endregion // Reassigning the `mainArray` variable does not affect the slice. The slice continues referencing the original // array's elements, while the `mainArray` variable now references a completely separate array. mainArray := array.new<int>(5, 1) displayTable.makeRow(9, "Assign a new array ID to `mainArray`", mainArray, slice)

Note that:

  • If the original array no longer contains elements at the indices required by the slice, the slice’s size is automatically reduced, as shown by row 3 in the table. However, the script can re-add elements to restore the slice to its maximum size, as shown by row 4.
  • The maximum index range covered by a slice changes only if the script explicitly inserts new elements into the slice or removes existing ones, as shown by rows 6 and 7.
  • As shown by the final table row, reassigning an array variable that a script uses to create a slice does not affect that slice. A slice always references a subset of the elements from a specific array; it is not associated with any variables or fields that store that array’s ID.

Searching arrays

We can test if a value is part of an array with the array.includes() function, which returns true if the element is found. We can find the first occurrence of a value in an array by using the array.indexof() function. The first occurrence is the one with the lowest index. We can also find the last occurrence of a value with array.lastindexof():

//@version=6 indicator("Searching in arrays") valueInput = input.int(1) a = array.new<float>(0) array.push(a, 0) array.push(a, 1) array.push(a, 2) array.push(a, 1) if barstate.islast valueFound = array.includes(a, valueInput) firstIndexFound = array.indexof(a, valueInput) lastIndexFound = array.lastindexof(a, valueInput) label.new(bar_index, 0, "a: " + str.tostring(a) + "\nFirst " + str.tostring(valueInput) + (firstIndexFound != -1 ? " value was found at index: " + str.tostring(firstIndexFound) : " value was not found.") + "\nLast " + str.tostring(valueInput) + (lastIndexFound != -1 ? " value was found at index: " + str.tostring(lastIndexFound) : " value was not found."))

We can also perform a binary search on an array but note that performing a binary search on an array means that the array will first need to be sorted in ascending order only. The array.binary_search() function will return the value’s index if it was found or -1 if it wasn’t. If we want to always return an existing index from the array even if our chosen value wasn’t found, then we can use one of the other binary search functions available. The array.binary_search_leftmost() function, which returns an index if the value was found or the first index to the left where the value would be found. The array.binary_search_rightmost() function is almost identical and returns an index if the value was found or the first index to the right where the value would be found.

Error handling

Malformed array.*() call syntax in Pine scripts will cause the usual compiler error messages to appear in Pine Editor’s console, at the bottom of the window, when you save a script. Refer to the Pine Script v6 Reference Manual when in doubt regarding the exact syntax of function calls.

Scripts using arrays can also throw runtime errors, which appear as an exclamation mark next to the indicator’s name on the chart. We discuss some of the most common runtime errors in this section.

Index xx is out of bounds. Array size is yy

This error is the most frequent one programmers encounter when using arrays. The error occurs when the script references a nonexistent array index. The “xx” value represents the out-of-bounds index the function tried to use, and “yy” represents the array’s size. Recall that array indices start at zero — not one — and end at the array’s size, minus one. For instance, the last valid index in a three-element array is 2.

To avoid this error, you must make provisions in your code logic to prevent using an index value outside the array’s boundaries. This code example generates the error because the last i value in the loop’s iterations is beyond the valid index range for the a array:

//@version=6 indicator("Out of bounds index") a = array.new<float>(3) for i = 1 to 3 array.set(a, i, i) plot(array.pop(a))

To resolve the error, last i value in the loop statement should be less than or equal to 2:

for i = 0 to 2

To iterate over all elements in an array of unknown size with a for loop, set the loop counter’s final value to one less than the array.size() value:

//@version=6 indicator("Protected `for` loop") sizeInput = input.int(0, "Array size", minval = 0, maxval = 100000) 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))

When sizing arrays dynamically using a field in the script’s Settings/Inputs tab, protect the boundaries of that value using input.int()‘s minval and maxval parameters:

//@version=6 indicator("Protected array size") sizeInput = input.int(10, "Array size", minval = 1, maxval = 100000) a = array.new<float>(sizeInput) for i = 0 to sizeInput - 1 array.set(a, i, i) plot(array.size(a))

See the Looping through array elements section of this page for more information.

Cannot call array methods when ID of array is ‘na’

If an array variable is initialized with na, using array.*() functions on that variable is not allowed, because the variable does not store the ID of an existing array. Note that an empty array containing no elements still has a valid ID. A variable that references an empty array still holds a valid ID, whereas a variable that stores na does not. The code below demonstrates this error:

//@version=6 indicator("Array methods on `na` array") array<int> a = na array.push(a, 111) label.new(bar_index, 0, "a: " + str.tostring(a))

To avoid the error, create an empty array and assign its reference to the variable instead. For example:

array<int> a = array.new<int>(0)

Note that the array<int> type identifier in the above declaration is optional. We can define the variable without it. For example:

a = array.new<int>(0)

Array is too large. Maximum size is 100000

This error appears if your code attempts to declare an array with a size greater than 100,000. It will also occur if, while dynamically appending elements to an array, a new element would increase the array’s size past the maximum.

Cannot create an array with a negative size

We haven’t found any use for arrays of negative size yet, but if you ever do, we may allow them :)

Cannot use shift() if array is empty.

This error occurs if array.shift() is called to remove the first element of an empty array.

Cannot use pop() if array is empty.

This error occurs if array.pop() is called to remove the last element of an empty array.

Index ‘from’ should be less than index ‘to’

When two indices are used in functions such as array.slice(), the first index must always be smaller than the second one.

Slice is out of bounds of the parent array

This message occurs whenever the parent array’s size is modified in such a way that it makes the shallow copy created by a slice point outside the boundaries of the parent array. This code will reproduce it because after creating a slice from index 3 to 4 (the last two elements of our five-element parent array), we remove the parent’s first element, making its size four and its last index 3. From that moment on, the shallow copy which is still pointing to the “window” at the parent array’s indices 3 to 4, is pointing out of the parent array’s boundaries:

//@version=6 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)