quantitative 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81321ddd563920c047db7eef1c0eaa922a582a5fc17f0c52ea49b4a629aa9010
4
- data.tar.gz: d7e00ef24cf0dcf1aaf2cb015591058e37fbbf2d0771f8d950f6d7e38da662cc
3
+ metadata.gz: 7b99f939e67992ff9a3659ef69ef8ff4bd5897baba1d269c491a7db7d1717c85
4
+ data.tar.gz: e9f456c34e16ed0c44b4b23f6e274ed44cce4ca6ad7fe654602dbd73afc281bc
5
5
  SHA512:
6
- metadata.gz: f79190f22f241256fe8a34c35ad8289c09253642a3f534ac8ee4b1fc3fc4cf5f092bbc6c263adb0bf9087067fb5772ad74ff8430f623b467e76973a7a0e95b16
7
- data.tar.gz: fa39ccd339097b6d7f18b0e5e9707643be4c224d52b5a27b0adea8b292f626f48ee4c7058771ca4316674196506920fd202b671c40446235e9d121bd9b2ea22e
6
+ metadata.gz: 4fca7722561f54543b92a7b50700eed4cb706b9bfca65405a8a101908b6ae49b547bcdd2cd13c4656c94a30ad817225118c22c905e47076c88f8feff85947bc4
7
+ data.tar.gz: 78b256560d7aec2ee19833618bd19e89f6c87ae718ad966b1d18caa18e08727b37d1232b169bce1fbc0b4ca4bac951e956fd4efefaa27450d533769f68d8ac23
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.0
data/.yardopts ADDED
@@ -0,0 +1,7 @@
1
+ --protected
2
+ --no-private
3
+ lib/quant/**/*.rb
4
+ -
5
+ CODE_OF_CONDUCT.md
6
+ DISCLAIMER.md
7
+ LICENSE
data/Gemfile CHANGED
@@ -11,4 +11,10 @@ gem "rspec", "~> 3.0"
11
11
 
12
12
  gem "rubocop", "~> 1.21"
13
13
  gem "rubocop-rspec"
14
- gem "relaxed-rubocop"
14
+ gem "relaxed-rubocop"
15
+
16
+ gem "debug"
17
+ gem "guard-rspec", "~> 4.7"
18
+ gem "yard", "~> 0.9"
19
+ gem "benchmark-ips", "~> 2.9"
20
+
data/Gemfile.lock CHANGED
@@ -1,13 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- quantitative (0.1.0)
4
+ quantitative (0.1.1)
5
5
  oj (~> 3.10)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  ast (2.4.2)
11
+ benchmark-ips (2.13.0)
12
+ bigdecimal (3.1.6)
11
13
  coderay (1.1.3)
12
14
  debug (1.8.0)
13
15
  irb (>= 1.5.0)
@@ -44,7 +46,8 @@ GEM
44
46
  notiffany (0.1.3)
45
47
  nenv (~> 0.1)
46
48
  shellany (~> 0.0)
47
- oj (3.16.1)
49
+ oj (3.16.3)
50
+ bigdecimal (>= 3.0)
48
51
  parallel (1.24.0)
49
52
  parser (3.3.0.4)
50
53
  ast (~> 2.4.1)
@@ -112,6 +115,7 @@ PLATFORMS
112
115
  arm64-darwin-22
113
116
 
114
117
  DEPENDENCIES
118
+ benchmark-ips (~> 2.9)
115
119
  debug
116
120
  guard-rspec (~> 4.7)
117
121
  quantitative!
data/lib/quant/errors.rb CHANGED
@@ -4,4 +4,5 @@ module Quant
4
4
  class Error < StandardError; end
5
5
  class InvalidInterval < Error; end
6
6
  class InvalidResolution < Error; end
7
+ class ArrayMaxSizeError < Error; end
7
8
  end
@@ -0,0 +1,195 @@
1
+ module Quant
2
+ module Refinements
3
+ # Refinements for the standard Ruby +Array+ class.
4
+ # These refinements add statistical methods to the Array class as well as some optimizations that greatly
5
+ # speed up some of the computations performed by the various indicators.
6
+ #
7
+ # In addtion to the statistical methods, the refinements also add a +max_size!+ method to the Array class, which
8
+ # allows us to bound the array to a maximum number of elements, which is useful for indicators that are computing
9
+ # averages or sums over a fixed number of lookback ticks.
10
+ #
11
+ # There are some various performance benchmarks in the spec/performance directory that show the performance
12
+ # improvements of using these refinements.
13
+ #
14
+ # Keep in mind that within this library, we're generally concerned with adding to the tail of the arrays and
15
+ # rarely with removing or popping, so there's few optimizations or protections for those operations in
16
+ # conjunction with the +max_size+ setting. The +max_size+ has also been designed to be set only once, to avoid
17
+ # adding additional complexity to the code that is unnecessary until a use-case presents itself.
18
+ #
19
+ # Usage: Call +using Quant+ in the file or scope where you want to use these refinements. It does not matter
20
+ # if the arrays were instantiated outside the scope of the refinement, the refinements will still be applied.
21
+ #
22
+ # @example
23
+ # using Quant
24
+ #
25
+ # array = [1, 2, 3, 4, 5]
26
+ # array.mean => 3.0
27
+ # another_array.max_size!(3).push(1, 2, 3, 4, 5, 6) => [4, 5, 6]
28
+ #
29
+ # @note The behavior of out of bound indexes into the Array deviates from standard Ruby and always returns an element.
30
+ # If an array only has three elements and 4 or more are requested for +n+, the method constrains itself to
31
+ # the size of the array. This is an intentional design decision, but it may be a gotcha if you're not expecting it.
32
+ # The refined behavior generally only exists within the library's scope, but if you call `using Quant` in your
33
+ # own code, you may encounter the changed behavior unexpectedly.
34
+ module Array
35
+
36
+ # Overrides the standard +<<+ method to track the +maximum+ and +minimum+ values
37
+ # while also respecting the +max_size+ setting.
38
+ def <<(value)
39
+ push(value)
40
+ end
41
+
42
+ # Overrides the standard +push+ method to track the +maximum+ and +minimum+ values
43
+ # while also respecting the +max_size+ setting.
44
+ def push(*objects)
45
+ Array(objects).each do |object|
46
+ super(object)
47
+ if @max_size && size > @max_size
48
+ voted_off = shift
49
+ @minimum = min if voted_off == @minimum
50
+ @maximum = max if voted_off == @maximum
51
+ else
52
+ @maximum = object if @maximum.nil? || object > @maximum
53
+ @minimum = object if @minimum.nil? || object < @minimum
54
+ end
55
+ end
56
+ self
57
+ end
58
+
59
+ # Returns the maximum element value in the array. It is an optimized version of the standard +max+ method.
60
+ def maximum
61
+ @maximum || max
62
+ end
63
+
64
+ # Returns the minimum element value in the array. It is an optimized version of the standard +min+ method.
65
+ def minimum
66
+ @minimum || min
67
+ end
68
+
69
+ # Treats the tail of the array as starting at zero and counting up. Does not overflow the head of the array.
70
+ # That is, if the +Array+ has 5 elements, prev(10) would return the first element in the array.
71
+ #
72
+ # @example
73
+ # series = [1, 2, 3, 4]
74
+ # series.prev(0) # => series[-1] => series[3] => 4
75
+ # series.prev(1) # => series[-2] => series[3] => 3
76
+ # series.prev(2) # => series[-3] => series[2] => 2
77
+ # series.prev(3) # => series[-4] => series[1] => 1
78
+ # series.prev(4) # => series[-4] => series[0] => 1 (no out of bounds!)
79
+ #
80
+ # Useful for when translating TradingView or MQ4 indicators to Ruby where those programs' indexing starts at 0
81
+ # for most recent bar and counts up to the oldest bar.
82
+ def prev(index)
83
+ raise ArgumentError, "index must be positive" if index < 0
84
+
85
+ self[[size - (index + 1), 0].max]
86
+ end
87
+
88
+ # Sets the maximum size of the array. When the size of the array exceeds the
89
+ # +max_size+, the first element is removed from the array.
90
+ # This setting modifies :<< and :push methods.
91
+ def max_size!(max_size)
92
+ # These guards are maybe not necessary, but they are here until a use-case is found.
93
+ # My concern lies with indicators that are built specifically against the +max_size+ of a given array.
94
+ raise Quant::ArrayMaxSizeError, 'cannot set max_size to nil.' unless max_size
95
+ raise Quant::ArrayMaxSizeError, 'can only max_size! once.' if @max_size
96
+ raise Quant::ArrayMaxSizeError, "size of Array #{size} exceeds max_size #{max_size}." if size > max_size
97
+
98
+ @max_size = max_size
99
+ self
100
+ end
101
+
102
+ # Computes the mean of the array. When +n+ is specified, the mean is computed over
103
+ # the last +n+ elements.
104
+ #
105
+ # @param n [Integer] the number of elements to compute the mean over
106
+ # @return [Float]
107
+ def mean(n: size)
108
+ subset = last(n)
109
+ return 0.0 if subset.empty?
110
+
111
+ sum = subset.sum / subset.size.to_f
112
+ end
113
+
114
+ # Computes the Exponential Moving Average (EMA) of the array. When +n+ is specified,
115
+ # the EMA is computed over the last +n+ elements.
116
+ # An Array of EMA's is returned, with the first entry always the first value in the subset.
117
+ #
118
+ # @params n [Integer] the number of elements to compute the EMA over.
119
+ # @return [Array<Float>]
120
+ def ema(n: size)
121
+ subset = last(n)
122
+ return [] if subset.empty?
123
+
124
+ alpha = 2.0 / (subset.size + 1)
125
+ naught_alpha = (1.0 - alpha)
126
+ pvalue = subset[0]
127
+ subset.map do |value|
128
+ pvalue = (alpha * value) + (naught_alpha * pvalue)
129
+ end
130
+ end
131
+
132
+ # Computes the Simple Moving Average (SMA) of the array. When +n+ is specified,
133
+ # the SMA is computed over the last +n+ elements.
134
+ # An Array of SMA's is returned, with the first entry always the first value in the subset.
135
+ #
136
+ # @param n [Integer] the number of elements to compute the SMA over
137
+ # @return [Array<Float>]
138
+ def sma(n: size)
139
+ subset = last(n)
140
+ return [] if subset.empty?
141
+
142
+ pvalue = subset[0]
143
+ subset.map do |value|
144
+ pvalue = (pvalue + value) / 2.0
145
+ end
146
+ end
147
+
148
+ # Computes the Weighted Moving Average (WMA) of the array. When +n+ is specified,
149
+ # the WMA is computed over the last +n+ elements.
150
+ # An Array of WMA's is returned, with the first entry always the first value in the subset.
151
+ #
152
+ # @param n [Integer] the number of elements to compute the WMA over
153
+ # @return [Array<Float>]
154
+ def wma(n: size)
155
+ subset = last(n)
156
+ return [] if subset.empty?
157
+
158
+ # ensures we return not more than number of elements in full array,
159
+ # yet have enough values to compute each iteration
160
+ max_size = [size, n].min
161
+ while subset.size <= max_size + 2
162
+ subset.unshift(subset[0])
163
+ end
164
+
165
+ subset.each_cons(4).map do |v1, v2, v3, v4|
166
+ (4.0 * v4 + 3.0 * v3 + 2.0 * v2 + v1) / 10.0
167
+ end
168
+ end
169
+
170
+ # Computes the Standard Deviation of the array. When +n+ is specified,
171
+ # the Standard Deviation is computed over the last +n+ elements.
172
+ #
173
+ # @param n [Integer] the number of elements to compute the Standard Deviation over.
174
+ # @return [Float]
175
+ def stddev(reference_value, n: size)
176
+ variance(reference_value, n: n) ** 0.5
177
+ end
178
+
179
+ def variance(reference_value, n: size)
180
+ subset = last(n)
181
+ return 0.0 if subset.empty?
182
+
183
+ subset.empty? ? 0.0 : subset.map{ |v| (v - reference_value)**2 }.mean
184
+ end
185
+ end
186
+ end
187
+
188
+ refine Array do
189
+ import_methods Quant::Refinements::Array
190
+
191
+ alias std_dev stddev
192
+ alias standard_deviation stddev
193
+ alias var variance
194
+ end
195
+ end
@@ -59,7 +59,7 @@ module Quant
59
59
  green: false,
60
60
  doji: nil)
61
61
 
62
- super(price: close_price, timestamp: close_timestamp, interval:, trades:)
62
+ super(price: close_price, timestamp: close_timestamp, interval: interval, trades: trades)
63
63
  @open_timestamp = extract_time(open_timestamp)
64
64
  @open_price = open_price.to_f
65
65
  @high_price = high_price.to_f
data/lib/quant/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quant
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/quantitative.rb CHANGED
@@ -11,7 +11,6 @@ quant_folder = File.join(lib_folder, "quant")
11
11
  Dir.glob(File.join(quant_folder, "*.rb")).each { |fn| require fn }
12
12
 
13
13
  # require sub-folders and their sub-folders
14
- %w(ticks).each do |sub_folder|
15
- Dir.glob(File.join(quant_folder, sub_folder, "*.rb")).each { |fn| require fn }
14
+ %w(refinements ticks).each do |sub_folder|
16
15
  Dir.glob(File.join(quant_folder, sub_folder, "**/*.rb")).each { |fn| require fn }
17
16
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quantitative
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Lang
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-11 00:00:00.000000000 Z
11
+ date: 2024-02-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -24,62 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.10'
27
- - !ruby/object:Gem::Dependency
28
- name: debug
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: guard-rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '4.7'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '4.7'
55
- - !ruby/object:Gem::Dependency
56
- name: rspec
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '3.2'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '3.2'
69
- - !ruby/object:Gem::Dependency
70
- name: yard
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '0.9'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '0.9'
83
27
  description: Quantitative and statistical tools written for Ruby 3.x for trading and
84
28
  finance.
85
29
  email:
@@ -90,12 +34,14 @@ extra_rdoc_files: []
90
34
  files:
91
35
  - ".rspec"
92
36
  - ".rubocop.yml"
37
+ - ".ruby-version"
38
+ - ".yardopts"
93
39
  - CODE_OF_CONDUCT.md
94
- - DISCLAIMER.txt
40
+ - DISCLAIMER.md
95
41
  - Gemfile
96
42
  - Gemfile.lock
97
43
  - Guardfile
98
- - LICENSE.txt
44
+ - LICENSE
99
45
  - README.md
100
46
  - Rakefile
101
47
  - lib/quant/errors.rb
@@ -109,6 +55,7 @@ files:
109
55
  - lib/quant/mixins/super_smoother.rb
110
56
  - lib/quant/mixins/trig.rb
111
57
  - lib/quant/mixins/weighted_average.rb
58
+ - lib/quant/refinements/array.rb
112
59
  - lib/quant/ticks/core.rb
113
60
  - lib/quant/ticks/indicator_points.rb
114
61
  - lib/quant/ticks/ohlc.rb
@@ -142,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
142
89
  - !ruby/object:Gem::Version
143
90
  version: '0'
144
91
  requirements: []
145
- rubygems_version: 3.3.7
92
+ rubygems_version: 3.5.5
146
93
  signing_key:
147
94
  specification_version: 4
148
95
  summary: Quantitative and statistical tools written for Ruby 3.x for trading and finance.
File without changes
File without changes