quantitative 0.3.0 → 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 +3 -1
- data/lib/quant/asset.rb +0 -2
- data/lib/quant/dominant_cycles_source.rb +1 -32
- data/lib/quant/experimental.rb +3 -1
- data/lib/quant/indicators/adx.rb +3 -10
- data/lib/quant/indicators/atr.rb +3 -4
- data/lib/quant/indicators/cci.rb +3 -1
- data/lib/quant/indicators/decycler.rb +3 -1
- data/lib/quant/indicators/dominant_cycles/acr.rb +5 -6
- data/lib/quant/indicators/dominant_cycles/band_pass.rb +4 -4
- data/lib/quant/indicators/dominant_cycles/differential.rb +4 -2
- data/lib/quant/indicators/dominant_cycles/dominant_cycle.rb +2 -4
- data/lib/quant/indicators/dominant_cycles/half_period.rb +4 -4
- data/lib/quant/indicators/dominant_cycles/homodyne.rb +4 -5
- data/lib/quant/indicators/dominant_cycles/phase_accumulator.rb +4 -4
- data/lib/quant/indicators/ema.rb +67 -0
- data/lib/quant/indicators/frama.rb +2 -1
- data/lib/quant/indicators/indicator.rb +5 -1
- data/lib/quant/indicators/indicator_point.rb +1 -1
- data/lib/quant/indicators/mama.rb +3 -1
- data/lib/quant/indicators/mesa.rb +4 -5
- data/lib/quant/indicators/ping.rb +3 -1
- data/lib/quant/indicators/pivots/atr.rb +3 -2
- data/lib/quant/indicators/pivots/bollinger.rb +4 -2
- data/lib/quant/indicators/pivots/camarilla.rb +3 -5
- data/lib/quant/indicators/pivots/classic.rb +4 -2
- data/lib/quant/indicators/pivots/demark.rb +4 -2
- data/lib/quant/indicators/pivots/donchian.rb +3 -2
- data/lib/quant/indicators/pivots/fibbonacci.rb +4 -2
- data/lib/quant/indicators/pivots/guppy.rb +5 -3
- data/lib/quant/indicators/pivots/keltner.rb +3 -2
- data/lib/quant/indicators/pivots/murrey.rb +4 -3
- data/lib/quant/indicators/pivots/pivot.rb +109 -0
- data/lib/quant/indicators/pivots/traditional.rb +4 -2
- data/lib/quant/indicators/pivots/woodie.rb +5 -3
- 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.rb +6 -0
- data/lib/quant/indicators_registry.rb +63 -0
- data/lib/quant/indicators_source.rb +14 -10
- data/lib/quant/mixins/filters.rb +0 -3
- data/lib/quant/mixins/moving_averages.rb +0 -3
- data/lib/quant/pivots_source.rb +1 -13
- data/lib/quant/ticks/ohlc.rb +0 -2
- data/lib/quant/ticks/spot.rb +0 -2
- data/lib/quant/version.rb +1 -1
- data/lib/quantitative.rb +29 -6
- metadata +25 -5
- data/lib/quant/indicators/pivot.rb +0 -107
- data/quantitative.gemspec +0 -39
@@ -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
|
@@ -1,8 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Quant
|
4
|
-
|
5
|
-
|
4
|
+
module Indicators
|
5
|
+
module Pivots
|
6
6
|
# One of the key differences in calculating Woodie's Pivot Point to other pivot
|
7
7
|
# points is that the current session's open price is used in the PP formula with
|
8
8
|
# the previous session's high and low. At the time-of-day that we calculate the
|
@@ -22,6 +22,8 @@ module Quant
|
|
22
22
|
# S3 = L - 2 * (H - PP) (same as: S1 - RANGE)
|
23
23
|
# S4 = S3 - RANGE
|
24
24
|
class Woodie < Pivot
|
25
|
+
register name: :woodie
|
26
|
+
|
25
27
|
def compute_value
|
26
28
|
p0.input = (t1.high_price + t1.low_price + 2.0 * t0.open_price) / 4.0
|
27
29
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
module Indicators
|
5
|
+
class RocketRsiPoint < IndicatorPoint
|
6
|
+
attribute :hp, default: 0.0
|
7
|
+
|
8
|
+
attribute :delta, default: 0.0
|
9
|
+
attribute :gain, default: 0.0
|
10
|
+
attribute :loss, default: 0.0
|
11
|
+
|
12
|
+
attribute :gains, default: 0.0
|
13
|
+
attribute :losses, default: 0.0
|
14
|
+
attribute :denom, default: 0.0
|
15
|
+
|
16
|
+
attribute :inst_rsi, default: 0.5
|
17
|
+
attribute :rsi, default: 0.0
|
18
|
+
attribute :crosses, default: false
|
19
|
+
end
|
20
|
+
|
21
|
+
class RocketRsi < Indicator
|
22
|
+
register name: :rocket_rsi
|
23
|
+
|
24
|
+
def quarter_period
|
25
|
+
half_period / 2
|
26
|
+
end
|
27
|
+
|
28
|
+
def half_period
|
29
|
+
(dc_period / 2) - 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def compute
|
33
|
+
p0.hp = two_pole_butterworth :input, previous: :hp, period: quarter_period
|
34
|
+
|
35
|
+
lp = p(half_period)
|
36
|
+
p0.delta = p0.hp - lp.hp
|
37
|
+
p0.delta > 0.0 ? p0.gain = p0.delta : p0.loss = p0.delta.abs
|
38
|
+
|
39
|
+
period_points(half_period).tap do |period_points|
|
40
|
+
p0.gains = period_points.map(&:gain).sum
|
41
|
+
p0.losses = period_points.map(&:loss).sum
|
42
|
+
end
|
43
|
+
|
44
|
+
p0.denom = p0.gains + p0.losses
|
45
|
+
|
46
|
+
if p0.denom.zero?
|
47
|
+
p0.inst_rsi = p1.inst_rsi
|
48
|
+
p0.rsi = p1.rsi
|
49
|
+
else
|
50
|
+
p0.inst_rsi = ((p0.gains - p0.losses) / p0.denom)
|
51
|
+
p0.rsi = fisher_transform(p0.inst_rsi).clamp(-1.0, 1.0)
|
52
|
+
end
|
53
|
+
p0.crosses = (p0.rsi >= 0.0 && p1.rsi < 0.0) || (p0.rsi <= 0.0 && p1.rsi > 0.0)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
module Indicators
|
5
|
+
# The ideal time to buy is when the cycle is at a trough, and the ideal time to exit a long position or to
|
6
|
+
# sell short is when the cycle is at a peak.These conditions are flagged by the filter crossing itself
|
7
|
+
# delayed by two bars, and are included as part of the indicator.
|
8
|
+
class RoofingPoint < IndicatorPoint
|
9
|
+
attribute :hp, default: 0.0
|
10
|
+
attribute :value, default: 0.0
|
11
|
+
attribute :peak, default: 0.0
|
12
|
+
attribute :agc, default: 0.0
|
13
|
+
attribute :direction, default: 0
|
14
|
+
attribute :turned, default: false
|
15
|
+
end
|
16
|
+
|
17
|
+
class Roofing < Indicator
|
18
|
+
register name: :roofing
|
19
|
+
|
20
|
+
def low_pass_period
|
21
|
+
dc_period
|
22
|
+
end
|
23
|
+
|
24
|
+
def high_pass_period
|
25
|
+
low_pass_period * 2
|
26
|
+
end
|
27
|
+
|
28
|
+
# //Highpass filter cyclic components whose periods are shorter than 48 bars
|
29
|
+
# alpha1 = (Cosine(.707*360 / HPPeriod) + Sine (.707*360 / HPPeriod) - 1) / Cosine(.707*360 / HPPeriod);
|
30
|
+
# HP = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(Close - 2*Close[1] + Close[2]) + 2*(1 - alpha1)*HP[1] - (1 - alpha1)*
|
31
|
+
# (1 - alpha1)*HP[2];
|
32
|
+
# //Smooth with a Super Smoother Filter from equation 3-3
|
33
|
+
# a1 = expvalue(-1.414*3.14159 / LPPeriod);
|
34
|
+
# b1 = 2*a1*Cosine(1.414*180 / LPPeriod);
|
35
|
+
# c2 = b1;
|
36
|
+
# c3 = -a1*a1;
|
37
|
+
# c1 = 1 - c2 - c3;
|
38
|
+
# Filt = c1*(HP + HP[1]) / 2 + c2*Filt[1] + c3*Filt[2
|
39
|
+
def compute
|
40
|
+
a = Math.cos(0.707 * deg2rad(360) / high_pass_period)
|
41
|
+
b = Math.sin(0.707 * deg2rad(360) / high_pass_period)
|
42
|
+
alpha1 = (a + b - 1) / a
|
43
|
+
|
44
|
+
p0.hp = (1 - alpha1 / 2)**2 * (p0.input - 2 * p1.input + p2.input) + 2 * (1 - alpha1) * p1.hp - (1 - alpha1)**2 * p2.hp
|
45
|
+
a1 = Math.exp(-1.414 * Math::PI / low_pass_period)
|
46
|
+
c2 = 2 * a1 * Math.cos(1.414 * deg2rad(180) / low_pass_period)
|
47
|
+
c3 = -a1**2
|
48
|
+
c1 = 1 - c2 - c3
|
49
|
+
p0.value = c1 * (p0.hp + p1.hp) / 2 + c2 * p1.value + c3 * p2.value
|
50
|
+
p0.direction = p0.value > p2.value ? 1 : -1
|
51
|
+
p0.turned = p0.direction != p2.direction
|
52
|
+
# Peak = .991 * Peak[1];
|
53
|
+
# If AbsValue(BP) > Peak Then Peak = AbsValue(BP); If Peak <> 0 Then Signal = BP / Peak;
|
54
|
+
p0.peak = [p0.value.abs, 0.991 * p1.peak].max
|
55
|
+
p0.agc = p0.peak == 0 ? 0 : p0.value / p0.peak
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
module Indicators
|
5
|
+
class RsiPoint < IndicatorPoint
|
6
|
+
attribute :hp, default: 0.0
|
7
|
+
attribute :filter, default: 0.0
|
8
|
+
|
9
|
+
attribute :delta, default: 0.0
|
10
|
+
attribute :gain, default: 0.0
|
11
|
+
attribute :loss, default: 0.0
|
12
|
+
|
13
|
+
attribute :gains, default: 0.0
|
14
|
+
attribute :losses, default: 0.0
|
15
|
+
attribute :denom, default: 0.0
|
16
|
+
|
17
|
+
attribute :inst_rsi, default: 0.0
|
18
|
+
attribute :rsi, default: 0.0
|
19
|
+
end
|
20
|
+
|
21
|
+
# The Relative Strength Index (RSI) is a momentum oscillator that measures the
|
22
|
+
# speed and change of price movements. This RSI indicator is adaptive and
|
23
|
+
# uses the half-period of the dominant cycle to calculate the RSI.
|
24
|
+
# It is further smoothed by an exponential moving average of the last three bars
|
25
|
+
# (or whatever the micro_period is set to).
|
26
|
+
#
|
27
|
+
# The RSI oscillates between 0 and 1. Traditionally, and in this implementation,
|
28
|
+
# the RSI is considered overbought when above 0.7 and oversold when below 0.3.
|
29
|
+
class Rsi < Indicator
|
30
|
+
register name: :rsi
|
31
|
+
|
32
|
+
def quarter_period
|
33
|
+
half_period / 2
|
34
|
+
end
|
35
|
+
|
36
|
+
def half_period
|
37
|
+
(dc_period / 2) - 1
|
38
|
+
end
|
39
|
+
|
40
|
+
def compute
|
41
|
+
# The High Pass filter is half the dominant cycle period while the
|
42
|
+
# Low Pass Filter (super smoother) is the quarter dominant cycle period.
|
43
|
+
p0.hp = high_pass_filter :input, period: half_period
|
44
|
+
p0.filter = ema :hp, previous: :filter, period: quarter_period
|
45
|
+
|
46
|
+
lp = p(half_period)
|
47
|
+
p0.delta = p0.filter - lp.filter
|
48
|
+
p0.delta > 0.0 ? p0.gain = p0.delta : p0.loss = p0.delta.abs
|
49
|
+
|
50
|
+
period_points(half_period).tap do |period_points|
|
51
|
+
p0.gains = period_points.map(&:gain).sum
|
52
|
+
p0.losses = period_points.map(&:loss).sum
|
53
|
+
end
|
54
|
+
|
55
|
+
p0.denom = p0.gains + p0.losses
|
56
|
+
|
57
|
+
if p0.denom > 0.0
|
58
|
+
p0.inst_rsi = (p0.gains / p0.denom)
|
59
|
+
p0.rsi = ema :inst_rsi, previous: :rsi, period: micro_period
|
60
|
+
else
|
61
|
+
p0.inst_rsi = 0.5
|
62
|
+
p0.rsi = 0.5
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
module Indicators
|
5
|
+
class SnrPoint < IndicatorPoint
|
6
|
+
attribute :smooth, default: 0.0
|
7
|
+
attribute :detrend, default: 0.0
|
8
|
+
attribute :i1, default: 0.0
|
9
|
+
attribute :q1, default: 0.0
|
10
|
+
attribute :noise, default: 0.0
|
11
|
+
attribute :signal, default: 0.0
|
12
|
+
attribute :ratio, default: 0.0
|
13
|
+
attribute :state, default: 0
|
14
|
+
end
|
15
|
+
|
16
|
+
class Snr < Indicator
|
17
|
+
register name: :snr
|
18
|
+
depends_on DominantCycles::Homodyne
|
19
|
+
|
20
|
+
def homodyne_dominant_cycle
|
21
|
+
series.indicators[source].dominant_cycles.homodyne
|
22
|
+
end
|
23
|
+
|
24
|
+
def current_dominant_cycle
|
25
|
+
homodyne_dominant_cycle.points[t0]
|
26
|
+
end
|
27
|
+
|
28
|
+
def threshold
|
29
|
+
@threshold ||= 10 * Math.log(0.5)**2
|
30
|
+
end
|
31
|
+
|
32
|
+
def compute_values
|
33
|
+
current_dominant_cycle.tap do |dc|
|
34
|
+
p0.i1 = dc.i1
|
35
|
+
p0.q1 = dc.q1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def compute_noise
|
40
|
+
noise = (p0.input - p2.input).abs
|
41
|
+
p0.noise = p1.noise.zero? ? noise : (0.1 * noise) + (0.9 * p1.noise)
|
42
|
+
end
|
43
|
+
|
44
|
+
def compute_ratio
|
45
|
+
# p0.ratio = 0.25 * (10 * Math.log(p0.i1**2 + p0.q1**2) / Math.log(10)) + 0.75 * p1.ratio
|
46
|
+
# ratio = .25*(10 * Log(I1*I1 + Q1*Q1)/(Range*Range))/Log(10) + 6) + .75*ratio[1]
|
47
|
+
if p0 == p1
|
48
|
+
p0.signal = 0.0
|
49
|
+
p0.ratio = 1.0
|
50
|
+
else
|
51
|
+
p0.signal = threshold + 10.0 * (Math.log((p0.i1**2 + p0.q1**2)/(p0.noise**2)) / Math.log(10))
|
52
|
+
p0.ratio = (0.25 * p0.signal) + (0.75 * p1.ratio)
|
53
|
+
end
|
54
|
+
p0.state = p0.ratio >= threshold ? 1 : 0
|
55
|
+
end
|
56
|
+
|
57
|
+
def compute
|
58
|
+
compute_values
|
59
|
+
compute_noise
|
60
|
+
compute_ratio
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
module IndicatorsRegistry
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
def define_indicator_accessors(indicator_source:)
|
10
|
+
self.class.define_indicator_accessors(indicator_source:)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def registry
|
15
|
+
@registry ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
class RegistryEntry
|
19
|
+
attr_reader :name, :indicator_class
|
20
|
+
|
21
|
+
def initialize(name:, indicator_class:)
|
22
|
+
@name = name
|
23
|
+
@indicator_class = indicator_class
|
24
|
+
end
|
25
|
+
|
26
|
+
def key
|
27
|
+
"#{indicator_class.name}::#{name}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def standard?
|
31
|
+
!pivot? && !dominant_cycle?
|
32
|
+
end
|
33
|
+
|
34
|
+
def pivot?
|
35
|
+
indicator_class < Indicators::Pivots::Pivot
|
36
|
+
end
|
37
|
+
|
38
|
+
def dominant_cycle?
|
39
|
+
indicator_class < Indicators::DominantCycles::DominantCycle
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def register(name:, indicator_class:)
|
44
|
+
entry = RegistryEntry.new(name:, indicator_class:)
|
45
|
+
registry[entry.key] = entry
|
46
|
+
# registry[name] = indicator_class
|
47
|
+
end
|
48
|
+
|
49
|
+
def registry_entries_for(indicator_source:)
|
50
|
+
return registry.values.select(&:pivot?) if indicator_source.is_a?(PivotsSource)
|
51
|
+
return registry.values.select(&:dominant_cycle?) if indicator_source.is_a?(DominantCyclesSource)
|
52
|
+
|
53
|
+
registry.values.select(&:standard?)
|
54
|
+
end
|
55
|
+
|
56
|
+
def define_indicator_accessors(indicator_source:)
|
57
|
+
registry_entries_for(indicator_source:).each do |entry|
|
58
|
+
indicator_source.define_singleton_method(entry.name) { indicator(entry.indicator_class) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "dominant_cycles_source"
|
4
3
|
module Quant
|
5
4
|
# {Quant::IndicatorSource} holds a collection of {Quant::Indicators::Indicator} for a given input source.
|
6
5
|
# This class ensures dominant cycle computations come before other indicators that depend on them.
|
@@ -17,13 +16,18 @@ module Quant
|
|
17
16
|
# By design, the {Quant::Indicators::Indicator} class holds the {Quant::Ticks::Tick} instance
|
18
17
|
# alongside the indicator's computed values for that tick.
|
19
18
|
class IndicatorsSource
|
19
|
+
include IndicatorsRegistry
|
20
|
+
|
20
21
|
attr_reader :series, :source, :dominant_cycles, :pivots
|
21
22
|
|
22
23
|
def initialize(series:, source:)
|
23
24
|
@series = series
|
24
25
|
@source = source
|
26
|
+
|
25
27
|
@indicators = {}
|
26
28
|
@ordered_indicators = []
|
29
|
+
define_indicator_accessors(indicator_source: self)
|
30
|
+
|
27
31
|
@dominant_cycles = DominantCyclesSource.new(indicator_source: self)
|
28
32
|
@pivots = PivotsSource.new(indicator_source: self)
|
29
33
|
end
|
@@ -36,20 +40,19 @@ module Quant
|
|
36
40
|
@ordered_indicators.each { |indicator| indicator << tick }
|
37
41
|
end
|
38
42
|
|
39
|
-
def adx; indicator(Indicators::Adx) end
|
40
|
-
def atr; indicator(Indicators::Atr) end
|
41
|
-
def cci; indicator(Indicators::Cci) end
|
42
|
-
def decycler; indicator(Indicators::Decycler) end
|
43
|
-
def frama; indicator(Indicators::Frama) end
|
44
|
-
def mama; indicator(Indicators::Mama) end
|
45
|
-
def mesa; indicator(Indicators::Mesa) end
|
46
|
-
def ping; indicator(Indicators::Ping) end
|
47
|
-
|
48
43
|
# Attaches a given Indicator class and defines the method for
|
49
44
|
# accessing it using the given name. Indicators take care of
|
50
45
|
# computing their values when first attached to a populated
|
51
46
|
# series.
|
52
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
|
+
#
|
53
56
|
# The indicators shipped with the library are all wired into the framework, thus
|
54
57
|
# this method should be used for custom indicators not shipped with the library.
|
55
58
|
#
|
@@ -58,6 +61,7 @@ module Quant
|
|
58
61
|
# @example
|
59
62
|
# series.indicators.oc2.attach(name: :foo, indicator_class: Indicators::Foo)
|
60
63
|
def attach(name:, indicator_class:)
|
64
|
+
self.class.register(name:, indicator_class:)
|
61
65
|
define_singleton_method(name) { indicator(indicator_class) }
|
62
66
|
end
|
63
67
|
|
data/lib/quant/mixins/filters.rb
CHANGED
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/ticks/ohlc.rb
CHANGED
data/lib/quant/ticks/spot.rb
CHANGED
data/lib/quant/version.rb
CHANGED
data/lib/quantitative.rb
CHANGED
@@ -4,14 +4,37 @@ require "time"
|
|
4
4
|
require "date"
|
5
5
|
require "oj"
|
6
6
|
require "csv"
|
7
|
+
require "zeitwerk"
|
7
8
|
|
8
9
|
lib_folder = File.expand_path(File.join(File.dirname(__FILE__)))
|
9
10
|
quant_folder = File.join(lib_folder, "quant")
|
10
11
|
|
11
|
-
# require
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
# Explicitly require module functions since Zeitwerk isn't configured, yet.
|
13
|
+
require_relative "quant/time_methods"
|
14
|
+
require_relative "quant/config"
|
15
|
+
require_relative "quant/experimental"
|
16
|
+
module Quant
|
17
|
+
include TimeMethods
|
18
|
+
include Config
|
19
|
+
include Experimental
|
17
20
|
end
|
21
|
+
|
22
|
+
# Configure Zeitwerk to autoload the Quant module.
|
23
|
+
loader = Zeitwerk::Loader.for_gem
|
24
|
+
loader.push_dir(quant_folder, namespace: Quant)
|
25
|
+
|
26
|
+
loader.inflector.inflect "ohlc" => "OHLC"
|
27
|
+
loader.inflector.inflect "version" => "VERSION"
|
28
|
+
|
29
|
+
loader.setup
|
30
|
+
|
31
|
+
# Refinements aren't autoloaded by Zeitwerk, so we need to require them manually.
|
32
|
+
# %w(refinements).each do |sub_folder|
|
33
|
+
# Dir.glob(File.join(quant_folder, sub_folder, "**/*.rb")).each { |fn| require fn }
|
34
|
+
# end
|
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)
|