quantitative 0.3.1 → 0.3.2

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/lib/quant/dominant_cycles_source.rb +1 -32
  4. data/lib/quant/indicators/adx.rb +2 -5
  5. data/lib/quant/indicators/atr.rb +2 -0
  6. data/lib/quant/indicators/cci.rb +2 -0
  7. data/lib/quant/indicators/decycler.rb +2 -0
  8. data/lib/quant/indicators/dominant_cycles/acr.rb +2 -0
  9. data/lib/quant/indicators/dominant_cycles/band_pass.rb +2 -0
  10. data/lib/quant/indicators/dominant_cycles/differential.rb +2 -0
  11. data/lib/quant/indicators/dominant_cycles/half_period.rb +2 -0
  12. data/lib/quant/indicators/dominant_cycles/homodyne.rb +2 -0
  13. data/lib/quant/indicators/dominant_cycles/phase_accumulator.rb +2 -0
  14. data/lib/quant/indicators/ema.rb +67 -0
  15. data/lib/quant/indicators/frama.rb +1 -0
  16. data/lib/quant/indicators/indicator.rb +4 -0
  17. data/lib/quant/indicators/mama.rb +2 -0
  18. data/lib/quant/indicators/mesa.rb +3 -4
  19. data/lib/quant/indicators/ping.rb +2 -0
  20. data/lib/quant/indicators/pivots/atr.rb +1 -0
  21. data/lib/quant/indicators/pivots/bollinger.rb +2 -0
  22. data/lib/quant/indicators/pivots/camarilla.rb +1 -3
  23. data/lib/quant/indicators/pivots/classic.rb +2 -0
  24. data/lib/quant/indicators/pivots/demark.rb +2 -0
  25. data/lib/quant/indicators/pivots/donchian.rb +1 -0
  26. data/lib/quant/indicators/pivots/fibbonacci.rb +2 -0
  27. data/lib/quant/indicators/pivots/guppy.rb +3 -1
  28. data/lib/quant/indicators/pivots/keltner.rb +1 -0
  29. data/lib/quant/indicators/pivots/murrey.rb +2 -0
  30. data/lib/quant/indicators/pivots/pivot.rb +109 -0
  31. data/lib/quant/indicators/pivots/traditional.rb +2 -0
  32. data/lib/quant/indicators/pivots/woodie.rb +2 -0
  33. data/lib/quant/indicators/rocket_rsi.rb +57 -0
  34. data/lib/quant/indicators/roofing.rb +59 -0
  35. data/lib/quant/indicators/rsi.rb +67 -0
  36. data/lib/quant/indicators/snr.rb +64 -0
  37. data/lib/quant/indicators_registry.rb +63 -0
  38. data/lib/quant/indicators_source.rb +14 -9
  39. data/lib/quant/pivots_source.rb +1 -13
  40. data/lib/quant/version.rb +1 -1
  41. data/lib/quantitative.rb +9 -3
  42. metadata +9 -3
  43. data/lib/quant/indicators/pivot.rb +0 -107
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cfda18120bf7e3432b359a53d19d633f70f66e3ea4468098673cfc89ac2f77e
4
- data.tar.gz: 598dc565f6da96ce9565afee249f38bc909234177d7b484ce82cba226e2e8d60
3
+ metadata.gz: 8908c3b863d42be9e0c27fcce1dc57a7b0f0b2f4699cb559140eca1caa717223
4
+ data.tar.gz: 2fda3094ef0da147ed1aef5c5aa6b3020f3de426e885562dab714ed00d3bac43
5
5
  SHA512:
6
- metadata.gz: 5b2625954abe0e72a776de95da8d26af213440cb7e2d7beddd48529dfdb70d7247562066b09a3edd1dd3f171ad3ff8a32390ffe337729ca9712f65bdb147084e
7
- data.tar.gz: 780f81a808f14d2d63b53aa81f8f30920b252979f17409d1ba48105ed5eadc91dadc253ada0cd75c6c21b8e6a8450cf39c75ce542adb450759fb097679899cce
6
+ metadata.gz: a35fa3205f447fa5b43a52c7725b3ee3fe3c576c5663e2a5cb29fb67325ca11ee4bea24e80bc93510f7d0c22c5bce706fb809a0e8b9a73bfe00a831f28e875ca
7
+ data.tar.gz: 1f31136c06beacee9276208a2e9a6bdaebd4c60930ece409b552581dea19b27e38231680ab7699e985cf1040b12d4ac0819510bc748c364051033b3bcd0e4017
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- quantitative (0.3.1)
4
+ quantitative (0.3.2)
5
5
  oj (~> 3.10)
6
6
  zeitwerk (~> 2.6)
7
7
 
@@ -17,40 +17,9 @@ module Quant
17
17
  class DominantCyclesSource
18
18
  def initialize(indicator_source:)
19
19
  @indicator_source = indicator_source
20
+ indicator_source.define_indicator_accessors(indicator_source: self)
20
21
  end
21
22
 
22
- # Auto-Correlation Reversals is a method of computing the dominant cycle
23
- # by correlating the data stream with itself delayed by a lag.
24
- def acr; indicator(Indicators::DominantCycles::Acr) end
25
-
26
- # The band-pass dominant cycle passes signals within a certain frequency
27
- # range, and attenuates signals outside that range.
28
- # The trend component of the signal is removed, leaving only the cyclical
29
- # component. Then we count number of iterations between zero crossings
30
- # and this is the `period` of the dominant cycle.
31
- def band_pass; indicator(Indicators::DominantCycles::BandPass) end
32
-
33
- # The Dual Differentiator algorithm computes the phase angle from the
34
- # analytic signal as the arctangent of the ratio of the imaginary
35
- # component to the real component. Further, the angular frequency
36
- # is defined as the rate change of phase. We can use these facts to
37
- # derive the cycle period.
38
- def differential; indicator(Indicators::DominantCycles::Differential) end
39
-
40
- # Static, arbitrarily set period.
41
- def half_period; indicator(Indicators::DominantCycles::HalfPeriod) end
42
-
43
- # Homodyne means the signal is multiplied by itself. More precisely,
44
- # we want to multiply the signal of the current bar with the complex
45
- # value of the signal one bar ago
46
- def homodyne; indicator(Indicators::DominantCycles::Homodyne) end
47
-
48
- # The phase accumulation method of computing the dominant cycle measures
49
- # the phase at each sample by taking the arctangent of the ratio of the
50
- # quadrature component to the in-phase component. The phase is then
51
- # accumulated and the period is derived from the phase.
52
- def phase_accumulator; indicator(Indicators::DominantCycles::PhaseAccumulator) end
53
-
54
23
  private
55
24
 
56
25
  def indicator(indicator_class)
@@ -21,12 +21,9 @@ module Quant
21
21
  end
22
22
 
23
23
  class Adx < Indicator
24
+ register name: :adx
24
25
  depends_on Indicators::Atr
25
26
 
26
- def alpha
27
- bars_to_alpha(dc_period)
28
- end
29
-
30
27
  def scale
31
28
  1.0
32
29
  end
@@ -71,7 +68,7 @@ module Quant
71
68
  p0.di_ema = three_pole_super_smooth(:di, period:, previous: :di_ema).clamp(-10.0, 10.0)
72
69
 
73
70
  p0.value = p0.di_ema
74
- p0.inst_stoch = stochastic :di, period: dc_period
71
+ p0.inst_stoch = stochastic(:di, period:)
75
72
  p0.stoch = three_pole_super_smooth :inst_stoch, period:, previous: :stoch
76
73
  end
77
74
  end
@@ -25,6 +25,8 @@ module Quant
25
25
  end
26
26
 
27
27
  class Atr < Indicator
28
+ register name: :atr
29
+
28
30
  attr_reader :points
29
31
 
30
32
  def period
@@ -18,6 +18,8 @@ module Quant
18
18
  #
19
19
  # SOURCE: https://www.mesasoftware.com/papers/CORRELATION%20AS%20A%20CYCLE%20INDICATOR.pdf
20
20
  class Cci < Indicator
21
+ register name: :cci
22
+
21
23
  def max_period
22
24
  [min_period, dc_period].max
23
25
  end
@@ -24,6 +24,8 @@ module Quant
24
24
  end
25
25
 
26
26
  class Decycler < Indicator
27
+ register name: :decycler
28
+
27
29
  def max_period
28
30
  dc_period
29
31
  end
@@ -28,6 +28,8 @@ module Quant
28
28
  # The cyclic information is extracted using a discrete Fourier transform
29
29
  # (DFT) of the autocorrelation results.
30
30
  class Acr < DominantCycle
31
+ register name: :acr
32
+
31
33
  BANDWIDTH_DEGREES = 370
32
34
  BANDWIDTH_RADIANS = BANDWIDTH_DEGREES * Math::PI / 180.0
33
35
 
@@ -20,6 +20,8 @@ module Quant
20
20
  # component. Then we count number of iterations between zero crossings
21
21
  # and this is the `period` of the dominant cycle.
22
22
  class BandPass < DominantCycle
23
+ register name: :band_pass
24
+
23
25
  def bandwidth
24
26
  0.75
25
27
  end
@@ -9,6 +9,8 @@ module Quant
9
9
  # is defined as the rate change of phase. We can use these facts to
10
10
  # derive the cycle period.
11
11
  class Differential < DominantCycle
12
+ register name: :differential
13
+
12
14
  def compute_period
13
15
  p0.ddd = (p0.q2 * (p0.i2 - p1.i2)) - (p0.i2 * (p0.q2 - p1.q2))
14
16
  p0.inst_period = p0.ddd > 0.01 ? 6.2832 * (p0.i2**2 + p0.q2**2) / p0.ddd : 0.0
@@ -12,6 +12,8 @@ module Quant
12
12
  end
13
13
 
14
14
  class HalfPeriod < DominantCycle
15
+ register name: :half_period
16
+
15
17
  def compute
16
18
  # No-Op
17
19
  end
@@ -7,6 +7,8 @@ module Quant
7
7
  # we want to multiply the signal of the current bar with the complex
8
8
  # value of the signal one bar ago
9
9
  class Homodyne < DominantCycle
10
+ register name: :homodyne
11
+
10
12
  def compute_period
11
13
  p0.re = (p0.i2 * p1.i2) + (p0.q2 * p1.q2)
12
14
  p0.im = (p0.i2 * p1.q2) - (p0.q2 * p1.i2)
@@ -24,6 +24,8 @@ module Quant
24
24
  # Therefore, shorter cycle periods necessarily have a higher output
25
25
  # signal-to-noise ratio.
26
26
  class PhaseAccumulator < DominantCycle
27
+ register name: :phase_accumulator
28
+
27
29
  def compute_period
28
30
  p0.i1 = 0.15 * p0.i1 + 0.85 * p1.i1
29
31
  p0.q1 = 0.15 * p0.q1 + 0.85 * p1.q1
@@ -0,0 +1,67 @@
1
+ module Quant
2
+ module Indicators
3
+ class EmaPoint < IndicatorPoint
4
+ attribute :ss_dc_period, default: :input
5
+ attribute :ss_half_dc_period, default: :input
6
+ attribute :ss_micro_period, default: :input
7
+ attribute :ss_min_period, default: :input
8
+ attribute :ss_half_period, default: :input
9
+ attribute :ss_max_period, default: :input
10
+
11
+ attribute :ema_dc_period, default: :input
12
+ attribute :ema_half_dc_period, default: :input
13
+ attribute :ema_micro_period, default: :input
14
+ attribute :ema_min_period, default: :input
15
+ attribute :ema_half_period, default: :input
16
+ attribute :ema_max_period, default: :input
17
+
18
+ attribute :osc_dc_period, default: 0.0
19
+ attribute :osc_half_dc_period, default: 0.0
20
+ attribute :osc_micro_period, default: 0.0
21
+ attribute :osc_min_period, default: 0.0
22
+ attribute :osc_half_period, default: 0.0
23
+ attribute :osc_max_period, default: 0.0
24
+ end
25
+
26
+ class Ema < Indicator
27
+ register name: :ema
28
+
29
+ def half_dc_period
30
+ dc_period / 2
31
+ end
32
+
33
+ def compute_super_smoothers
34
+ p0.ss_dc_period = super_smoother :input, previous: :ss_dc_period, period: dc_period
35
+ p0.ss_half_dc_period = super_smoother :input, previous: :ss_half_dc_period, period: half_dc_period
36
+ p0.ss_micro_period = super_smoother :input, previous: :ss_micro_period, period: micro_period
37
+ p0.ss_min_period = super_smoother :input, previous: :ss_min_period, period: min_period
38
+ p0.ss_half_period = super_smoother :input, previous: :ss_half_period, period: half_period
39
+ p0.ss_max_period = super_smoother :input, previous: :ss_max_period, period: max_period
40
+ end
41
+
42
+ def compute_emas
43
+ p0.ema_dc_period = ema :input, previous: :ema_dc_period, period: dc_period
44
+ p0.ema_half_dc_period = ema :input, previous: :ema_half_dc_period, period: half_dc_period
45
+ p0.ema_micro_period = ema :input, previous: :ema_micro_period, period: micro_period
46
+ p0.ema_min_period = ema :input, previous: :ema_min_period, period: min_period
47
+ p0.ema_half_period = ema :input, previous: :ema_half_period, period: half_period
48
+ p0.ema_max_period = ema :input, previous: :ema_max_period, period: max_period
49
+ end
50
+
51
+ def compute_oscillators
52
+ p0.osc_dc_period = p0.ss_dc_period - p0.ema_dc_period
53
+ p0.osc_half_dc_period = p0.ss_half_dc_period - p0.ema_half_dc_period
54
+ p0.osc_micro_period = p0.ss_micro_period - p0.ema_micro_period
55
+ p0.osc_min_period = p0.ss_min_period - p0.ema_min_period
56
+ p0.osc_half_period = p0.ss_half_period - p0.ema_half_period
57
+ p0.osc_max_period = p0.ss_max_period - p0.ema_max_period
58
+ end
59
+
60
+ def compute
61
+ compute_super_smoothers
62
+ compute_emas
63
+ compute_oscillators
64
+ end
65
+ end
66
+ end
67
+ end
@@ -15,6 +15,7 @@ module Quant
15
15
  #
16
16
  # SOURCE: http://www.mesasoftware.com/papers/FRAMA.pdf
17
17
  class Frama < Indicator
18
+ register name: :frama
18
19
  using Quant
19
20
 
20
21
  # The max_period is divided into two smaller, equal periods, so must be even
@@ -15,6 +15,10 @@ module Quant
15
15
  include Mixins::FisherTransform
16
16
  # include Mixins::Direction
17
17
 
18
+ def self.register(name:)
19
+ Quant::IndicatorsSource.register(name:, indicator_class: self)
20
+ end
21
+
18
22
  # Provides a registry of dependent indicators for each indicator class.
19
23
  # NOTE: Internal use only.
20
24
  def self.dependent_indicator_classes
@@ -46,6 +46,8 @@ module Quant
46
46
  # dominant cycle indicator other than the homodyne for the rest
47
47
  # of your indicators.
48
48
  class Mama < Indicator
49
+ register name: :mama
50
+
49
51
  # constrain between 6 and 50 bars
50
52
  def constrain_period_bars
51
53
  p0.period = p0.period.clamp(min_period, max_period)
@@ -33,12 +33,11 @@ module Quant
33
33
  # indicators for the dominant cycle, then this version is useful
34
34
  # as it avoids extra computational steps.
35
35
  class Mesa < Indicator
36
- def period
37
- dc_period
38
- end
36
+ register name: :mesa
37
+ depends_on DominantCycles::Homodyne
39
38
 
40
39
  def fast_limit
41
- @fast_limit ||= bars_to_alpha(min_period / 2)
40
+ @fast_limit ||= bars_to_alpha(micro_period)
42
41
  end
43
42
 
44
43
  def slow_limit
@@ -13,6 +13,8 @@ module Quant
13
13
 
14
14
  # A simple idicator used primarily to test the indicator system
15
15
  class Ping < Indicator
16
+ register name: :ping
17
+
16
18
  def compute
17
19
  p0.pong = input
18
20
  p0.compute_count += 1
@@ -2,6 +2,7 @@ module Quant
2
2
  module Indicators
3
3
  module Pivots
4
4
  class Atr < Pivot
5
+ register name: :atr
5
6
  depends_on Indicators::Atr
6
7
 
7
8
  def atr_point
@@ -4,6 +4,8 @@ module Quant
4
4
  module Indicators
5
5
  module Pivots
6
6
  class Bollinger < Pivot
7
+ register name: :bollinger
8
+
7
9
  using Quant
8
10
 
9
11
  def compute_midpoint
@@ -27,9 +27,7 @@ module Quant
27
27
  # S5 = S4 – 1.168 * (S3 – S4)
28
28
  # S6 = Close – (R6 – Close)
29
29
  class Camarilla < Pivot
30
- def multiplier
31
- 1.1
32
- end
30
+ register name: :camarilla
33
31
 
34
32
  def compute_midpoint
35
33
  p0.midpoint = t0.close_price
@@ -4,6 +4,8 @@ module Quant
4
4
  module Indicators
5
5
  module Pivots
6
6
  class Classic < Pivot
7
+ register name: :classic
8
+
7
9
  def compute_midpoint
8
10
  p0.midpoint = super_smoother :input, previous: :midpoint, period: averaging_period
9
11
  end
@@ -14,6 +14,8 @@ module Quant
14
14
  # PP = X / 4 (this is not an official DeMark number but merely a reference point based on the calculation of X)
15
15
  # S1 = X / 2 - H
16
16
  class Demark < Pivot
17
+ register name: :demark
18
+
17
19
  def averaging_period
18
20
  min_period / 2
19
21
  end
@@ -4,6 +4,7 @@ module Quant
4
4
  module Indicators
5
5
  module Pivots
6
6
  class Donchian < Pivot
7
+ register name: :donchian
7
8
  using Quant
8
9
 
9
10
  def st_period; min_period end
@@ -2,6 +2,8 @@ module Quant
2
2
  module Indicators
3
3
  module Pivots
4
4
  class Fibbonacci < Pivot
5
+ register name: :fibbonacci
6
+
5
7
  def averaging_period
6
8
  half_period
7
9
  end
@@ -2,6 +2,8 @@ module Quant
2
2
  module Indicators
3
3
  module Pivots
4
4
  class Guppy < Pivot
5
+ register name: :guppy
6
+
5
7
  def guppy_ema(period, band)
6
8
  return p0.input unless p1[band]
7
9
 
@@ -15,7 +17,7 @@ module Quant
15
17
 
16
18
  # The short-term MAs are typically set at 3, 5, 8, 10, 12, and 15 periods. The
17
19
  # longer-term MAs are typically set at 30, 35, 40, 45, 50, and 60.
18
- def compute
20
+ def compute_bands
19
21
  p0[1] = guppy_ema(5, 1)
20
22
  p0[2] = guppy_ema(8, 2)
21
23
  p0[3] = guppy_ema(10, 3)
@@ -2,6 +2,7 @@ module Quant
2
2
  module Indicators
3
3
  module Pivots
4
4
  class Keltner < Pivot
5
+ register name: :keltner
5
6
  depends_on Indicators::Atr
6
7
 
7
8
  def atr_point
@@ -3,6 +3,8 @@ module Quant
3
3
  module Indicators
4
4
  module Pivots
5
5
  class Murrey < Pivot
6
+ register name: :murrey
7
+
6
8
  def multiplier
7
9
  0.125
8
10
  end
@@ -0,0 +1,109 @@
1
+ module Quant
2
+ module Indicators
3
+ module Pivots
4
+ class PivotPoint < IndicatorPoint
5
+ attribute :high_price
6
+ attribute :avg_high, default: :high_price
7
+ attribute :highest, default: :input
8
+
9
+ attribute :low_price
10
+ attribute :avg_low, default: :low_price
11
+ attribute :lowest, default: :input
12
+
13
+ attribute :range, default: 0.0
14
+ attribute :avg_range, default: 0.0
15
+ attribute :std_dev, default: 0.0
16
+
17
+ def bands
18
+ @bands ||= { 0 => input }
19
+ end
20
+
21
+ def [](band)
22
+ bands[band]
23
+ end
24
+
25
+ def []=(band, value)
26
+ bands[band] = value
27
+ end
28
+
29
+ def key?(band)
30
+ bands.key?(band)
31
+ end
32
+
33
+ def midpoint
34
+ bands[0]
35
+ end
36
+ alias :h0 :midpoint
37
+ alias :l0 :midpoint
38
+
39
+ def midpoint=(value)
40
+ bands[0] = value
41
+ end
42
+ alias :h0= :midpoint=
43
+ alias :l0= :midpoint=
44
+
45
+ (1..8).each do |band|
46
+ define_method("h#{band}") { bands[band] }
47
+ define_method("h#{band}=") { |value| bands[band] = value }
48
+
49
+ define_method("l#{band}") { bands[-band] }
50
+ define_method("l#{band}=") { |value| bands[-band] = value }
51
+ end
52
+ end
53
+
54
+ class Pivot < Indicator
55
+ def points_class
56
+ Quant::Indicators::Pivots::PivotPoint
57
+ end
58
+
59
+ def band?(band)
60
+ p0.key?(band)
61
+ end
62
+
63
+ def period
64
+ dc_period
65
+ end
66
+
67
+ def averaging_period
68
+ min_period
69
+ end
70
+
71
+ def period_midpoints
72
+ period_points(period).map(&:midpoint)
73
+ end
74
+
75
+ def compute
76
+ compute_extents
77
+ compute_value
78
+ compute_midpoint
79
+ compute_bands
80
+ end
81
+
82
+ def compute_midpoint
83
+ p0.midpoint = p0.input
84
+ end
85
+
86
+ def compute_value
87
+ # No-op -- override in subclasses
88
+ end
89
+
90
+ def compute_bands
91
+ # No-op -- override in subclasses
92
+ end
93
+
94
+ def compute_extents
95
+ period_midpoints.tap do |midpoints|
96
+ p0.high_price = t0.high_price
97
+ p0.low_price = t0.low_price
98
+ p0.highest = midpoints.max
99
+ p0.lowest = midpoints.min
100
+ p0.range = p0.high_price - p0.low_price
101
+ p0.avg_low = super_smoother(:low_price, previous: :avg_low, period: averaging_period)
102
+ p0.avg_high = super_smoother(:high_price, previous: :avg_high, period: averaging_period)
103
+ p0.avg_range = super_smoother(:range, previous: :avg_range, period: averaging_period)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -2,6 +2,8 @@ module Quant
2
2
  module Indicators
3
3
  module Pivots
4
4
  class Traditional < Pivot
5
+ register name: :traditional
6
+
5
7
  def multiplier
6
8
  2.0
7
9
  end
@@ -22,6 +22,8 @@ module Quant
22
22
  # S3 = L - 2 * (H - PP) (same as: S1 - RANGE)
23
23
  # S4 = S3 - RANGE
24
24
  class Woodie < Pivot
25
+ register name: :woodie
26
+
25
27
  def compute_value
26
28
  p0.input = (t1.high_price + t1.low_price + 2.0 * t0.open_price) / 4.0
27
29
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ module Indicators
5
+ class RocketRsiPoint < IndicatorPoint
6
+ attribute :hp, default: 0.0
7
+
8
+ attribute :delta, default: 0.0
9
+ attribute :gain, default: 0.0
10
+ attribute :loss, default: 0.0
11
+
12
+ attribute :gains, default: 0.0
13
+ attribute :losses, default: 0.0
14
+ attribute :denom, default: 0.0
15
+
16
+ attribute :inst_rsi, default: 0.5
17
+ attribute :rsi, default: 0.0
18
+ attribute :crosses, default: false
19
+ end
20
+
21
+ class RocketRsi < Indicator
22
+ register name: :rocket_rsi
23
+
24
+ def quarter_period
25
+ half_period / 2
26
+ end
27
+
28
+ def half_period
29
+ (dc_period / 2) - 1
30
+ end
31
+
32
+ def compute
33
+ p0.hp = two_pole_butterworth :input, previous: :hp, period: quarter_period
34
+
35
+ lp = p(half_period)
36
+ p0.delta = p0.hp - lp.hp
37
+ p0.delta > 0.0 ? p0.gain = p0.delta : p0.loss = p0.delta.abs
38
+
39
+ period_points(half_period).tap do |period_points|
40
+ p0.gains = period_points.map(&:gain).sum
41
+ p0.losses = period_points.map(&:loss).sum
42
+ end
43
+
44
+ p0.denom = p0.gains + p0.losses
45
+
46
+ if p0.denom.zero?
47
+ p0.inst_rsi = p1.inst_rsi
48
+ p0.rsi = p1.rsi
49
+ else
50
+ p0.inst_rsi = ((p0.gains - p0.losses) / p0.denom)
51
+ p0.rsi = fisher_transform(p0.inst_rsi).clamp(-1.0, 1.0)
52
+ end
53
+ p0.crosses = (p0.rsi >= 0.0 && p1.rsi < 0.0) || (p0.rsi <= 0.0 && p1.rsi > 0.0)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ module Indicators
5
+ # The ideal time to buy is when the cycle is at a trough, and the ideal time to exit a long position or to
6
+ # sell short is when the cycle is at a peak.These conditions are flagged by the filter crossing itself
7
+ # delayed by two bars, and are included as part of the indicator.
8
+ class RoofingPoint < IndicatorPoint
9
+ attribute :hp, default: 0.0
10
+ attribute :value, default: 0.0
11
+ attribute :peak, default: 0.0
12
+ attribute :agc, default: 0.0
13
+ attribute :direction, default: 0
14
+ attribute :turned, default: false
15
+ end
16
+
17
+ class Roofing < Indicator
18
+ register name: :roofing
19
+
20
+ def low_pass_period
21
+ dc_period
22
+ end
23
+
24
+ def high_pass_period
25
+ low_pass_period * 2
26
+ end
27
+
28
+ # //Highpass filter cyclic components whose periods are shorter than 48 bars
29
+ # alpha1 = (Cosine(.707*360 / HPPeriod) + Sine (.707*360 / HPPeriod) - 1) / Cosine(.707*360 / HPPeriod);
30
+ # HP = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(Close - 2*Close[1] + Close[2]) + 2*(1 - alpha1)*HP[1] - (1 - alpha1)*
31
+ # (1 - alpha1)*HP[2];
32
+ # //Smooth with a Super Smoother Filter from equation 3-3
33
+ # a1 = expvalue(-1.414*3.14159 / LPPeriod);
34
+ # b1 = 2*a1*Cosine(1.414*180 / LPPeriod);
35
+ # c2 = b1;
36
+ # c3 = -a1*a1;
37
+ # c1 = 1 - c2 - c3;
38
+ # Filt = c1*(HP + HP[1]) / 2 + c2*Filt[1] + c3*Filt[2
39
+ def compute
40
+ a = Math.cos(0.707 * deg2rad(360) / high_pass_period)
41
+ b = Math.sin(0.707 * deg2rad(360) / high_pass_period)
42
+ alpha1 = (a + b - 1) / a
43
+
44
+ p0.hp = (1 - alpha1 / 2)**2 * (p0.input - 2 * p1.input + p2.input) + 2 * (1 - alpha1) * p1.hp - (1 - alpha1)**2 * p2.hp
45
+ a1 = Math.exp(-1.414 * Math::PI / low_pass_period)
46
+ c2 = 2 * a1 * Math.cos(1.414 * deg2rad(180) / low_pass_period)
47
+ c3 = -a1**2
48
+ c1 = 1 - c2 - c3
49
+ p0.value = c1 * (p0.hp + p1.hp) / 2 + c2 * p1.value + c3 * p2.value
50
+ p0.direction = p0.value > p2.value ? 1 : -1
51
+ p0.turned = p0.direction != p2.direction
52
+ # Peak = .991 * Peak[1];
53
+ # If AbsValue(BP) > Peak Then Peak = AbsValue(BP); If Peak <> 0 Then Signal = BP / Peak;
54
+ p0.peak = [p0.value.abs, 0.991 * p1.peak].max
55
+ p0.agc = p0.peak == 0 ? 0 : p0.value / p0.peak
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ module Indicators
5
+ class RsiPoint < IndicatorPoint
6
+ attribute :hp, default: 0.0
7
+ attribute :filter, default: 0.0
8
+
9
+ attribute :delta, default: 0.0
10
+ attribute :gain, default: 0.0
11
+ attribute :loss, default: 0.0
12
+
13
+ attribute :gains, default: 0.0
14
+ attribute :losses, default: 0.0
15
+ attribute :denom, default: 0.0
16
+
17
+ attribute :inst_rsi, default: 0.0
18
+ attribute :rsi, default: 0.0
19
+ end
20
+
21
+ # The Relative Strength Index (RSI) is a momentum oscillator that measures the
22
+ # speed and change of price movements. This RSI indicator is adaptive and
23
+ # uses the half-period of the dominant cycle to calculate the RSI.
24
+ # It is further smoothed by an exponential moving average of the last three bars
25
+ # (or whatever the micro_period is set to).
26
+ #
27
+ # The RSI oscillates between 0 and 1. Traditionally, and in this implementation,
28
+ # the RSI is considered overbought when above 0.7 and oversold when below 0.3.
29
+ class Rsi < Indicator
30
+ register name: :rsi
31
+
32
+ def quarter_period
33
+ half_period / 2
34
+ end
35
+
36
+ def half_period
37
+ (dc_period / 2) - 1
38
+ end
39
+
40
+ def compute
41
+ # The High Pass filter is half the dominant cycle period while the
42
+ # Low Pass Filter (super smoother) is the quarter dominant cycle period.
43
+ p0.hp = high_pass_filter :input, period: half_period
44
+ p0.filter = ema :hp, previous: :filter, period: quarter_period
45
+
46
+ lp = p(half_period)
47
+ p0.delta = p0.filter - lp.filter
48
+ p0.delta > 0.0 ? p0.gain = p0.delta : p0.loss = p0.delta.abs
49
+
50
+ period_points(half_period).tap do |period_points|
51
+ p0.gains = period_points.map(&:gain).sum
52
+ p0.losses = period_points.map(&:loss).sum
53
+ end
54
+
55
+ p0.denom = p0.gains + p0.losses
56
+
57
+ if p0.denom > 0.0
58
+ p0.inst_rsi = (p0.gains / p0.denom)
59
+ p0.rsi = ema :inst_rsi, previous: :rsi, period: micro_period
60
+ else
61
+ p0.inst_rsi = 0.5
62
+ p0.rsi = 0.5
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ module Indicators
5
+ class SnrPoint < IndicatorPoint
6
+ attribute :smooth, default: 0.0
7
+ attribute :detrend, default: 0.0
8
+ attribute :i1, default: 0.0
9
+ attribute :q1, default: 0.0
10
+ attribute :noise, default: 0.0
11
+ attribute :signal, default: 0.0
12
+ attribute :ratio, default: 0.0
13
+ attribute :state, default: 0
14
+ end
15
+
16
+ class Snr < Indicator
17
+ register name: :snr
18
+ depends_on DominantCycles::Homodyne
19
+
20
+ def homodyne_dominant_cycle
21
+ series.indicators[source].dominant_cycles.homodyne
22
+ end
23
+
24
+ def current_dominant_cycle
25
+ homodyne_dominant_cycle.points[t0]
26
+ end
27
+
28
+ def threshold
29
+ @threshold ||= 10 * Math.log(0.5)**2
30
+ end
31
+
32
+ def compute_values
33
+ current_dominant_cycle.tap do |dc|
34
+ p0.i1 = dc.i1
35
+ p0.q1 = dc.q1
36
+ end
37
+ end
38
+
39
+ def compute_noise
40
+ noise = (p0.input - p2.input).abs
41
+ p0.noise = p1.noise.zero? ? noise : (0.1 * noise) + (0.9 * p1.noise)
42
+ end
43
+
44
+ def compute_ratio
45
+ # p0.ratio = 0.25 * (10 * Math.log(p0.i1**2 + p0.q1**2) / Math.log(10)) + 0.75 * p1.ratio
46
+ # ratio = .25*(10 * Log(I1*I1 + Q1*Q1)/(Range*Range))/Log(10) + 6) + .75*ratio[1]
47
+ if p0 == p1
48
+ p0.signal = 0.0
49
+ p0.ratio = 1.0
50
+ else
51
+ p0.signal = threshold + 10.0 * (Math.log((p0.i1**2 + p0.q1**2)/(p0.noise**2)) / Math.log(10))
52
+ p0.ratio = (0.25 * p0.signal) + (0.75 * p1.ratio)
53
+ end
54
+ p0.state = p0.ratio >= threshold ? 1 : 0
55
+ end
56
+
57
+ def compute
58
+ compute_values
59
+ compute_noise
60
+ compute_ratio
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ module IndicatorsRegistry
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ def define_indicator_accessors(indicator_source:)
10
+ self.class.define_indicator_accessors(indicator_source:)
11
+ end
12
+
13
+ module ClassMethods
14
+ def registry
15
+ @registry ||= {}
16
+ end
17
+
18
+ class RegistryEntry
19
+ attr_reader :name, :indicator_class
20
+
21
+ def initialize(name:, indicator_class:)
22
+ @name = name
23
+ @indicator_class = indicator_class
24
+ end
25
+
26
+ def key
27
+ "#{indicator_class.name}::#{name}"
28
+ end
29
+
30
+ def standard?
31
+ !pivot? && !dominant_cycle?
32
+ end
33
+
34
+ def pivot?
35
+ indicator_class < Indicators::Pivots::Pivot
36
+ end
37
+
38
+ def dominant_cycle?
39
+ indicator_class < Indicators::DominantCycles::DominantCycle
40
+ end
41
+ end
42
+
43
+ def register(name:, indicator_class:)
44
+ entry = RegistryEntry.new(name:, indicator_class:)
45
+ registry[entry.key] = entry
46
+ # registry[name] = indicator_class
47
+ end
48
+
49
+ def registry_entries_for(indicator_source:)
50
+ return registry.values.select(&:pivot?) if indicator_source.is_a?(PivotsSource)
51
+ return registry.values.select(&:dominant_cycle?) if indicator_source.is_a?(DominantCyclesSource)
52
+
53
+ registry.values.select(&:standard?)
54
+ end
55
+
56
+ def define_indicator_accessors(indicator_source:)
57
+ registry_entries_for(indicator_source:).each do |entry|
58
+ indicator_source.define_singleton_method(entry.name) { indicator(entry.indicator_class) }
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -16,13 +16,18 @@ module Quant
16
16
  # By design, the {Quant::Indicators::Indicator} class holds the {Quant::Ticks::Tick} instance
17
17
  # alongside the indicator's computed values for that tick.
18
18
  class IndicatorsSource
19
+ include IndicatorsRegistry
20
+
19
21
  attr_reader :series, :source, :dominant_cycles, :pivots
20
22
 
21
23
  def initialize(series:, source:)
22
24
  @series = series
23
25
  @source = source
26
+
24
27
  @indicators = {}
25
28
  @ordered_indicators = []
29
+ define_indicator_accessors(indicator_source: self)
30
+
26
31
  @dominant_cycles = DominantCyclesSource.new(indicator_source: self)
27
32
  @pivots = PivotsSource.new(indicator_source: self)
28
33
  end
@@ -35,20 +40,19 @@ module Quant
35
40
  @ordered_indicators.each { |indicator| indicator << tick }
36
41
  end
37
42
 
38
- def adx; indicator(Indicators::Adx) end
39
- def atr; indicator(Indicators::Atr) end
40
- def cci; indicator(Indicators::Cci) end
41
- def decycler; indicator(Indicators::Decycler) end
42
- def frama; indicator(Indicators::Frama) end
43
- def mama; indicator(Indicators::Mama) end
44
- def mesa; indicator(Indicators::Mesa) end
45
- def ping; indicator(Indicators::Ping) end
46
-
47
43
  # Attaches a given Indicator class and defines the method for
48
44
  # accessing it using the given name. Indicators take care of
49
45
  # computing their values when first attached to a populated
50
46
  # series.
51
47
  #
48
+ # NOTE: You can also use the `register` method on the indicator class to
49
+ # accomplish the same thing. `attach` lets you inject a custom indicator
50
+ # at run-time when you have an instance of {Quant::IndicatorsSource} while
51
+ # the `register` method is used to define the indicator at load-time.
52
+ #
53
+ # NOTE Calling `attach` also registers the indicator with the framework, so
54
+ # you only have to `attach` once.
55
+ #
52
56
  # The indicators shipped with the library are all wired into the framework, thus
53
57
  # this method should be used for custom indicators not shipped with the library.
54
58
  #
@@ -57,6 +61,7 @@ module Quant
57
61
  # @example
58
62
  # series.indicators.oc2.attach(name: :foo, indicator_class: Indicators::Foo)
59
63
  def attach(name:, indicator_class:)
64
+ self.class.register(name:, indicator_class:)
60
65
  define_singleton_method(name) { indicator(indicator_class) }
61
66
  end
62
67
 
@@ -4,21 +4,9 @@ module Quant
4
4
  class PivotsSource
5
5
  def initialize(indicator_source:)
6
6
  @indicator_source = indicator_source
7
+ indicator_source.define_indicator_accessors(indicator_source: self)
7
8
  end
8
9
 
9
- def atr; indicator(Indicators::Pivots::Atr) end
10
- def bollinger; indicator(Indicators::Pivots::Bollinger) end
11
- def camarilla; indicator(Indicators::Pivots::Camarilla) end
12
- def classic; indicator(Indicators::Pivots::Classic) end
13
- def demark; indicator(Indicators::Pivots::Demark) end
14
- def donchian; indicator(Indicators::Pivots::Donchian) end
15
- def fibbonacci; indicator(Indicators::Pivots::Fibbonacci) end
16
- def guppy; indicator(Indicators::Pivots::Guppy) end
17
- def keltner; indicator(Indicators::Pivots::Keltner) end
18
- def murrey; indicator(Indicators::Pivots::Murrey) end
19
- def traditional; indicator(Indicators::Pivots::Traditional) end
20
- def woodie; indicator(Indicators::Pivots::Woodie) end
21
-
22
10
  private
23
11
 
24
12
  def indicator(indicator_class)
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.3.1"
4
+ VERSION = "0.3.2"
5
5
  end
data/lib/quantitative.rb CHANGED
@@ -29,6 +29,12 @@ loader.inflector.inflect "version" => "VERSION"
29
29
  loader.setup
30
30
 
31
31
  # Refinements aren't autoloaded by Zeitwerk, so we need to require them manually.
32
- %w(refinements).each do |sub_folder|
33
- Dir.glob(File.join(quant_folder, sub_folder, "**/*.rb")).each { |fn| require fn }
34
- end
32
+ # %w(refinements).each do |sub_folder|
33
+ # Dir.glob(File.join(quant_folder, sub_folder, "**/*.rb")).each { |fn| require fn }
34
+ # end
35
+
36
+ refinements_folder = File.join(quant_folder, "refinements")
37
+ indicators_folder = File.join(quant_folder, "indicators")
38
+
39
+ loader.eager_load_dir(refinements_folder)
40
+ loader.eager_load_dir(indicators_folder)
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.3.1
4
+ version: 0.3.2
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-06-01 00:00:00.000000000 Z
11
+ date: 2024-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -77,13 +77,13 @@ files:
77
77
  - lib/quant/indicators/dominant_cycles/half_period.rb
78
78
  - lib/quant/indicators/dominant_cycles/homodyne.rb
79
79
  - lib/quant/indicators/dominant_cycles/phase_accumulator.rb
80
+ - lib/quant/indicators/ema.rb
80
81
  - lib/quant/indicators/frama.rb
81
82
  - lib/quant/indicators/indicator.rb
82
83
  - lib/quant/indicators/indicator_point.rb
83
84
  - lib/quant/indicators/mama.rb
84
85
  - lib/quant/indicators/mesa.rb
85
86
  - lib/quant/indicators/ping.rb
86
- - lib/quant/indicators/pivot.rb
87
87
  - lib/quant/indicators/pivots/atr.rb
88
88
  - lib/quant/indicators/pivots/bollinger.rb
89
89
  - lib/quant/indicators/pivots/camarilla.rb
@@ -94,8 +94,14 @@ files:
94
94
  - lib/quant/indicators/pivots/guppy.rb
95
95
  - lib/quant/indicators/pivots/keltner.rb
96
96
  - lib/quant/indicators/pivots/murrey.rb
97
+ - lib/quant/indicators/pivots/pivot.rb
97
98
  - lib/quant/indicators/pivots/traditional.rb
98
99
  - lib/quant/indicators/pivots/woodie.rb
100
+ - lib/quant/indicators/rocket_rsi.rb
101
+ - lib/quant/indicators/roofing.rb
102
+ - lib/quant/indicators/rsi.rb
103
+ - lib/quant/indicators/snr.rb
104
+ - lib/quant/indicators_registry.rb
99
105
  - lib/quant/indicators_source.rb
100
106
  - lib/quant/indicators_sources.rb
101
107
  - lib/quant/interval.rb
@@ -1,107 +0,0 @@
1
- module Quant
2
- module Indicators
3
- class PivotPoint < IndicatorPoint
4
- attribute :high_price
5
- attribute :avg_high, default: :high_price
6
- attribute :highest, default: :input
7
-
8
- attribute :low_price
9
- attribute :avg_low, default: :low_price
10
- attribute :lowest, default: :input
11
-
12
- attribute :range, default: 0.0
13
- attribute :avg_range, default: 0.0
14
- attribute :std_dev, default: 0.0
15
-
16
- def bands
17
- @bands ||= { 0 => input }
18
- end
19
-
20
- def [](band)
21
- bands[band]
22
- end
23
-
24
- def []=(band, value)
25
- bands[band] = value
26
- end
27
-
28
- def key?(band)
29
- bands.key?(band)
30
- end
31
-
32
- def midpoint
33
- bands[0]
34
- end
35
- alias :h0 :midpoint
36
- alias :l0 :midpoint
37
-
38
- def midpoint=(value)
39
- bands[0] = value
40
- end
41
- alias :h0= :midpoint=
42
- alias :l0= :midpoint=
43
-
44
- (1..8).each do |band|
45
- define_method("h#{band}") { bands[band] }
46
- define_method("h#{band}=") { |value| bands[band] = value }
47
-
48
- define_method("l#{band}") { bands[-band] }
49
- define_method("l#{band}=") { |value| bands[-band] = value }
50
- end
51
- end
52
-
53
- class Pivot < Indicator
54
- def points_class
55
- Quant::Indicators::PivotPoint
56
- end
57
-
58
- def band?(band)
59
- p0.key?(band)
60
- end
61
-
62
- def period
63
- dc_period
64
- end
65
-
66
- def averaging_period
67
- min_period
68
- end
69
-
70
- def period_midpoints
71
- period_points(period).map(&:midpoint)
72
- end
73
-
74
- def compute
75
- compute_extents
76
- compute_value
77
- compute_midpoint
78
- compute_bands
79
- end
80
-
81
- def compute_midpoint
82
- p0.midpoint = p0.input
83
- end
84
-
85
- def compute_value
86
- # No-op -- override in subclasses
87
- end
88
-
89
- def compute_bands
90
- # No-op -- override in subclasses
91
- end
92
-
93
- def compute_extents
94
- period_midpoints.tap do |midpoints|
95
- p0.high_price = t0.high_price
96
- p0.low_price = t0.low_price
97
- p0.highest = midpoints.max
98
- p0.lowest = midpoints.min
99
- p0.range = p0.high_price - p0.low_price
100
- p0.avg_low = super_smoother(:low_price, previous: :avg_low, period: averaging_period)
101
- p0.avg_high = super_smoother(:high_price, previous: :avg_high, period: averaging_period)
102
- p0.avg_range = super_smoother(:range, previous: :avg_range, period: averaging_period)
103
- end
104
- end
105
- end
106
- end
107
- end