X
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © fikira
//@version=5

// @description a collection of 'special' methods/functions ('special' at the time of conception)
// Initial functions includes:
// • count of a given number in a given array
// • array.get() but option added to use negative index
// • sum of all digits until the output < 10
// • slope/angle calculation of lines

library("X", overlay=true)

n = bar_index

_                                                                                                                                                                                                                                                                = "
  -----------------------------  -----------------------------  -----------------------------  ----------------------------
                                                       METHODS : FUNCTIONS      
  -----------------------------  -----------------------------  -----------------------------  ----------------------------                                                                                                                                        "            


_                                                                                                                                                                                                                                                                = "
  -----------------------------  -----------------------------  -----------------------------  ----------------------------
  Typically, we use loops for counting how many values an array contains. With count_num_in_array(), we don't 
  use a loop, but the build-in functions array.sort(), array.includes(), array.lastindexof() and array.indexof()
  This should give us better performance due to using less loops                            
  -----------------------------  -----------------------------  -----------------------------  ----------------------------                                                                                                                                        "            

// @function counts how many times a given number is present in a given array (0 when not present)
// @param arr array<int>
// @param num Number that needs to be counted (int, float)
// @returns count of number in array (0 when not present)
export method count_num_in_array(array< int > arr, int   num) => arr.sort(), arr.includes(num) ? arr.lastindexof(num) - arr.indexof(num) + 1 : 0

// @function counts how many times a given number is present in a given array (0 when not present)
// @param arr array<float>
// @param num Number that needs to be counted (int, float)
// @returns count of number in array (0 when not present)
export method count_num_in_array(array<float> arr, float num) => arr.sort(), arr.includes(num) ? arr.lastindexof(num) - arr.indexof(num) + 1 : 0

 

_                                                                                                                                                                                                                                                                = "
  -----------------------------  -----------------------------  -----------------------------  ----------------------------
  array.get() can only be used with a positive index, indexes as -1, -2, ... are not allowed.
  For the ones who want to, the get_() function is able to use negative indexes                         
  -----------------------------  -----------------------------  -----------------------------  ----------------------------                                                                                                                                        "            

// @function array.get() but you can use negative index (-1 is last of array, -2 is second last,...)
// @param arr array<int>
// @param idx index
// @returns value/object at index, 'na' if index is outside array
export method get_(array<int>    arr, int idx) => sz = arr.size(), sz == 0 or idx < -sz or idx > sz -1 ? na : arr.get(idx + (idx < 0 ? sz : 0))

// @function array.get() but you can use negative index (-1 is last of array, -2 is second last,...)
// @param arr array<float>
// @param idx index
// @returns value/object at index, 'na' if index is outside array
export method get_(array<float>  arr, int idx) => sz = arr.size(), sz == 0 or idx < -sz or idx > sz -1 ? na : arr.get(idx + (idx < 0 ? sz : 0))

// @function array.get() but you can use negative index (-1 is last of array, -2 is second last,...)
// @param arr array<string>
// @param idx Index
// @returns value/object at index, 'na' if index is outside array
export method get_(array<string> arr, int idx) => sz = arr.size(), sz == 0 or idx < -sz or idx > sz -1 ? na : arr.get(idx + (idx < 0 ? sz : 0))

// @function array.get() but you can use negative index (-1 is last of array, -2 is second last,...)
// @param arr array<bool>
// @param idx Index
// @returns value/object at index, 'na' if index is outside array
export method get_(array<bool>   arr, int idx) => sz = arr.size(), sz == 0 or idx < -sz or idx > sz -1 ? na : arr.get(idx + (idx < 0 ? sz : 0))

// @function array.get() but you can use negative index (-1 is last of array, -2 is second last,...)
// @param arr array<label>
// @param idx Index
// @returns value/object at index, 'na' if index is outside array
export method get_(array<label>  arr, int idx) => sz = arr.size(), sz == 0 or idx < -sz or idx > sz -1 ? na : arr.get(idx + (idx < 0 ? sz : 0))

// @function array.get() but you can use negative index (-1 is last of array, -2 is second last,...)
// @param arr array<line>
// @param idx Index
// @returns value/object at index, 'na' if index is outside array
export method get_(array<line>   arr, int idx) => sz = arr.size(), sz == 0 or idx < -sz or idx > sz -1 ? na : arr.get(idx + (idx < 0 ? sz : 0))

// @function array.get() but you can use negative index (-1 is last of array, -2 is second last,...)
// @param arr array<box>
// @param idx Index
// @returns value/object at index, 'na' if index is outside array
export method get_(array<box>    arr, int idx) => sz = arr.size(), sz == 0 or idx < -sz or idx > sz -1 ? na : arr.get(idx + (idx < 0 ? sz : 0))

// @function array.get() but you can use negative index (-1 is last of array, -2 is second last,...)
// @param arr array<color>
// @param idx Index
// @returns value/object at index, 'na' if index is outside array
export method get_(array<color>  arr, int idx) => sz = arr.size(), sz == 0 or idx < -sz or idx > sz -1 ? na : arr.get(idx + (idx < 0 ? sz : 0))



_                                                                                                                                                                                                                                                                = "
  -----------------------------  -----------------------------  -----------------------------  ----------------------------
 There are people who believe in the power of certain numbers between 0 and 9, they count every digit until they end up with a number between 0 and 9
 For example -> count every digit of 123456 -> 1+2+3+4+5+6 = 21, if the sum is higher then 9, repeat -> 2+1 = 3    
 It was not so easy to create a function which uses as less string manipulation as possible (more string manupulation is greater risk for memory errors)  
 I finally ended up with sumAllNumbers_till_under_10()        
  -----------------------------  -----------------------------  -----------------------------  ----------------------------                                                                                                                                        "            

// @function sums all separate digit numbers, it repeats the process until sum < 10 
// @param num Number (int) 
// @returns value between 0 and 9
export method sumAllNumbers_till_under_10(int   num) => calc = str.tonumber(str.replace(str.tostring(num), ".", "")) % 9, num == 0 ? 0 : calc == 0 ? 9 : calc 

// @function sums all separate digit numbers, it repeats the process until sum < 10 
// @param num Number (float)
// @returns value between 0 and 9
export method sumAllNumbers_till_under_10(float num) => calc = str.tonumber(str.replace(str.tostring(num), ".", "")) % 9, num == 0 ? 0 : calc == 0 ? 9 : calc 



_                                                                                                                                                                                                                                                                = "
  -----------------------------  -----------------------------  -----------------------------  ----------------------------
 Angle / slope calculation is tricky due to different units in X and Y axis (bar_index vs. price)     
 Also, certain techniques give very different result when comparing historical data, due to price fluctuations.
 With the techniques in XYaxis() and calculate_slope() I tried to create angles / slopes which are rather similar on all bars. 
 Importantly, XYaxis() always needs to be placed globally, calculate_slope() can be placed locally.
 The XYratio can be useful for finetuning, since every user has other habits regarding zooming in on the chart. Zooming in/out doesn't 
 change bars/price, but it does change our angle perspective. Therefore XYratio can be very useful (for example as input.float(3, '', minval=0, maxval=10))
  -----------------------------  -----------------------------  -----------------------------  ----------------------------                                                                                                                                        "            

// @function     Global function to calculate Yaxis, which is used in calculate_slope() method
// @param width  Amount of bars for reference X-axis
// @returns      Yaxis
export method XYaxis(int width) => Xaxis = math.min(math.max(1, n), width), ta.highest(Xaxis) - ta.lowest(Xaxis)
    
// @function Returns a normalised slope 
// @param width   Amount of bars to calculate height
// @param XYratio Ratio to calculate height (from width) normalised_slope calculation
// @param Yaxis   Y-axis from XYaxis() method
// @param x1      x1 of line
// @param y1      y1 of line
// @param x2      x2 of line
// @param y2      y2 of line
// @returns Tuple of [slope, angle] -> slope = price difference per bar
export method calculate_slope(int width, float XYratio, float Yaxis, int x1, float y1, int x2, float y2) => 
    height           =   width / XYratio
    diffX            =    x2   -    x1 
    diffY            =    y2   -    y1
    diffY_to_Yaxis   =  Yaxis  /  diffY
    normalised_slope = (height /  diffY_to_Yaxis) / diffX
    slope            =            diffY           / diffX
    angle            = math.round(math.atan(normalised_slope) * 180 / math.pi, 2)
    [slope, angle]



_                                                                                                                                                                                                                                                                = "
  -----------------------------  -----------------------------  -----------------------------  ----------------------------
                                                           EXAMPLES      
  -----------------------------  -----------------------------  -----------------------------  ----------------------------                                                                                                                                        "            

import fikira/X/1 as X
 
// count_num_in_array(), get_() & sumAllNumbers_till_under_10()

// Typically, we use loops for counting how many values an array contains
// With count_num_in_array(), we don't use a loop, but the build-in functions array.sort(), array.includes(), array.lastindexof() and array.indexof()
// this should give us better performance due to using less loops
aF1 = array.from(3, 4, 3, 0, 1., 2, 3, 4) // array<float> -> becomes sorted by count_num_in_array() 
aI1 = array.from(3, 4, 3, 0, 1 , 2, 3, 4) // array<int> -> becomes sorted by count_num_in_array() 
aI2 = array.from(3, 4, 3, 0, 1 , 2, 3, 4) // array<int> -> not sorted

plot(X.count_num_in_array(aF1, 3)       , color=color.red)      // count_num_in_array() used as FUNCTION
plot(aF1.count_num_in_array(5)          , color=color.blue)     // count_num_in_array() used as  METHOD
plot(aI1.count_num_in_array(0)          , color=color.white)
plot(aI2.get_(-3)                       , color=color.yellow)
plot(close.sumAllNumbers_till_under_10(), color=color.fuchsia)


// XYaxis() & calculate_slope()
type p 
    int b
    float p

var aPiv = array.new<p>(2, p.new(na, na))
len      = 10
ph       = ta.pivothigh(len, len)
bars     = 500
Yaxis    = bars.XYaxis() 

if not na(ph) 
    [slope, angle] = bars.calculate_slope(
       XYratio = 3
     , Yaxis   = Yaxis
     , x1      = aPiv.first().b
     , y1      = aPiv.first().p
     , x2      = n -len
     , y2      = ph
     )

    line.new (aPiv.first().b, aPiv.first().p, n -len, ph, color=color.fuchsia)
    label.new(                                n -len, ph
     , text      = str.format("slope: {0} / bar\nangle: {1}°", slope, angle)
     , style     = label.style_none
     , textcolor = color.yellow
     )

    aPiv.unshift(p.new(n - len, ph)), aPiv.pop() 

var label lab = label.new(
   na
 , na
 , color     =color.new(color.blue, 100)
 , style     = label.style_label_left
 , textcolor = chart.fg_color
 , size      = size.large
 )

if barstate.islast 
    lab.set_xy  (n + 1, hl2)
    lab.set_text(
      str.format(
     "4 is x times in array: {0}\narray.get(-3): {1}\nSum digits 'close' till < 10: {2}"
     , aF1.count_num_in_array(4)
     , aI2.get_(-3)
     , close.sumAllNumbers_till_under_10()
      )
     )