Module: Quant::Refinements::Array
- Defined in:
- lib/quant/refinements/array.rb
Overview
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.
Instance Method Summary collapse
-
#<<(value) ⇒ Object
Overrides the standard << method to track the
maximumandminimumvalues while also respecting themax_sizesetting. -
#ema(n: size) ⇒ Array<Float>
Computes the Exponential Moving Average (EMA) of the array.
-
#max_size!(max_size) ⇒ Object
Sets the maximum size of the array.
-
#maximum ⇒ Object
Returns the maximum element value in the array.
-
#mean(n: size) ⇒ Float
Computes the mean of the array.
-
#minimum ⇒ Object
Returns the minimum element value in the array.
-
#prev(index) ⇒ Object
Treats the tail of the array as starting at zero and counting up.
-
#push(*objects) ⇒ Object
Overrides the standard
pushmethod to track themaximumandminimumvalues while also respecting themax_sizesetting. -
#sma(n: size) ⇒ Array<Float>
Computes the Simple Moving Average (SMA) of the array.
-
#stddev(reference_value, n: size) ⇒ Float
Computes the Standard Deviation of the array.
- #variance(reference_value, n: size) ⇒ Object
-
#wma(n: size) ⇒ Array<Float>
Computes the Weighted Moving Average (WMA) of the array.
Instance Method Details
#<<(value) ⇒ Object
Overrides the standard << method to track the maximum and minimum values while also respecting the max_size setting.
38 39 40 |
# File 'lib/quant/refinements/array.rb', line 38 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. An Array of EMA’s is returned, with the first entry always the first value in the subset.
120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/quant/refinements/array.rb', line 120 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.
91 92 93 94 95 96 97 98 99 100 |
# File 'lib/quant/refinements/array.rb', line 91 def max_size!(max_size) # These guards are maybe not necessary, but they are here until a use-case is found. # My concern lies with indicators that are built specifically against the +max_size+ of a given array. raise Quant::ArrayMaxSizeError, 'cannot set max_size to nil.' unless max_size raise Quant::ArrayMaxSizeError, 'can only max_size! once.' if @max_size raise Quant::ArrayMaxSizeError, "size of Array #{size} exceeds max_size #{max_size}." if size > max_size @max_size = max_size self end |
#maximum ⇒ Object
Returns the maximum element value in the array. It is an optimized version of the standard max method.
60 61 62 |
# File 'lib/quant/refinements/array.rb', line 60 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.
107 108 109 110 111 112 |
# File 'lib/quant/refinements/array.rb', line 107 def mean(n: size) subset = last(n) return 0.0 if subset.empty? sum = subset.sum / subset.size.to_f end |
#minimum ⇒ Object
Returns the minimum element value in the array. It is an optimized version of the standard min method.
65 66 67 |
# File 'lib/quant/refinements/array.rb', line 65 def minimum @minimum || min 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.
82 83 84 85 86 |
# File 'lib/quant/refinements/array.rb', line 82 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.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/quant/refinements/array.rb', line 44 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. An Array of SMA’s is returned, with the first entry always the first value in the subset.
138 139 140 141 142 143 144 145 146 |
# File 'lib/quant/refinements/array.rb', line 138 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.
175 176 177 |
# File 'lib/quant/refinements/array.rb', line 175 def stddev(reference_value, n: size) variance(reference_value, n: n) ** 0.5 end |
#variance(reference_value, n: size) ⇒ Object
179 180 181 182 183 184 |
# File 'lib/quant/refinements/array.rb', line 179 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. An Array of WMA’s is returned, with the first entry always the first value in the subset.
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/quant/refinements/array.rb', line 154 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 |