How to find the closest value in pinescript array?

2.8k views Asked by At

In pine script, I have an array called levels. In there, I am adding several values and sorting it out. Now I want to find the closest value from that array to the current price. How do I do that?

levels = array.new_float(size = 3, initial_value = na)

// push all value into array
array.push(levels, valOne) 
array.push(levels, valTwo) 
array.push(levels, valThree) 
.......

// sort the array
array.sort(levels, order.ascending)

// get s r value
supportForLong = array.min(levels) // I want to find the closest value in level and not min
resistanceForLong = array.max(levels)


plot(supportForLong, color = black)
plot(resistanceForLong, color = black)
// clear all element for next iteration
array.clear()
2

There are 2 answers

1
PineCoders-LucF On BEST ANSWER

Version 1

There is currently no array function to accomplish this, but we can build a custom function to do so. It returns the index of the first occurrence of the value in the array, starting from the beginning of the array:

arrayFind(_id, _val) =>
    int _return = na
    for _i = 0 to array.size(_id) - 1
        if array.get(_id, _i) == _val
            _return := _i
            break
    _return

You could use it like so:

plot(arrayFind(levels, close))

Version 2

Not too sure what I was thinking there, but:

  1. My answer isn't functionally correct as it doesn't meet your requirements. It only finds a value if that exact value is in the array, whereas you asked for the index of the closest value in the array.
  2. Existing array functions already allow us to find the index of the first or last occurrence of a value in an array: array.indexof() and array.lastindexof(). So you could use the built-in array.indexof(levels, close) instead of arrayFind(levels, close).

The correct answer to your question would be:

arrayIndexOfClosest(_id, _val) =>
    size = array.size(_id)
    int _return = na
    if size != 0
        delta = abs(array.get(_id, 0) - _val)
        _return := 0
        for _i = 1 to size - 1
            thisDelta = abs(array.get(_id, _i) - _val)
            if thisDelta < delta
                delta := thisDelta
                _return := _i
    _return

This would return the index of the closest match of a value in an array.

0
galki On

Here's another way that uses indexes and so has no need for a loop. If _v is not in _id set _push_v to true:

array_closest(_id, _v, _push_v) =>
    float _closest = na
    _copy = array.copy(_id)
    if _push_v
        array.push(_copy, _v)
    array.sort(_copy)
    i = array.indexof(_copy, _v)
    if i == 0
        _closest := array.get(_copy, 1)
    else if i == array.size(_copy) - 1
        _closest := array.get(_copy, i - 1)
    else 
        _a = array.get(_copy, i - 1)
        _b = array.get(_copy, i + 1)
        _closest := math.abs(_a - _v) > math.abs(_b - _v) ? _b : _a
    _closest

Here's an easier** way that uses built-in functions but makes 2 searches instead of 1. Make sure the _v_offset is smaller than any potential differences between any 2 values in the array:

array_closest(_id, _v) =>
    float _closest = na
    _v_offset = 0.00001
    
    _sorted = array.copy(_id) // assumes `_id` isn't already sorted 
    array.sort(_sorted) // assumes `_id` isn't already sorted
    _closest_r_i = array.binary_search_rightmost(_sorted, _v + _v_offset)
    _closest_s_i = array.binary_search_leftmost(_sorted, _v - _v_offset)
    _closest_r = _closest_r_i > -1 ? array.get(_sorted, _closest_r_i) : na
    _closest_s = _closest_s_i > -1 ? array.get(_sorted, _closest_s_i) : na
    _closest := math.abs(_closest_r - _v) < math.abs(_closest_s - _v) ? _closest_r : _closest_s
    _closest

* the reason for copying before sorting is so that the input array doesn't get sorted in the global scope

** in many cases you want to find the closest array value to a chart variable such as the close price. The first example forces you to push that chart variable into the array. The second example doesn't (you can use the array as is).