quantitative 0.3.1 → 0.3.2

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