Module: Quant::Refinements::Array

Defined in:
lib/quant/refinements/array.rb

Overview

Note:

The behavior of out of bound indexes into the Array deviates from standard Ruby and always returns an element. If an array only has three elements and 4 or more are requested for n, the method constrains itself to the size of the array. This is an intentional design decision, but it may be a gotcha if you’re not expecting it. The refined behavior generally only exists within the library’s scope, but if you call ‘using Quant` in your own code, you may encounter the changed behavior unexpectedly.

Refinements for the standard Ruby Array class. These refinements add statistical methods to the Array class as well as some optimizations that greatly speed up some of the computations performed by the various indicators.

In addtion to the statistical methods, the refinements also add a max_size! method to the Array class, which allows us to bound the array to a maximum number of elements, which is useful for indicators that are computing averages or sums over a fixed number of lookback ticks.

There are some various performance benchmarks in the spec/performance directory that show the performance improvements of using these refinements.

Keep in mind that within this library, we’re generally concerned with adding to the tail of the arrays and rarely with removing or popping, so there’s few optimizations or protections for those operations in conjunction with the max_size setting. The max_size has also been designed to be set only once, to avoid adding additional complexity to the code that is unnecessary until a use-case presents itself.

Usage: Call using Quant in the file or scope where you want to use these refinements. It does not matter if the arrays were instantiated outside the scope of the refinement, the refinements will still be applied.

Examples:

using Quant

array = [1, 2, 3, 4, 5]
array.mean => 3.0
another_array.max_size!(3).push(1, 2, 3, 4, 5, 6) => [4, 5, 6]

Instance Method Summary collapse

Instance Method Details

#<<(value) ⇒ Object

Overrides the standard << method to track the maximum and minimum values while also respecting the max_size setting.



39
40
41
# File 'lib/quant/refinements/array.rb', line 39

def <<(value)
  push(value)
end

#ema(n: size) ⇒ Array<Float>

Computes the Exponential Moving Average (EMA) of the array. When n is specified, the EMA is computed over the last n elements, otherwise it is computed over the entire array. An Array of EMA’s is returned, with the first entry always the first value in the subset.

Returns:



136
137
138
139
140
141
142
143
144
145
146
# File 'lib/quant/refinements/array.rb', line 136

def ema(n: size)
  subset = last(n)
  return [] if subset.empty?

  alpha = 2.0 / (subset.size + 1)
  naught_alpha = (1.0 - alpha)
  pvalue = subset[0]
  subset.map do |value|
    pvalue = (alpha * value) + (naught_alpha * pvalue)
  end
end

#max_size!(max_size) ⇒ Object

Sets the maximum size of the array. When the size of the array exceeds the max_size, the first element is removed from the array. This setting modifies :<< and :push methods.



92
93
94
95
96
97
98
99
100
101
102
# File 'lib/quant/refinements/array.rb', line 92

def max_size!(max_size)
  # These guards are maybe unnecessary, but they are here until a use-case is found.
  # Some indicators are built specifically against the +max_size+ of a given array.
  # Adjusting the +max_size+ after the fact could lead to unexpected, unintended behavior.
  raise Errors::ArrayMaxSizeError, "Cannot set max_size to nil." unless max_size
  raise Errors::ArrayMaxSizeError, "The max_size can only be set once." if @max_size
  raise Errors::ArrayMaxSizeError, "The size of Array #{size} exceeds max_size #{max_size}." if size > max_size

  @max_size = max_size
  self
end

#maximumObject

Returns the maximum element value in the array. It is an optimized version of the standard max method.



61
62
63
# File 'lib/quant/refinements/array.rb', line 61

def maximum
  @maximum || max
end

#mean(n: size) ⇒ Float

Computes the mean of the array. When n is specified, the mean is computed over the last n elements, otherwise it is computed over the entire array. If the array is empty, 0.0 is returned.

Parameters:

  • n (Integer) (defaults to: size)

    the number of elements to compute the mean over

Returns:

  • (Float)


110
111
112
113
114
115
# File 'lib/quant/refinements/array.rb', line 110

def mean(n: size)
  subset = last(n)
  return 0.0 if subset.empty?

  subset.sum / subset.size.to_f
end

#minimumObject

Returns the minimum element value in the array. It is an optimized version of the standard min method.



66
67
68
# File 'lib/quant/refinements/array.rb', line 66

def minimum
  @minimum || min
end

#peak(n: size) ⇒ Float

Returns the largest absolute value in the array. When n is specified, the peak is computed over the last n elements, otherwise it is computed over the entire array. If the array is empty, 0.0 is returned.

Parameters:

  • n (Integer) (defaults to: size)

    the number of elements to compute the peak over

Returns:

  • (Float)


123
124
125
126
127
128
# File 'lib/quant/refinements/array.rb', line 123

def peak(n: size)
  subset = last(n)
  return 0.0 if subset.empty?

  [subset.max.abs, subset.min.abs].max
end

#prev(index) ⇒ Object

Treats the tail of the array as starting at zero and counting up. Does not overflow the head of the array. That is, if the Array has 5 elements, prev(10) would return the first element in the array.

Useful for when translating TradingView or MQ4 indicators to Ruby where those programs’ indexing starts at 0 for most recent bar and counts up to the oldest bar.

Examples:

series = [1, 2, 3, 4]
series.prev(0) # => series[-1] => series[3] => 4
series.prev(1) # => series[-2] => series[3] => 3
series.prev(2) # => series[-3] => series[2] => 2
series.prev(3) # => series[-4] => series[1] => 1
series.prev(4) # => series[-4] => series[0] => 1 (no out of bounds!)

Raises:

  • (ArgumentError)


83
84
85
86
87
# File 'lib/quant/refinements/array.rb', line 83

def prev(index)
  raise ArgumentError, "index must be positive" if index < 0

  self[[size - (index + 1), 0].max]
end

#push(*objects) ⇒ Object

Overrides the standard push method to track the maximum and minimum values while also respecting the max_size setting.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/quant/refinements/array.rb', line 45

def push(*objects)
  Array(objects).each do |object|
    super(object)
    if @max_size && size > @max_size
      voted_off = shift
      @minimum = min if voted_off == @minimum
      @maximum = max if voted_off == @maximum
    else
      @maximum = object if @maximum.nil? || object > @maximum
      @minimum = object if @minimum.nil? || object < @minimum
    end
  end
  self
end

#sma(n: size) ⇒ Array<Float>

Computes the Simple Moving Average (SMA) of the array. When n is specified, the SMA is computed over the last n elements, otherwise it is computed over the entire array. An Array of SMA’s is returned, with the first entry always the first value in the subset.

Parameters:

  • n (Integer) (defaults to: size)

    the number of elements to compute the SMA over

Returns:



154
155
156
157
158
159
160
161
162
# File 'lib/quant/refinements/array.rb', line 154

def sma(n: size)
  subset = last(n)
  return [] if subset.empty?

  pvalue = subset[0]
  subset.map do |value|
    pvalue = (pvalue + value) / 2.0
  end
end

#stddev(reference_value, n: size) ⇒ Float

Computes the Standard Deviation of the array. When n is specified, the Standard Deviation is computed over the last n elements, otherwise it is computed over the entire array.

Parameters:

  • n (Integer) (defaults to: size)

    the number of elements to compute the Standard Deviation over.

Returns:

  • (Float)


192
193
194
# File 'lib/quant/refinements/array.rb', line 192

def stddev(reference_value, n: size)
  variance(reference_value, n:)**0.5
end

#variance(reference_value, n: size) ⇒ Object



196
197
198
199
200
201
# File 'lib/quant/refinements/array.rb', line 196

def variance(reference_value, n: size)
  subset = last(n)
  return 0.0 if subset.empty?

  subset.empty? ? 0.0 : subset.map{ |v| (v - reference_value)**2 }.mean
end

#wma(n: size) ⇒ Array<Float>

Computes the Weighted Moving Average (WMA) of the array. When n is specified, the WMA is computed over the last n elements, otherwise it is computed over the entire array. An Array of WMA’s is returned, with the first entry always the first value in the subset.

Parameters:

  • n (Integer) (defaults to: size)

    the number of elements to compute the WMA over

Returns:



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/quant/refinements/array.rb', line 170

def wma(n: size)
  subset = last(n)
  return [] if subset.empty?

  # ensures we return not more than number of elements in full array,
  # yet have enough values to compute each iteration
  max_size = [size, n].min
  while subset.size <= max_size + 2
    subset.unshift(subset[0])
  end

  subset.each_cons(4).map do |v1, v2, v3, v4|
    (4.0 * v4 + 3.0 * v3 + 2.0 * v2 + v1) / 10.0
  end
end