quantitative 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/quant/dominant_cycles_source.rb +1 -32
- data/lib/quant/indicators/adx.rb +2 -5
- data/lib/quant/indicators/atr.rb +2 -0
- data/lib/quant/indicators/cci.rb +2 -0
- data/lib/quant/indicators/decycler.rb +2 -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 +2 -0
- data/lib/quant/indicators/dominant_cycles/half_period.rb +2 -0
- data/lib/quant/indicators/dominant_cycles/homodyne.rb +2 -0
- data/lib/quant/indicators/dominant_cycles/phase_accumulator.rb +2 -0
- data/lib/quant/indicators/ema.rb +67 -0
- data/lib/quant/indicators/frama.rb +1 -0
- data/lib/quant/indicators/indicator.rb +4 -0
- data/lib/quant/indicators/mama.rb +2 -0
- data/lib/quant/indicators/mesa.rb +3 -4
- data/lib/quant/indicators/ping.rb +2 -0
- data/lib/quant/indicators/pivots/atr.rb +1 -0
- data/lib/quant/indicators/pivots/bollinger.rb +2 -0
- data/lib/quant/indicators/pivots/camarilla.rb +1 -3
- data/lib/quant/indicators/pivots/classic.rb +2 -0
- data/lib/quant/indicators/pivots/demark.rb +2 -0
- data/lib/quant/indicators/pivots/donchian.rb +1 -0
- data/lib/quant/indicators/pivots/fibbonacci.rb +2 -0
- data/lib/quant/indicators/pivots/guppy.rb +3 -1
- data/lib/quant/indicators/pivots/keltner.rb +1 -0
- data/lib/quant/indicators/pivots/murrey.rb +2 -0
- data/lib/quant/indicators/pivots/pivot.rb +109 -0
- data/lib/quant/indicators/pivots/traditional.rb +2 -0
- data/lib/quant/indicators/pivots/woodie.rb +2 -0
- data/lib/quant/indicators/rocket_rsi.rb +57 -0
- data/lib/quant/indicators/roofing.rb +59 -0
- data/lib/quant/indicators/rsi.rb +67 -0
- data/lib/quant/indicators/snr.rb +64 -0
- data/lib/quant/indicators_registry.rb +63 -0
- data/lib/quant/indicators_source.rb +14 -9
- data/lib/quant/pivots_source.rb +1 -13
- data/lib/quant/version.rb +1 -1
- data/lib/quantitative.rb +9 -3
- metadata +9 -3
- data/lib/quant/indicators/pivot.rb +0 -107
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8908c3b863d42be9e0c27fcce1dc57a7b0f0b2f4699cb559140eca1caa717223
|
4
|
+
data.tar.gz: 2fda3094ef0da147ed1aef5c5aa6b3020f3de426e885562dab714ed00d3bac43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a35fa3205f447fa5b43a52c7725b3ee3fe3c576c5663e2a5cb29fb67325ca11ee4bea24e80bc93510f7d0c22c5bce706fb809a0e8b9a73bfe00a831f28e875ca
|
7
|
+
data.tar.gz: 1f31136c06beacee9276208a2e9a6bdaebd4c60930ece409b552581dea19b27e38231680ab7699e985cf1040b12d4ac0819510bc748c364051033b3bcd0e4017
|
data/Gemfile.lock
CHANGED
@@ -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)
|
data/lib/quant/indicators/adx.rb
CHANGED
@@ -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
|
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
|
data/lib/quant/indicators/atr.rb
CHANGED
data/lib/quant/indicators/cci.rb
CHANGED
@@ -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
|
|
@@ -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
|
@@ -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,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
|
-
|
37
|
-
|
38
|
-
end
|
36
|
+
register name: :mesa
|
37
|
+
depends_on DominantCycles::Homodyne
|
39
38
|
|
40
39
|
def fast_limit
|
41
|
-
@fast_limit ||= bars_to_alpha(
|
40
|
+
@fast_limit ||= bars_to_alpha(micro_period)
|
42
41
|
end
|
43
42
|
|
44
43
|
def slow_limit
|
@@ -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
|
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)
|
@@ -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
|
@@ -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
|
|
data/lib/quant/pivots_source.rb
CHANGED
@@ -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
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
|
-
|
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.
|
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-
|
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
|