quantitative 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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