quantitative 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf0aa73684247efc7bc9750c27c856a094fa362d99c1eba73bd70457da8f3a97
4
- data.tar.gz: 56dcaa82230328cb44149ceb957420484217e33c6f601ea135997391bfb95440
3
+ metadata.gz: 6b5786b75635cde82e7919fce6b8fcd568724b27fc2810efe6db36a7e6dd09cc
4
+ data.tar.gz: 3a6fb86c25bb4f2b1e4994710539bfb528377ef57531e6845a4417c87fc86e29
5
5
  SHA512:
6
- metadata.gz: b674a5e4408a69039cf2c7ad2378d065e3f88b409a7530108af1bfc5a186a02690cba8ffb1c8d40a87ad69368f302d9eec0211135beec7db6a9c51c4e1be46d3
7
- data.tar.gz: e7d7693cd878ebe777f81ca0d90b4f305e645c8547aa9f420cccb4a4f9b53920211a2b7f49af146508756e6e38f5cc7e0c4b57f1c8abdd722f65bd3409abd02f
6
+ metadata.gz: 5ae30c5b971d6c7bd41e3c2b764e7d95438931790271133dc0e3a476a371affddfdcf06355ffa933240c0c9952fd8fa5e4212c496c95c80f028de7399d3b129f
7
+ data.tar.gz: 9ff8aeaf24800a7208e51f836dd7c7c3621b2e2e3a7f1e8820d89f6fc5cd56dd3c4b762c478d221c6ef7279f62c0df75ae1a19f6a4d34e7ba82e116e6a10a790
data/Gemfile CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- # Specify your gem's dependencies in quantitative.gemspec
5
+ # Specify your gem"s dependencies in quantitative.gemspec
6
6
  gemspec
7
7
 
8
8
  gem "rake", "~> 13.0"
@@ -18,7 +18,12 @@ gem "guard-rspec", "~> 4.7"
18
18
  gem "yard", "~> 0.9"
19
19
  gem "benchmark-ips", "~> 2.9"
20
20
 
21
- gem 'rspec-github'
22
- gem 'simplecov'
23
- gem 'simplecov-cobertura'
21
+ gem "rspec-github"
24
22
 
23
+ # Test coverage and profiling
24
+ gem "simplecov"
25
+ gem "simplecov-cobertura"
26
+ gem "stackprof", require: false
27
+ gem "test-prof", require: false
28
+ gem "vernier", require: false
29
+ gem "ruby-prof", require: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- quantitative (0.2.1)
4
+ quantitative (0.2.2)
5
5
  oj (~> 3.10)
6
6
 
7
7
  GEM
@@ -107,6 +107,7 @@ GEM
107
107
  rubocop (~> 1.40)
108
108
  rubocop-capybara (~> 2.17)
109
109
  rubocop-factory_bot (~> 2.22)
110
+ ruby-prof (1.7.0)
110
111
  ruby-progressbar (1.13.0)
111
112
  shellany (0.0.1)
112
113
  simplecov (0.22.0)
@@ -118,9 +119,12 @@ GEM
118
119
  simplecov (~> 0.19)
119
120
  simplecov-html (0.12.3)
120
121
  simplecov_json_formatter (0.1.4)
122
+ stackprof (0.2.26)
121
123
  stringio (3.1.0)
124
+ test-prof (1.3.2)
122
125
  thor (1.3.0)
123
126
  unicode-display_width (2.5.0)
127
+ vernier (0.5.1)
124
128
  yard (0.9.34)
125
129
 
126
130
  PLATFORMS
@@ -138,8 +142,12 @@ DEPENDENCIES
138
142
  rspec-github
139
143
  rubocop (~> 1.21)
140
144
  rubocop-rspec
145
+ ruby-prof
141
146
  simplecov
142
147
  simplecov-cobertura
148
+ stackprof
149
+ test-prof
150
+ vernier
143
151
  yard (~> 0.9)
144
152
 
145
153
  BUNDLED WITH
@@ -1,3 +1,6 @@
1
+
2
+ require_relative 'indicators_proxy'
3
+
1
4
  module Quant
2
5
  # Dominant Cycles measure the primary cycle within a given range. By default, the library
3
6
  # is wired to look for cycles between 10 and 48 bars. These values can be adjusted by setting
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "indicator_point"
4
+ require_relative "indicator"
5
+
6
+ module Quant
7
+ class Indicators
8
+ class AdxPoint < IndicatorPoint
9
+ attribute :dmu, default: 0.0
10
+ attribute :dmd, default: 0.0
11
+ attribute :dmu_ema, default: 0.0
12
+ attribute :dmd_ema, default: 0.0
13
+ attribute :diu, default: 0.0
14
+ attribute :did, default: 0.0
15
+ attribute :di, default: 0.0
16
+ attribute :di_ema, default: 0.0
17
+ attribute :value, default: 0.0
18
+ attribute :inst_stoch, default: 0.0
19
+ attribute :stoch, default: 0.0
20
+ attribute :stoch_up, default: false
21
+ attribute :stoch_turned, default: false
22
+ attribute :ssf, default: 0.0
23
+ attribute :hp, default: 0.0
24
+ end
25
+
26
+ class Adx < Indicator
27
+ def alpha
28
+ bars_to_alpha(dc_period)
29
+ end
30
+
31
+ def scale
32
+ 1.0
33
+ end
34
+
35
+ def period
36
+ dc_period
37
+ end
38
+
39
+ def atr_point
40
+ series.indicators[source].atr.points[t0]
41
+ end
42
+
43
+ def compute
44
+ # To calculate the ADX, first determine the + and - directional movement, or DM.
45
+ # The +DM and -DM are found by calculating the "up-move," or current high minus
46
+ # the previous high, and "down-move," or current low minus the previous low.
47
+ # If the up-move is greater than the down-move and greater than zero, the +DM equals the up-move;
48
+ # otherwise, it equals zero. If the down-move is greater than the up-move and greater than zero,
49
+ # the -DM equals the down-move; otherwise, it equals zero.
50
+ dm_highs = [t0.high_price - t1.high_price, 0.0].max
51
+ dm_lows = [t0.low_price - t1.low_price, 0.0].max
52
+
53
+ p0.dmu = dm_highs > dm_lows ? 0.0 : dm_highs
54
+ p0.dmd = dm_lows > dm_highs ? 0.0 : dm_lows
55
+
56
+ p0.dmu_ema = three_pole_super_smooth :dmu, period:, previous: :dmu_ema
57
+ p0.dmd_ema = three_pole_super_smooth :dmd, period:, previous: :dmd_ema
58
+
59
+ atr_value = atr_point.fast * scale
60
+ return if atr_value == 0.0 || @points.size < period
61
+
62
+ # The positive directional indicator, or +DI, equals 100 times the EMA of +DM divided by the ATR
63
+ # over a given number of time periods. Welles usually used 14 periods.
64
+ # The negative directional indicator, or -DI, equals 100 times the EMA of -DM divided by the ATR.
65
+ p0.diu = (100.0 * p0.dmu_ema) / atr_value
66
+ p0.did = (100.0 * p0.dmd_ema) / atr_value
67
+
68
+ # The ADX indicator itself equals 100 times the EMA of the absolute value of (+DI minus -DI)
69
+ # divided by (+DI plus -DI).
70
+ delta = p0.diu + p0.did
71
+ p0.di = (p0.diu - p1.did).abs / delta
72
+ p0.di_ema = three_pole_super_smooth(:di, period:, previous: :di_ema).clamp(-10.0, 10.0)
73
+
74
+ p0.value = p0.di_ema
75
+ p0.inst_stoch = stochastic :di, period: dc_period
76
+ p0.stoch = three_pole_super_smooth :inst_stoch, period:, previous: :stoch
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "indicator_point"
4
+ require_relative "indicator"
5
+
6
+ module Quant
7
+ class Indicators
8
+ class AtrPoint < IndicatorPoint
9
+ attribute :tr, default: 0.0
10
+ attribute :period, default: :min_period
11
+ attribute :value, default: 0.0
12
+ attribute :slow, default: 0.0
13
+ attribute :fast, default: 0.0
14
+ attribute :inst_stoch, default: 0.0
15
+ attribute :stoch, default: 0.0
16
+ attribute :stoch_up, default: false
17
+ attribute :stoch_turned, default: false
18
+ attribute :osc, default: 0.0
19
+ attribute :crossed, default: :unchanged
20
+
21
+ def crossed_up?
22
+ @crossed == :up
23
+ end
24
+
25
+ def crossed_down?
26
+ @crossed == :down
27
+ end
28
+ end
29
+
30
+ class Atr < Indicator
31
+ attr_reader :points
32
+
33
+ def period
34
+ dc_period / 2
35
+ end
36
+
37
+ def fast_alpha
38
+ period_to_alpha(period)
39
+ end
40
+
41
+ def slow_alpha
42
+ period_to_alpha(2 * period)
43
+ end
44
+
45
+ # Typically, the Average True Range (ATR) is based on 14 periods and can be calculated on an intraday, daily, weekly
46
+ # or monthly basis. For this example, the ATR will be based on daily data. Because there must be a beginning, the first
47
+ # TR value is simply the High minus the Low, and the first 14-day ATR is the average of the daily TR values for the
48
+ # last 14 days. After that, Wilder sought to smooth the data by incorporating the previous period's ATR value.
49
+
50
+ # Current ATR = [(Prior ATR x 13) + Current TR] / 14
51
+
52
+ # - Multiply the previous 14-day ATR by 13.
53
+ # - Add the most recent day's TR value.
54
+ # - Divide the total by 14
55
+
56
+ def compute
57
+ p0.period = period
58
+ p0.tr = (t1.high_price - t0.close_price).abs
59
+
60
+ p0.value = three_pole_super_smooth :tr, period:, previous: :value
61
+
62
+ p0.slow = (slow_alpha * p0.value) + ((1.0 - slow_alpha) * p1.slow)
63
+ p0.fast = (fast_alpha * p0.value) + ((1.0 - fast_alpha) * p1.fast)
64
+
65
+ p0.inst_stoch = stochastic :value, period:
66
+ p0.stoch = three_pole_super_smooth(:inst_stoch, previous: :stoch, period:).clamp(0, 100)
67
+ p0.stoch_up = p0.stoch >= 70
68
+ p0.stoch_turned = p0.stoch_up && !p1.stoch_up
69
+ compute_oscillator
70
+ end
71
+
72
+ def compute_oscillator
73
+ p0.osc = p0.value - wma(:value)
74
+ p0.crossed = :up if p0.osc >= 0 && p1.osc < 0
75
+ p0.crossed = :down if p0.osc <= 0 && p1.osc > 0
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ class Indicators
5
+ class CciPoint < IndicatorPoint
6
+ attribute :hp, default: 0.0
7
+ attribute :real, default: 0.0
8
+ attribute :imag, default: 0.0
9
+ attribute :angle, default: 0.0
10
+ attribute :state, default: 0
11
+ end
12
+
13
+ # Correlation Cycle Index
14
+ # The very definition of a trend mode and a cycle mode makes it simple
15
+ # to create a state variable that identifies the market state. If the
16
+ # state is zero, the market is in a cycle mode. If the state is +1 the
17
+ # market is in a trend up. If the state is -1 the market is in a trend down.
18
+ #
19
+ # SOURCE: https://www.mesasoftware.com/papers/CORRELATION%20AS%20A%20CYCLE%20INDICATOR.pdf
20
+ class Cci < Indicator
21
+ def max_period
22
+ [min_period, dc_period].max
23
+ end
24
+
25
+ def compute_correlations
26
+ corr_real = Statistics::Correlation.new
27
+ corr_imag = Statistics::Correlation.new
28
+ arc = 2.0 * Math::PI / max_period.to_f
29
+ (0...max_period).each do |period|
30
+ radians = arc * period
31
+ prev_hp = p(period).hp
32
+ corr_real.add(prev_hp, Math.cos(radians))
33
+ corr_imag.add(prev_hp, -Math.sin(radians))
34
+ end
35
+ p0.real = corr_real.coefficient
36
+ p0.imag = corr_imag.coefficient
37
+ end
38
+
39
+ def compute_angle
40
+ # Compute the angle as an arctangent and resolve the quadrant
41
+ p0.angle = 90 + rad2deg(Math.atan(p0.real / p0.imag))
42
+ p0.angle -= 180 if p0.imag > 0
43
+
44
+ # Do not allow the rate change of angle to go negative
45
+ p0.angle = p1.angle if (p0.angle < p1.angle) && (p1.angle - p0.angle) < 270
46
+ end
47
+
48
+ def compute_state
49
+ return unless (p0.angle - p1.angle).abs < 9
50
+
51
+ p0.state = p0.angle < 0 ? -1 : 1
52
+ end
53
+
54
+ def compute
55
+ p0.hp = two_pole_butterworth :input, previous: :hp, period: min_period
56
+
57
+ compute_correlations
58
+ compute_angle
59
+ compute_state
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ class Indicators
5
+ # The decycler oscillator can be useful for determining the transition be- tween uptrends and downtrends by the crossing of the zero
6
+ # line. Alternatively, the changes of slope of the decycler oscillator are easier to identify than the changes in slope of the
7
+ # original decycler. Optimum cutoff periods can easily be found by experimentation.
8
+ #
9
+ # 1. A decycler filter functions the same as a low-pass filter.
10
+ # 2. A decycler filter is created by subtracting the output of a high-pass filter from the input, thereby removing the
11
+ # high-frequency components by cancellation.
12
+ # 3. A decycler filter has very low lag.
13
+ # 4. A decycler oscillator is created by subtracting the output of a high-pass filter having a shorter cutoff period from the
14
+ # output of another high-pass filter having a longer cutoff period.
15
+ # 5. A decycler oscillator shows transitions between uptrends and down-trends at the zero crossings.
16
+ class DecyclerPoint < IndicatorPoint
17
+ attr_accessor :decycle, :hp1, :hp2, :osc, :peak, :agc, :ift
18
+
19
+ def to_h
20
+ {
21
+ "dc" => decycle,
22
+ "hp1" => hp1,
23
+ "hp2" => hp2,
24
+ "osc" => osc,
25
+ }
26
+ end
27
+
28
+ def initialize_data_points(indicator:)
29
+ @decycle = oc2
30
+ @hp1 = 0.0
31
+ @hp2 = 0.0
32
+ @osc = 0.0
33
+ @peak = 0.0
34
+ @agc = 0.0
35
+ end
36
+ end
37
+
38
+ class Decycler < Indicator
39
+ def max_period
40
+ dc_period
41
+ end
42
+
43
+ def min_period
44
+ settings.min_period
45
+ end
46
+
47
+ def compute_decycler
48
+ alpha = period_to_alpha(max_period)
49
+ p0.decycle = (alpha / 2) * (p0.oc2 + p1.oc2) + (1.0 - alpha) * p1.decycle
50
+ end
51
+
52
+ # alpha1 = (Cosine(.707*360 / HPPeriod1) + Sine (.707*360 / HPPeriod1) - 1) / Cosine(.707*360 / HPPeriod1);
53
+ # HP1 = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(Close - 2*Close[1] + Close[2]) + 2*(1 - alpha1)*HP1[1] - (1 - alpha1)*(1 - alpha1)*HP1[2];
54
+ def compute_hp(period, hp)
55
+ radians = deg2rad(360)
56
+ c = Math.cos(0.707 * radians / period)
57
+ s = Math.sin(0.707 * radians / period)
58
+ alpha = (c + s - 1) / c
59
+ (1 - alpha / 2)**2 * (p0.oc2 - 2 * p1.oc2 + p2.oc2) + 2 * (1 - alpha) * p1.send(hp) - (1 - alpha) * (1 - alpha) * p2.send(hp)
60
+ end
61
+
62
+ def compute_oscillator
63
+ p0.hp1 = compute_hp(min_period, :hp1)
64
+ p0.hp2 = compute_hp(max_period, :hp2)
65
+ p0.osc = p0.hp2 - p0.hp1
66
+ end
67
+
68
+ def compute_agc
69
+ p0.peak = [p0.osc.abs, 0.991 * p1.peak].max
70
+ p0.agc = p0.peak.zero? ? p0.osc : p0.osc / p0.peak
71
+ end
72
+
73
+ def compute_ift
74
+ p0.ift = ift(p0.agc, 5.0)
75
+ end
76
+
77
+ def compute
78
+ compute_decycler
79
+ compute_oscillator
80
+ compute_agc
81
+ compute_ift
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../indicator_point"
2
4
  require_relative "dominant_cycle"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "dominant_cycle"
2
4
 
3
5
  module Quant
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Quant
2
4
  class Indicators
3
5
  class DominantCycles
@@ -18,4 +20,4 @@ module Quant
18
20
  end
19
21
  end
20
22
  end
21
- end
23
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../indicator"
2
4
 
3
5
  module Quant
@@ -76,11 +78,6 @@ module Quant
76
78
  [p0.period.to_i, min_period].max
77
79
  end
78
80
 
79
- def period_points(max_period)
80
- extent = [values.size, max_period].min
81
- values[-extent, extent]
82
- end
83
-
84
81
  def compute
85
82
  compute_input_data_points
86
83
  compute_quadrature_components
@@ -141,4 +138,4 @@ module Quant
141
138
  end
142
139
  end
143
140
  end
144
- end
141
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "dominant_cycle"
2
4
 
3
5
  module Quant
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../indicator_point"
2
4
  require_relative "dominant_cycle"
3
5
 
@@ -15,7 +17,7 @@ module Quant
15
17
  p0.re = (0.2 * p0.re) + (0.8 * p1.re)
16
18
  p0.im = (0.2 * p0.im) + (0.8 * p1.im)
17
19
 
18
- p0.inst_period = 360.0 / rad2deg(Math.atan(p0.im/p0.re)) if (p0.im != 0) && (p0.re != 0)
20
+ p0.inst_period = 360.0 / rad2deg(Math.atan(p0.im / p0.re)) if (p0.im != 0) && (p0.re != 0)
19
21
 
20
22
  constrain_period_magnitude_change
21
23
  constrain_period_bars
@@ -25,4 +27,4 @@ module Quant
25
27
  end
26
28
  end
27
29
  end
28
- end
30
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "dominant_cycle"
2
4
 
3
5
  module Quant
@@ -30,10 +32,12 @@ module Quant
30
32
 
31
33
  p0.accumulator_phase = Math.atan(p0.q1 / p0.i1) unless p0.i1.zero?
32
34
 
33
- case
34
- when p0.i1 < 0 && p0.q1 > 0 then p0.accumulator_phase = 180.0 - p0.accumulator_phase
35
- when p0.i1 < 0 && p0.q1 < 0 then p0.accumulator_phase = 180.0 + p0.accumulator_phase
36
- when p0.i1 > 0 && p0.q1 < 0 then p0.accumulator_phase = 360.0 - p0.accumulator_phase
35
+ if p0.i1 < 0 && p0.q1 > 0
36
+ p0.accumulator_phase = 180.0 - p0.accumulator_phase
37
+ elsif p0.i1 < 0 && p0.q1 < 0
38
+ p0.accumulator_phase = 180.0 + p0.accumulator_phase
39
+ elsif p0.i1 > 0 && p0.q1 < 0
40
+ p0.accumulator_phase = 360.0 - p0.accumulator_phase
37
41
  end
38
42
 
39
43
  p0.delta_phase = p1.accumulator_phase - p0.accumulator_phase
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ class Indicators
5
+ class FramaPoint < IndicatorPoint
6
+ attribute :frama, default: :input
7
+ end
8
+
9
+ # FRAMA (FRactal Adaptive Moving Average). A nonlinear moving average
10
+ # is derived using the Hurst exponent. It rapidly follows significant
11
+ # changes in price but becomes very flat in congestion zones so that
12
+ # bad whipsaw trades can be eliminated.
13
+ #
14
+ # SOURCE: http://www.mesasoftware.com/papers/FRAMA.pdf
15
+ class Frama < Indicator
16
+ using Quant
17
+
18
+ # The max_period is divided into two smaller, equal periods, so must be even
19
+ def max_period
20
+ @max_period ||= begin
21
+ mp = super
22
+ mp.even? ? mp : mp + 1
23
+ end
24
+ end
25
+
26
+ # def max_period
27
+ # mp = dc_period
28
+ # mp.even? ? mp : mp + 1
29
+ # end
30
+
31
+ def half_period
32
+ max_period / 2
33
+ end
34
+
35
+ def compute
36
+ pp = period_points(max_period).map(&:input)
37
+ return if pp.size < max_period
38
+
39
+ n3 = (pp.maximum - pp.minimum) / max_period
40
+
41
+ ppn2 = pp.first(half_period)
42
+ n2 = (ppn2.maximum - ppn2.minimum) / half_period
43
+
44
+ ppn1 = pp.last(half_period)
45
+ n1 = (ppn1.maximum - ppn1.minimum) / half_period
46
+
47
+ dimension = (Math.log(n1 + n2) - Math.log(n3)) / Math.log(2)
48
+ alpha = Math.exp(-4.6 * (dimension - 1.0)).clamp(0.01, 1.0)
49
+ p0.frama = (alpha * p0.input) + ((1 - alpha) * p1.frama)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -71,6 +71,11 @@ module Quant
71
71
  @points.size
72
72
  end
73
73
 
74
+ def period_points(max_period)
75
+ extent = [values.size, max_period].min
76
+ values[-extent, extent]
77
+ end
78
+
74
79
  attr_reader :p0, :p1, :p2, :p3
75
80
  attr_reader :t0, :t1, :t2, :t3
76
81
 
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ class Indicators
5
+ class MamaPoint < IndicatorPoint
6
+ attribute :smooth, default: 0.0
7
+ attribute :detrend, default: 0.0
8
+ attribute :re, default: 0.0
9
+ attribute :im, default: 0.0
10
+ attribute :i1, default: 0.0
11
+ attribute :q1, default: 0.0
12
+ attribute :ji, default: 0.0
13
+ attribute :jq, default: 0.0
14
+ attribute :i2, default: 0.0
15
+ attribute :q2, default: 0.0
16
+ attribute :period, default: :min_period
17
+ attribute :smooth_period, default: :min_period
18
+ attribute :mama, default: :input
19
+ attribute :fama, default: :input
20
+ attribute :gama, default: :input
21
+ attribute :dama, default: :input
22
+ attribute :lama, default: :input
23
+ attribute :faga, default: :input
24
+ attribute :phase, default: 0.0
25
+ attribute :delta_phase, default: 0.0
26
+ attribute :osc, default: 0.0
27
+ attribute :crossed, default: :unchanged
28
+
29
+ def crossed_up?
30
+ @crossed == :up
31
+ end
32
+
33
+ def crossed_down?
34
+ @crossed == :down
35
+ end
36
+ end
37
+
38
+ # https://www.mesasoftware.com/papers/MAMA.pdf
39
+ # MESA Adaptive Moving Average (MAMA) adapts to price movement in an
40
+ # entirely new and unique way. The adapation is based on the rate change
41
+ # of phase as measured by the Hilbert Transform Discriminator.
42
+ #
43
+ # This version of Ehler's MAMA indicator duplicates the computations
44
+ # present in the homodyne version of the dominant cycle indicator.
45
+ # Use this version of the indicator when you're using a different
46
+ # dominant cycle indicator other than the homodyne for the rest
47
+ # of your indicators.
48
+ class Mama < Indicator
49
+ # constrain between 6 and 50 bars
50
+ def constrain_period_bars
51
+ p0.period = p0.period.clamp(min_period, max_period)
52
+ end
53
+
54
+ # constrain magnitude of change in phase
55
+ def constrain_period_magnitude_change
56
+ p0.period = [1.5 * p1.period, p0.period].min
57
+ p0.period = [0.67 * p1.period, p0.period].max
58
+ end
59
+
60
+ # amplitude correction using previous period value
61
+ def compute_smooth_period
62
+ p0.period = ((0.2 * p0.period) + (0.8 * p1.period)).round
63
+ p0.smooth_period = ((0.33333 * p0.period) + (0.666667 * p1.smooth_period)).round
64
+ end
65
+
66
+ def homodyne_discriminator
67
+ p0.re = (p0.i2 * p1.i2) + (p0.q2 * p1.q2)
68
+ p0.im = (p0.i2 * p1.q2) - (p0.q2 * p1.i2)
69
+
70
+ p0.re = (0.2 * p0.re) + (0.8 * p1.re)
71
+ p0.im = (0.2 * p0.im) + (0.8 * p1.im)
72
+
73
+ p0.period = 360.0 / rad2deg(Math.atan(p0.im / p0.re)) if (p0.im != 0) && (p0.re != 0)
74
+
75
+ constrain_period_magnitude_change
76
+ constrain_period_bars
77
+ compute_smooth_period
78
+ end
79
+
80
+ def compute_dominant_cycle
81
+ p0.smooth = wma :input
82
+ p0.detrend = hilbert_transform :smooth, period: p1.period
83
+
84
+ # { Compute Inphase and Quadrature components }
85
+ p0.q1 = hilbert_transform :detrend, period: p1.period
86
+ p0.i1 = p3.detrend
87
+
88
+ # { Advance the phase of I1 and Q1 by 90 degrees }
89
+ p0.ji = hilbert_transform :i1, period: p1.period
90
+ p0.jq = hilbert_transform :q1, period: p1.period
91
+
92
+ # { Smooth the I and Q components before applying the discriminator }
93
+ p0.i2 = (0.2 * (p0.i1 - p0.jq)) + 0.8 * (p1.i2 || (p0.i1 - p0.jq))
94
+ p0.q2 = (0.2 * (p0.q1 + p0.ji)) + 0.8 * (p1.q2 || (p0.q1 + p0.ji))
95
+
96
+ homodyne_discriminator
97
+ end
98
+
99
+ def fast_limit
100
+ @fast_limit ||= bars_to_alpha(min_period / 2)
101
+ end
102
+
103
+ def slow_limit
104
+ @slow_limit ||= bars_to_alpha(max_period)
105
+ end
106
+
107
+ def compute_dominant_cycle_phase
108
+ p0.delta_phase = p1.phase - p0.phase
109
+ p0.delta_phase = 1.0 if p0.delta_phase < 1.0
110
+ end
111
+
112
+ FAMA = 0.500
113
+ GAMA = 0.950
114
+ DAMA = 0.125
115
+ LAMA = 0.100
116
+ FAGA = 0.050
117
+
118
+ def compute_moving_averages
119
+ alpha = [fast_limit / p0.delta_phase, slow_limit].max
120
+ p0.mama = (alpha * p0.input) + ((1.0 - alpha) * p1.mama)
121
+
122
+ p0.fama = (FAMA * alpha * p0.mama) + ((1.0 - (FAMA * alpha)) * p1.fama)
123
+ p0.gama = (GAMA * alpha * p0.mama) + ((1.0 - (GAMA * alpha)) * p1.gama)
124
+ p0.dama = (DAMA * alpha * p0.mama) + ((1.0 - (DAMA * alpha)) * p1.dama)
125
+ p0.lama = (LAMA * alpha * p0.mama) + ((1.0 - (LAMA * alpha)) * p1.lama)
126
+ p0.faga = (FAGA * alpha * p0.fama) + ((1.0 - (FAGA * alpha)) * p1.faga)
127
+ end
128
+
129
+ def compute_oscillator
130
+ p0.osc = p0.mama - p0.fama
131
+ p0.crossed = :up if p0.osc >= 0 && p1.osc < 0
132
+ p0.crossed = :down if p0.osc <= 0 && p1.osc > 0
133
+ end
134
+
135
+ def compute
136
+ compute_dominant_cycle
137
+ compute_dominant_cycle_phase
138
+ compute_moving_averages
139
+ compute_oscillator
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ class Indicators
5
+ # The MESA inidicator
6
+ class MesaPoint < IndicatorPoint
7
+ attribute :mama, default: :input
8
+ attribute :fama, default: :input
9
+ attribute :dama, default: :input
10
+ attribute :gama, default: :input
11
+ attribute :lama, default: :input
12
+ attribute :faga, default: :input
13
+ attribute :osc, default: 0.0
14
+ attribute :crossed, default: :unchanged
15
+
16
+ def crossed_up?
17
+ @crossed == :up
18
+ end
19
+
20
+ def crossed_down?
21
+ @crossed == :down
22
+ end
23
+ end
24
+
25
+ # https://www.mesasoftware.com/papers/MAMA.pdf
26
+ # MESA Adaptive Moving Average (MAMA) adapts to price movement in an
27
+ # entirely new and unique way. The adapation is based on the rate change
28
+ # of phase as measured by the Hilbert Transform Discriminator.
29
+ #
30
+ # This version of Ehler's MAMA indicator ties into the homodyne
31
+ # dominant cycle indicator to provide a more efficient computation
32
+ # for this indicator. If you're using the homodyne in all your
33
+ # indicators for the dominant cycle, then this version is useful
34
+ # as it avoids extra computational steps.
35
+ class Mesa < Indicator
36
+ def period
37
+ dc_period
38
+ end
39
+
40
+ def fast_limit
41
+ @fast_limit ||= bars_to_alpha(min_period / 2)
42
+ end
43
+
44
+ def slow_limit
45
+ @slow_limit ||= bars_to_alpha(max_period)
46
+ end
47
+
48
+ def homodyne_dominant_cycle
49
+ series.indicators[source].dominant_cycles.homodyne
50
+ end
51
+
52
+ def current_dominant_cycle
53
+ homodyne_dominant_cycle.points[t0]
54
+ end
55
+
56
+ def delta_phase
57
+ current_dominant_cycle.delta_phase
58
+ end
59
+
60
+ FAMA = 0.500
61
+ GAMA = 0.950
62
+ DAMA = 0.125
63
+ LAMA = 0.100
64
+ FAGA = 0.050
65
+
66
+ def compute
67
+ alpha = [fast_limit / delta_phase, slow_limit].max
68
+
69
+ p0.mama = (alpha * p0.input) + ((1.0 - alpha) * p1.mama)
70
+ p0.fama = (FAMA * alpha * p0.mama) + ((1.0 - (FAMA * alpha)) * p1.fama)
71
+ p0.gama = (GAMA * alpha * p0.mama) + ((1.0 - (GAMA * alpha)) * p1.gama)
72
+ p0.dama = (DAMA * alpha * p0.mama) + ((1.0 - (DAMA * alpha)) * p1.dama)
73
+ p0.lama = (LAMA * alpha * p0.mama) + ((1.0 - (LAMA * alpha)) * p1.lama)
74
+ p0.faga = (FAGA * alpha * p0.fama) + ((1.0 - (FAGA * alpha)) * p1.faga)
75
+
76
+ compute_oscillator
77
+ end
78
+
79
+ def compute_oscillator
80
+ p0.osc = p0.mama - p0.fama
81
+ p0.crossed = :up if p0.osc >= 0 && p1.osc < 0
82
+ p0.crossed = :down if p0.osc <= 0 && p1.osc > 0
83
+ end
84
+ end
85
+ end
86
+ end
@@ -6,9 +6,13 @@ module Quant
6
6
  # used outside those shipped with the library.
7
7
  class Indicators < IndicatorsProxy
8
8
  def ping; indicator(Indicators::Ping) end
9
+ def adx; indicator(Indicators::Adx) end
10
+ def atr; indicator(Indicators::Atr) end
11
+ def mesa; indicator(Indicators::Mesa) end
12
+ def mama; indicator(Indicators::MAMA) end
9
13
 
10
14
  def dominant_cycles
11
- @dominant_cycles ||= Indicators::DominantCycleIndicators.new(series:, source:)
15
+ @dominant_cycles ||= Quant::DominantCycleIndicators.new(series:, source:)
12
16
  end
13
17
  end
14
18
  end
@@ -16,7 +16,7 @@ module Quant
16
16
  def stochastic(source, period:)
17
17
  subset = values.last(period).map{ |p| p.send(source) }
18
18
 
19
- lowest, highest = subset.minimum, subset.maximum
19
+ lowest, highest = subset.minimum.to_f, subset.maximum.to_f
20
20
  return 0.0 if (highest - lowest).zero?
21
21
 
22
22
  100.0 * (subset[-1] - lowest) / (highest - lowest)
@@ -2,8 +2,17 @@
2
2
 
3
3
  module Quant
4
4
  module Mixins
5
+ # Super Smoother Filters provide a way to smooth out the noise in a series
6
+ # without out introducing undesirable lag that you would other get with
7
+ # traditional moving averages.
8
+ #
9
+ # The EMA only reduces the amplitude at the Nyquist frequency by 13 dB.
10
+ # On the other hand, the SuperSmoother filter theoretically completely
11
+ # eliminates components at the Nyquist Frequency. The added benefit is
12
+ # that the SuperSmoother filter has significantly less lag than the EMA.
5
13
  module SuperSmoother
6
- def two_pole_super_smooth(source, period:, previous: :ss)
14
+ # https://www.mesasoftware.com/papers/PredictiveIndicators.pdf
15
+ def two_pole_super_smooth(source, period:, previous:)
7
16
  raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
8
17
 
9
18
  radians = Math.sqrt(2) * Math::PI / period
@@ -23,7 +32,7 @@ module Quant
23
32
  alias super_smoother two_pole_super_smooth
24
33
  alias ss2p two_pole_super_smooth
25
34
 
26
- def three_pole_super_smooth(source, period:, previous: :ss)
35
+ def three_pole_super_smooth(source, period:, previous:)
27
36
  raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
28
37
 
29
38
  radians = Math::PI / period
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.2.1"
4
+ VERSION = "0.2.2"
5
5
  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.2.1
4
+ version: 0.2.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-03-16 00:00:00.000000000 Z
11
+ date: 2024-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -48,10 +48,14 @@ files:
48
48
  - lib/quant/asset_class.rb
49
49
  - lib/quant/attributes.rb
50
50
  - lib/quant/config.rb
51
+ - lib/quant/dominant_cycle_indicators.rb
51
52
  - lib/quant/errors.rb
52
53
  - lib/quant/experimental.rb
53
54
  - lib/quant/indicators.rb
54
- - lib/quant/indicators/dominant_cycle_indicators.rb
55
+ - lib/quant/indicators/adx.rb
56
+ - lib/quant/indicators/atr.rb
57
+ - lib/quant/indicators/cci.rb
58
+ - lib/quant/indicators/decycler.rb
55
59
  - lib/quant/indicators/dominant_cycles/acr.rb
56
60
  - lib/quant/indicators/dominant_cycles/band_pass.rb
57
61
  - lib/quant/indicators/dominant_cycles/differential.rb
@@ -59,8 +63,11 @@ files:
59
63
  - lib/quant/indicators/dominant_cycles/half_period.rb
60
64
  - lib/quant/indicators/dominant_cycles/homodyne.rb
61
65
  - lib/quant/indicators/dominant_cycles/phase_accumulator.rb
66
+ - lib/quant/indicators/frama.rb
62
67
  - lib/quant/indicators/indicator.rb
63
68
  - lib/quant/indicators/indicator_point.rb
69
+ - lib/quant/indicators/mama.rb
70
+ - lib/quant/indicators/mesa.rb
64
71
  - lib/quant/indicators/ping.rb
65
72
  - lib/quant/indicators_proxy.rb
66
73
  - lib/quant/indicators_sources.rb