quantitative 0.1.9 → 0.2.0

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/Guardfile +1 -1
  4. data/Rakefile +6 -1
  5. data/lib/quant/attributes.rb +31 -43
  6. data/lib/quant/config.rb +8 -0
  7. data/lib/quant/experimental.rb +20 -0
  8. data/lib/quant/indicators/dominant_cycle_indicators.rb +10 -0
  9. data/lib/quant/indicators/dominant_cycles/acr.rb +101 -0
  10. data/lib/quant/indicators/dominant_cycles/band_pass.rb +80 -0
  11. data/lib/quant/indicators/dominant_cycles/differential.rb +19 -0
  12. data/lib/quant/indicators/dominant_cycles/dominant_cycle.rb +128 -0
  13. data/lib/quant/indicators/dominant_cycles/homodyne.rb +27 -0
  14. data/lib/quant/indicators/dominant_cycles/phase_accumulator.rb +59 -0
  15. data/lib/quant/indicators/indicator.rb +30 -8
  16. data/lib/quant/indicators/indicator_point.rb +12 -2
  17. data/lib/quant/indicators.rb +9 -2
  18. data/lib/quant/indicators_proxy.rb +0 -3
  19. data/lib/quant/indicators_sources.rb +1 -1
  20. data/lib/quant/interval.rb +6 -9
  21. data/lib/quant/mixins/filters.rb +5 -42
  22. data/lib/quant/mixins/functions.rb +7 -3
  23. data/lib/quant/mixins/high_pass_filters.rb +129 -0
  24. data/lib/quant/mixins/super_smoother.rb +18 -15
  25. data/lib/quant/mixins/universal_filters.rb +326 -0
  26. data/lib/quant/series.rb +1 -1
  27. data/lib/quant/statistics/correlation.rb +37 -0
  28. data/lib/quant/ticks/ohlc.rb +5 -4
  29. data/lib/quant/time_methods.rb +4 -0
  30. data/lib/quant/time_period.rb +13 -14
  31. data/lib/quant/version.rb +1 -1
  32. data/lib/quantitative.rb +1 -1
  33. metadata +13 -4
  34. data/lib/quant/indicators/ma.rb +0 -40
  35. data/lib/quant/mixins/high_pass_filter.rb +0 -54
@@ -0,0 +1,326 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ module Mixins
5
+ # Source: Ehlers, J. F. (2013). Cycle Analytics for Traders:
6
+ # Advanced Technical Trading Concepts. John Wiley & Sons.
7
+ #
8
+ # == Universal Filters
9
+ # Ehlers devoted a chapter in his book to generalizing the algorithms
10
+ # for the most common filters used in trading. The universal filters
11
+ # makes an attempt to translate that work into working code. However,
12
+ # Ehler write up contained some typos and incomplete treatment of the
13
+ # subject matter, and as a non-mathematician, I am not sure if I have
14
+ # translated the formulas correctly. So far, I have not been able to
15
+ # prove correctness of the universal EMA vs. the optimzed EMA, but
16
+ # the others are still unproven and Ehlers' many papers over the year
17
+ # tend to change implementation details, too.
18
+ #
19
+ # == Experimental!
20
+ # The main goal with the universal filters is to provide a means to
21
+ # compare the optimized filters with the generalized filters and
22
+ # generally show correctness of the solutions. However, that also
23
+ # means validating the outputs of those computations, which is not my forté.
24
+ # My idea of validating is if I have two or more implementations that produce
25
+ # identical (or nearly identical) results, then I consider the implementation
26
+ # sound and doing what it is supposed to do.
27
+ #
28
+ # Several are marked "experimental" because I have not been able to
29
+ # prove their correctness. Those that are proven correct are not
30
+ # marked as experimental and you'll find their outputs show up in other
31
+ # specs where they're used alongside the optimized versions of those filters.
32
+ #
33
+ # == Ehlers' Notes on Generalized Filters
34
+ # 1. All the common filters useful for traders have a transfer response
35
+ # that can be written as a ratio of two polynomials.
36
+ # 2. Lag is very important to traders. More complex filters can be
37
+ # created using more input data, but more input data increases lag.
38
+ # Sophisticated filters are not very useful for trading because they
39
+ # incur too much lag.
40
+ # 3. Filter transfer response can be viewed in the time domain and
41
+ # the frequency domain with equal validity.
42
+ # 4. Nonrecursive filters can have zeros in the transfer response, enabling
43
+ # the complete cancellation of some selected frequency components.
44
+ # 5. Nonrecursive filters having coefficients symmetrical about the
45
+ # center of the filter will have a delay of half the degree of the
46
+ # transfer response polynomial at all frequencies.
47
+ # 6. Low-pass filters are smoothers because they attenuate the high-frequency
48
+ # components of the input data.
49
+ # 7. High-pass filters are detrenders because they attenuate the
50
+ # low-frequency components of trends.
51
+ # 8. Band-pass filters are both detrenders and smoothers because they
52
+ # attenuate all but the desired frequency components.
53
+ # 9. Filters provide an output only through their transfer response.
54
+ # The transfer response is strictly a mathematical function, and
55
+ # interpretations such as overbought, oversold, convergence, divergence,
56
+ # and so on are not implied. The validity of such interpretations
57
+ # must be made on the basis of statistics apart from the filter.
58
+ # 10. The critical period of a filter output is the frequency at which
59
+ # the output power of the filter is half the power of the input
60
+ # wave at that frequency.
61
+ # 11. A WMA has little or no redeeming virtue.
62
+ # 12. A median filter is best used when the data contain impulsive noise
63
+ # or when there are wild variations in the data. Smoothing volume
64
+ # data is one example of a good application for a median filter.
65
+ #
66
+ # == Filter Coefficients forVariousTypes of Filters
67
+ #
68
+ # Filter Type b0 b1 b2 a1 a2
69
+ # EMA α 0 0 −(1−α) 0
70
+ # Two-pole low-pass α**2 0 0 −2*(1-α) (1-α)**2
71
+ # High-pass (1-α/2) -(1-α/2) 0 −(1−α) 0
72
+ # Two-pole high-pass (1-α/2)**2 -2*(1-α/2)**2 (1-α/2)**2 -2*(1-α) (1-α)**2
73
+ # Band-pass (1-σ)/2 0 -(1-σ)/2 -λ*(1+σ) σ
74
+ # Band-stop (1+σ)/2 -2λ*(1+σ)/2 (1+σ)/2 -λ*(1+σ) σ
75
+ #
76
+ module UniversalFilters
77
+ K = {
78
+ single_pole: 1.0,
79
+ two_pole_high_pass: Math.sqrt(2) * 0.5,
80
+ two_pole_low_pass: Math.sqrt(2)
81
+ }.freeze
82
+
83
+ # The universal filter is a generalization of the common filters. The other
84
+ # algorithms in this module are derived from this one.
85
+ def universal_filter(source, previous:, b0:, b1:, b2:, a1:, a2:)
86
+ b0 * p0.send(source) +
87
+ b1 * p1.send(source) +
88
+ b2 * p2.send(source) -
89
+ a1 * p1.send(previous) -
90
+ a2 * p2.send(previous)
91
+ end
92
+
93
+ # The EMA is optimized in the {Quant::Mixins::ExponentialMovingAverage} module
94
+ # and its correctness is proven with this particular implementation.
95
+ def universal_ema(source, previous:, period:)
96
+ raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
97
+ raise ArgumentError, ":previous must be a Symbol" unless previous.is_a?(Symbol)
98
+
99
+ alpha = bars_to_alpha(period)
100
+ b0 = alpha
101
+ b1 = 0
102
+ b2 = 0
103
+ a1 = -(1 - alpha)
104
+ a2 = 0
105
+
106
+ universal_filter(source, previous:, b0:, b1:, b2:, a1:, a2:)
107
+ end
108
+
109
+ # The two-pole low-pass filter can serve several purposes:
110
+ #
111
+ # 1. Noise Reduction: Stock price data often contains high-frequency
112
+ # fluctuations or noise due to market volatility, algorithmic
113
+ # trading, or other factors. By applying a low-pass filter, you can
114
+ # smooth out these fluctuations, making it easier to identify
115
+ # underlying trends or patterns in the price action.
116
+ # 2. Trend Identification: Low-pass filtering can help in identifying
117
+ # the underlying trend in stock price movements by removing short-term
118
+ # fluctuations and emphasizing longer-term movements. This can be
119
+ # useful for trend-following strategies or identifying potential
120
+ # trend reversals.
121
+ # 3. Signal Smoothing: Filtering can help in removing erratic or
122
+ # spurious movements in the price data, providing a clearer and
123
+ # more consistent representation of the overall price action.
124
+ # 4. Highlighting Structural Changes: Filtering can make structural
125
+ # changes in the price action more apparent by reducing noise and
126
+ # focusing on significant movements. This can be useful for detecting
127
+ # shifts in market sentiment or the emergence of new trends.
128
+ # 5. Trade Signal Generation: Smoothed price data from a low-pass filter
129
+ # can be used as input for trading strategies, such as moving
130
+ # average-based strategies or momentum strategies, where identifying
131
+ # trends or momentum is crucial.
132
+ def universal_two_pole_low_pass(source, previous:, period:)
133
+ Quant.experimental("This method is unproven and may be incorrect.")
134
+ raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
135
+ raise ArgumentError, ":previous must be a Symbol" unless previous.is_a?(Symbol)
136
+
137
+ alpha = period_to_alpha(period, k: K[:two_pole_low_pass] )
138
+ b0 = alpha**2
139
+ b1 = 0
140
+ b2 = 0
141
+ a1 = -2.0 * (1.0 - alpha)
142
+ a2 = (1.0 - alpha)**2
143
+
144
+ universal_filter(source, previous:, b0:, b1:, b2:, a1:, a2:)
145
+ end
146
+
147
+ # A single-pole low-pass filter, also known as a first-order low-pass
148
+ # filter, has a simpler response compared to a two-pole low-pass filter.
149
+ # It attenuates higher-frequency components of the signal while allowing
150
+ # lower-frequency components to pass through, but it does so with a
151
+ # gentler roll-off compared to higher-order filters.
152
+ #
153
+ # Here's what a single-pole low-pass filter typically does to stock
154
+ # price action data:
155
+ # 1. Noise Reduction: Similar to a two-pole low-pass filter, a single-pole
156
+ # filter can help in reducing high-frequency noise in stock price data,
157
+ # smoothing out rapid fluctuations caused by market volatility or other factors.
158
+ # 2. Trend Identification: It can aid in identifying trends by smoothing
159
+ # out short-term fluctuations, making the underlying trend more apparent.
160
+ # However, compared to higher-order filters, it may not provide as
161
+ # sharp or accurate trend signals.
162
+ # 3. Signal Smoothing: Single-pole filters provide a basic level of signal
163
+ # smoothing, which can help in removing minor fluctuations and emphasizing
164
+ # larger movements in the price action. This can make the data easier to
165
+ # interpret and analyze.
166
+ # 4. Delay and Responsiveness: Single-pole filters introduce less delay
167
+ # compared to higher-order filters, making them more responsive to changes
168
+ # in the input signal. However, this responsiveness comes at the cost of a
169
+ # less aggressive attenuation of high-frequency noise.
170
+ # 5. Simple Filtering: Single-pole filters are computationally efficient and
171
+ # easy to implement, making them suitable for real-time processing and
172
+ # applications where simplicity is preferred.
173
+ #
174
+ # Overall, while a single-pole low-pass filter can still be effective for
175
+ # noise reduction and basic trend identification in stock price action data,
176
+ # it may not offer the same level of precision and robustness as higher-order
177
+ # filters. The choice between single-pole and higher-order filters depends on
178
+ # the specific requirements of the analysis and the trade-offs between
179
+ # responsiveness and noise attenuation.
180
+ def universal_one_pole_low_pass(source, previous:, period:)
181
+ Quant.experimental("This method is unproven and may be incorrect.")
182
+ raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
183
+ raise ArgumentError, ":previous must be a Symbol" unless previous.is_a?(Symbol)
184
+
185
+ alpha = period_to_alpha(period, k: K[:single_pole])
186
+ b0 = alpha
187
+ b1 = 0
188
+ b2 = 0
189
+ a1 = -(1 - alpha)
190
+ a2 = 0
191
+
192
+ universal_filter(source, previous:, b0:, b1:, b2:, a1:, a2:)
193
+ end
194
+
195
+ # A single-pole high-pass filter, also known as a first-order high-pass
196
+ # filter, attenuates low-frequency components of a signal while allowing
197
+ # higher-frequency components to pass through. In the context of processing
198
+ # stock price action data, applying a single-pole high-pass filter has
199
+ # several potential effects:
200
+ #
201
+ # 1. Removal of Low-Frequency Trends: Similar to higher-order high-pass
202
+ # filters, a single-pole high-pass filter removes or attenuates slow-moving
203
+ # components of the price action data, such as long-term trends. This can
204
+ # help in focusing on shorter-term fluctuations and identifying short-term
205
+ # trading opportunities.
206
+ # 2. Noise Amplification: As with higher-order high-pass filters, a single-pole
207
+ # high-pass filter can amplify high-frequency noise present in the data,
208
+ # especially if the cutoff frequency is set too low. This noise amplification
209
+ # can make it challenging to analyze the data accurately, particularly if the
210
+ # noise overwhelms the signal of interest.
211
+ # 3. Emphasis on Short-Term Variations: By removing low-frequency components, a
212
+ # single-pole high-pass filter highlights short-term variations and rapid
213
+ # movements in the price action data. This can be beneficial for traders or
214
+ # analysts who are primarily interested in short-term price dynamics or
215
+ # intraday trading opportunities.
216
+ # 4. Simple Filtering: Single-pole filters are computationally efficient and
217
+ # straightforward to implement, making them suitable for real-time processing
218
+ # and applications where simplicity is preferred. However, they may not offer
219
+ # the same level of noise attenuation and signal preservation as higher-order
220
+ # filters.
221
+ # 5. Enhanced Responsiveness: Single-pole high-pass filters offer relatively high
222
+ # responsiveness to changes in the input signal, reflecting recent price movements
223
+ # quickly. This responsiveness can be advantageous for certain trading strategies
224
+ # that rely on timely identification of market events or short-term trends.
225
+ #
226
+ # Overall, applying a single-pole high-pass filter to stock price action data can
227
+ # help in removing low-frequency trends and focusing on short-term variations and
228
+ # rapid movements. However, it's essential to carefully select the cutoff frequency
229
+ # to balance noise attenuation with signal preservation and to consider the potential
230
+ # trade-offs between simplicity and filtering effectiveness.
231
+ def universal_one_pole_high_pass(source, previous:, period:)
232
+ Quant.experimental("This method is unproven and may be incorrect.")
233
+ raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
234
+ raise ArgumentError, ":previous must be a Symbol" unless previous.is_a?(Symbol)
235
+
236
+ alpha = period_to_alpha(period, k: K[:single_pole])
237
+ b0 = (1 - alpha / 2)
238
+ b1 = -(1 - alpha / 2)
239
+ b2 = 0
240
+ a1 = -(1 - alpha)
241
+ a2 = 0
242
+
243
+ universal_filter(source, previous:, b0:, b1:, b2:, a1:, a2:)
244
+ end
245
+
246
+ # A two-pole high-pass filter, also known as a second-order high-pass
247
+ # filter, attenuates low-frequency components of a signal while allowing
248
+ # higher-frequency components to pass through. In the context of
249
+ # processing stock price action data, applying a two-pole high-pass
250
+ # filter has several potential effects:
251
+ #
252
+ # 1. Removal of Low-Frequency Trends: High-pass filtering can remove
253
+ # or attenuate long-term trends or slow-moving components of the
254
+ # price action data. This can be useful for focusing on shorter-term
255
+ # fluctuations or identifying short-term trading opportunities.
256
+ # 2. Noise Attenuation: By suppressing low-frequency components, a
257
+ # high-pass filter can help in reducing the impact of slow-moving
258
+ # noise or irrelevant signals in the data. This can improve the
259
+ # clarity and interpretability of the price action data.
260
+ # 3. Noise Amplification: High-pass filters can amplify high-frequency
261
+ # noise present in the data, particularly if the cutoff frequency is
262
+ # set too low. This noise amplification can make it challenging to
263
+ # analyze the data accurately, especially if the noise overwhelms
264
+ # the signal of interest.
265
+ # 4. Emphasis on Short-Term Variations: By removing low-frequency
266
+ # components, a high-pass filter highlights short-term variations
267
+ # and rapid movements in the price action data. This can be beneficial
268
+ # for traders or analysts who are primarily interested in short-term
269
+ # price dynamics.
270
+ # 5. Enhanced Responsiveness: Compared to low-pass filters, high-pass
271
+ # filters typically offer greater responsiveness to changes in the
272
+ # input signal. This means that high-pass filtered data can reflect
273
+ # recent price movements more quickly, which may be advantageous for
274
+ # certain trading strategies.
275
+ # 6. Identification of Market Events: High-pass filtering can help in
276
+ # identifying specific market events or anomalies that occur on
277
+ # shorter time scales, such as intraday price spikes or volatility
278
+ # clusters.
279
+ #
280
+ # Overall, applying a two-pole high-pass filter to stock price action
281
+ # data can help in focusing on short-term variations and removing
282
+ # long-term trends or slow-moving components. However, it's essential
283
+ # to carefully select the cutoff frequency to balance noise attenuation
284
+ # with signal preservation, as excessive noise amplification can degrade
285
+ # the quality of the analysis. Additionally, high-pass filtering may
286
+ # not be suitable for all trading or analysis purposes, and its effects
287
+ # should be evaluated in the context of specific goals and strategies.
288
+ def universal_two_pole_high_pass(source, previous:, period:)
289
+ raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
290
+ raise ArgumentError, ":previous must be a Symbol" unless previous.is_a?(Symbol)
291
+
292
+ alpha = period_to_alpha(period, k: K[:two_pole_high_pass])
293
+ b0 = (1 - alpha / 2)**2
294
+ b1 = -2 * (1 - alpha / 2)**2
295
+ b2 = (1 - alpha / 2)**2
296
+ a1 = -2 * (1 - alpha)
297
+ a2 = (1 - alpha)**2
298
+
299
+ universal_filter(source, previous:, b0:, b1:, b2:, a1:, a2:)
300
+ end
301
+
302
+ # Band-pass filters are both detrenders and smoothers because they
303
+ # attenuate all but the desired frequency components.
304
+ # NOTE: Ehlers' book contains a typo in the formula for the band-pass
305
+ # filter. I am not sure what the correct formulation is, so
306
+ # this is a best guess for how, left for further investigation.
307
+ def universal_band_pass(source, previous:, period:)
308
+ Quant.experimental("This method is unproven and may be incorrect.")
309
+ raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
310
+ raise ArgumentError, ":previous must be a Symbol" unless previous.is_a?(Symbol)
311
+
312
+ lambda = deg2rad(360.0 / period)
313
+ gamma = Math.cos(lambda)
314
+ sigma = 1 / gamma - Math.sqrt(1 / gamma**2 - 1)
315
+
316
+ b0 = (1 - sigma) * 0.5
317
+ b1 = 0
318
+ b2 = -(1 - sigma) * 0.5
319
+ a1 = -lambda * (1 + sigma)
320
+ a2 = sigma
321
+
322
+ universal_filter(source, previous:, b0:, b1:, b2:, a1:, a2:)
323
+ end
324
+ end
325
+ end
326
+ end
data/lib/quant/series.rb CHANGED
@@ -114,7 +114,7 @@ module Quant
114
114
 
115
115
  def to_h
116
116
  { "symbol" => symbol,
117
- "interval" => interval,
117
+ "interval" => interval.to_s,
118
118
  "ticks" => ticks.map(&:to_h) }
119
119
  end
120
120
 
@@ -0,0 +1,37 @@
1
+ module Quant
2
+ module Statistics
3
+ class Correlation
4
+ attr_accessor :length, :sx, :sy, :sxx, :sxy, :syy
5
+
6
+ def initialize
7
+ @length = 0.0
8
+ @sx = 0.0
9
+ @sy = 0.0
10
+ @sxx = 0.0
11
+ @sxy = 0.0
12
+ @syy = 0.0
13
+ end
14
+
15
+ def add(x, y)
16
+ @length += 1
17
+ @sx += x
18
+ @sy += y
19
+ @sxx += x * x
20
+ @sxy += x * y
21
+ @syy += y * y
22
+ end
23
+
24
+ def devisor
25
+ value = (length * sxx - sx**2) * (length * syy - sy**2)
26
+ value.zero? ? 1.0 : value
27
+ end
28
+
29
+ def coefficient
30
+ (length * sxy - sx * sy) / Math.sqrt(devisor)
31
+ rescue Math::DomainError
32
+ 0.0
33
+ end
34
+ end
35
+ end
36
+ end
37
+
@@ -82,9 +82,10 @@ module Quant
82
82
  # A value of 0.0 means no change.
83
83
  # @return [Float]
84
84
  def daily_price_change
85
- ((open_price / close_price) - 1.0)
86
- rescue ZeroDivisionError
87
- 0.0
85
+ return open_price.zero? ? 0.0 : -1.0 if close_price.zero?
86
+ return 0.0 if open_price == close_price
87
+
88
+ (open_price / close_price) - 1.0
88
89
  end
89
90
 
90
91
  # Calculates the absolute change from the open_price to the close_price, divided by the average of the
@@ -93,7 +94,7 @@ module Quant
93
94
  # This method is useful for comparing the volatility of different assets.
94
95
  # @return [Float]
95
96
  def daily_price_change_ratio
96
- @daily_price_change_ratio ||= ((open_price - close_price) / oc2).abs
97
+ (open_price - close_price).abs / oc2
97
98
  end
98
99
 
99
100
  # Set the #green? property to true when the close_price is greater than or equal to the open_price.
@@ -41,6 +41,10 @@ module Quant
41
41
  case value
42
42
  when Time
43
43
  value.utc
44
+ when DateTime
45
+ Time.new(value.year, value.month, value.day, value.hour, value.minute, value.second).utc
46
+ when Date
47
+ Time.utc(value.year, value.month, value.day, 0, 0, 0)
44
48
  when Integer
45
49
  Time.at(value).utc
46
50
  when String
@@ -36,7 +36,7 @@ module Quant
36
36
  def validate_bounds!
37
37
  return if lower_bound? || upper_bound?
38
38
 
39
- raise "TimePeriod cannot be unbounded at start_at and end_at"
39
+ raise "TimePeriod cannot be unbound at start_at and end_at"
40
40
  end
41
41
 
42
42
  def cover?(value)
@@ -48,11 +48,19 @@ module Quant
48
48
  end
49
49
 
50
50
  def lower_bound?
51
- !!@start_at
51
+ !lower_unbound?
52
+ end
53
+
54
+ def lower_unbound?
55
+ @start_at.nil?
56
+ end
57
+
58
+ def upper_unbound?
59
+ @end_at.nil?
52
60
  end
53
61
 
54
62
  def upper_bound?
55
- !!@end_at
63
+ !upper_unbound?
56
64
  end
57
65
 
58
66
  def end_at
@@ -66,17 +74,8 @@ module Quant
66
74
  def ==(other)
67
75
  return false unless other.is_a?(TimePeriod)
68
76
 
69
- if lower_bound?
70
- other.lower_bound? && start_at == other.start_at
71
- elsif upper_bound?
72
- oher.upper_bound? && end_at == other.end_at
73
- else
74
- [start_at, end_at] == [other.start_at, other.end_at]
75
- end
76
- end
77
-
78
- def eql?(other)
79
- self == other
77
+ [lower_bound?, upper_bound?, start_at, end_at] ==
78
+ [other.lower_bound?, other.upper_bound?, other.start_at, other.end_at]
80
79
  end
81
80
 
82
81
  def to_h
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.9"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/quantitative.rb CHANGED
@@ -12,6 +12,6 @@ quant_folder = File.join(lib_folder, "quant")
12
12
  Dir.glob(File.join(quant_folder, "*.rb")).each { |fn| require fn }
13
13
 
14
14
  # require sub-folders and their sub-folders
15
- %w(refinements mixins settings ticks indicators).each do |sub_folder|
15
+ %w(refinements mixins statistics settings ticks indicators).each do |sub_folder|
16
16
  Dir.glob(File.join(quant_folder, sub_folder, "**/*.rb")).each { |fn| require fn }
17
17
  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.9
4
+ version: 0.2.0
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-28 00:00:00.000000000 Z
11
+ date: 2024-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -49,10 +49,17 @@ files:
49
49
  - lib/quant/attributes.rb
50
50
  - lib/quant/config.rb
51
51
  - lib/quant/errors.rb
52
+ - lib/quant/experimental.rb
52
53
  - lib/quant/indicators.rb
54
+ - lib/quant/indicators/dominant_cycle_indicators.rb
55
+ - lib/quant/indicators/dominant_cycles/acr.rb
56
+ - lib/quant/indicators/dominant_cycles/band_pass.rb
57
+ - lib/quant/indicators/dominant_cycles/differential.rb
58
+ - lib/quant/indicators/dominant_cycles/dominant_cycle.rb
59
+ - lib/quant/indicators/dominant_cycles/homodyne.rb
60
+ - lib/quant/indicators/dominant_cycles/phase_accumulator.rb
53
61
  - lib/quant/indicators/indicator.rb
54
62
  - lib/quant/indicators/indicator_point.rb
55
- - lib/quant/indicators/ma.rb
56
63
  - lib/quant/indicators/ping.rb
57
64
  - lib/quant/indicators_proxy.rb
58
65
  - lib/quant/indicators_sources.rb
@@ -63,17 +70,19 @@ files:
63
70
  - lib/quant/mixins/filters.rb
64
71
  - lib/quant/mixins/fisher_transform.rb
65
72
  - lib/quant/mixins/functions.rb
66
- - lib/quant/mixins/high_pass_filter.rb
73
+ - lib/quant/mixins/high_pass_filters.rb
67
74
  - lib/quant/mixins/hilbert_transform.rb
68
75
  - lib/quant/mixins/moving_averages.rb
69
76
  - lib/quant/mixins/simple_moving_average.rb
70
77
  - lib/quant/mixins/stochastic.rb
71
78
  - lib/quant/mixins/super_smoother.rb
79
+ - lib/quant/mixins/universal_filters.rb
72
80
  - lib/quant/mixins/weighted_moving_average.rb
73
81
  - lib/quant/refinements/array.rb
74
82
  - lib/quant/series.rb
75
83
  - lib/quant/settings.rb
76
84
  - lib/quant/settings/indicators.rb
85
+ - lib/quant/statistics/correlation.rb
77
86
  - lib/quant/ticks/ohlc.rb
78
87
  - lib/quant/ticks/serializers/ohlc.rb
79
88
  - lib/quant/ticks/serializers/spot.rb
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Quant
4
- class Indicators
5
- class MaPoint < IndicatorPoint
6
- attribute :ss, key: "ss"
7
- attribute :ema, key: "ema"
8
- attr_accessor :ss, :ema, :osc
9
-
10
- def initialize_data_points
11
- @ss = input
12
- @ema = input
13
- @osc = nil
14
- end
15
- end
16
-
17
- # Moving Averages
18
- class Ma < Indicator
19
- include Quant::Mixins::Filters
20
-
21
- def alpha(period)
22
- bars_to_alpha(period)
23
- end
24
-
25
- def min_period
26
- 8 # Quant.config.indicators.min_period
27
- end
28
-
29
- def max_period
30
- 48 # Quant.config.indicators.max_period
31
- end
32
-
33
- def compute
34
- # p0.ss = super_smoother input, :ss, min_period
35
- p0.ema = alpha(max_period) * input + (1 - alpha(max_period)) * p1.ema
36
- p0.osc = p0.ss - p0.ema
37
- end
38
- end
39
- end
40
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Quant
4
- module Mixins
5
- module HighPassFilter
6
- # HighPass Filters are “detrenders” because they attenuate low frequency components
7
- # One pole HighPass and SuperSmoother does not produce a zero mean because low
8
- # frequency spectral dilation components are “leaking” through The one pole
9
- # HighPass Filter response
10
- def two_pole_high_pass_filter(source, prev_source, min_period, max_period = nil)
11
- raise "source must be a symbol" unless source.is_a?(Symbol)
12
- return p0.send(source) if p0 == p2
13
-
14
- max_period ||= min_period * 2
15
- (min_period * Math.sqrt(2))
16
- max_radians = 2.0 * Math::PI / (max_period * Math.sqrt(2))
17
-
18
- v1 = p0.send(source) - (2.0 * p1.send(source)) + p2.send(source)
19
- v2 = p1.send(prev_source)
20
- v3 = p2.send(prev_source)
21
-
22
- alpha = period_to_alpha(max_radians)
23
-
24
- a = (1 - (alpha * 0.5))**2 * v1
25
- b = 2 * (1 - alpha) * v2
26
- c = (1 - alpha)**2 * v3
27
-
28
- a + b - c
29
- end
30
-
31
- # alpha = (Cosine(.707* 2 * PI / 48) + Sine (.707*360 / 48) - 1) / Cosine(.707*360 / 48);
32
- # is the same as the following:
33
- # radians = Math.sqrt(2) * Math::PI / period
34
- # alpha = (Math.cos(radians) + Math.sin(radians) - 1) / Math.cos(radians)
35
- def high_pass_filter(source, period)
36
- v0 = source.is_a?(Symbol) ? p0.send(source) : source
37
- return v0 if p3 == p0
38
-
39
- v1 = p1.send(source)
40
- v2 = p2.send(source)
41
-
42
- radians = Math.sqrt(2) * Math::PI / period
43
- a = Math.exp(-radians)
44
- b = 2 * a * Math.cos(radians)
45
-
46
- c2 = b
47
- c3 = -a**2
48
- c1 = (1 + c2 - c3) / 4
49
-
50
- (c1 * (v0 - (2 * v1) + v2)) + (c2 * p1.hp) + (c3 * p2.hp)
51
- end
52
- end
53
- end
54
- end