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 +4 -4
- data/Gemfile +9 -4
- data/Gemfile.lock +9 -1
- data/lib/quant/{indicators/dominant_cycle_indicators.rb → dominant_cycle_indicators.rb} +3 -0
- data/lib/quant/indicators/adx.rb +80 -0
- data/lib/quant/indicators/atr.rb +79 -0
- data/lib/quant/indicators/cci.rb +63 -0
- data/lib/quant/indicators/decycler.rb +85 -0
- data/lib/quant/indicators/dominant_cycles/acr.rb +2 -0
- data/lib/quant/indicators/dominant_cycles/band_pass.rb +2 -0
- data/lib/quant/indicators/dominant_cycles/differential.rb +3 -1
- data/lib/quant/indicators/dominant_cycles/dominant_cycle.rb +3 -6
- data/lib/quant/indicators/dominant_cycles/half_period.rb +2 -0
- data/lib/quant/indicators/dominant_cycles/homodyne.rb +4 -2
- data/lib/quant/indicators/dominant_cycles/phase_accumulator.rb +8 -4
- data/lib/quant/indicators/frama.rb +53 -0
- data/lib/quant/indicators/indicator.rb +5 -0
- data/lib/quant/indicators/mama.rb +143 -0
- data/lib/quant/indicators/mesa.rb +86 -0
- data/lib/quant/indicators.rb +5 -1
- data/lib/quant/mixins/stochastic.rb +1 -1
- data/lib/quant/mixins/super_smoother.rb +11 -2
- data/lib/quant/version.rb +1 -1
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b5786b75635cde82e7919fce6b8fcd568724b27fc2810efe6db36a7e6dd09cc
|
4
|
+
data.tar.gz: 3a6fb86c25bb4f2b1e4994710539bfb528377ef57531e6845a4417c87fc86e29
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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.
|
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
|
@@ -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"
|
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 "../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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
@@ -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
|
data/lib/quant/indicators.rb
CHANGED
@@ -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 ||=
|
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
|
-
|
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:
|
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
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.
|
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-
|
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/
|
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
|