quantitative 0.1.9 → 0.2.0

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