quantitative 0.2.1 → 0.3.0
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/README.md +5 -0
- data/lib/quant/{indicators/dominant_cycle_indicators.rb → dominant_cycles_source.rb} +19 -8
- data/lib/quant/experimental.rb +8 -1
- data/lib/quant/indicators/adx.rb +83 -0
- data/lib/quant/indicators/atr.rb +79 -0
- data/lib/quant/indicators/cci.rb +63 -0
- data/lib/quant/indicators/decycler.rb +71 -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 +12 -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 +50 -0
- data/lib/quant/indicators/indicator.rb +50 -2
- data/lib/quant/indicators/mama.rb +143 -0
- data/lib/quant/indicators/mesa.rb +86 -0
- data/lib/quant/indicators/pivot.rb +107 -0
- data/lib/quant/indicators/pivots/atr.rb +41 -0
- data/lib/quant/indicators/pivots/bollinger.rb +45 -0
- data/lib/quant/indicators/pivots/camarilla.rb +61 -0
- data/lib/quant/indicators/pivots/classic.rb +24 -0
- data/lib/quant/indicators/pivots/demark.rb +50 -0
- data/lib/quant/indicators/pivots/donchian.rb +40 -0
- data/lib/quant/indicators/pivots/fibbonacci.rb +22 -0
- data/lib/quant/indicators/pivots/guppy.rb +39 -0
- data/lib/quant/indicators/pivots/keltner.rb +43 -0
- data/lib/quant/indicators/pivots/murrey.rb +34 -0
- data/lib/quant/indicators/pivots/traditional.rb +36 -0
- data/lib/quant/indicators/pivots/woodie.rb +59 -0
- data/lib/quant/indicators_source.rb +140 -0
- data/lib/quant/indicators_sources.rb +36 -10
- data/lib/quant/mixins/stochastic.rb +1 -1
- data/lib/quant/mixins/super_smoother.rb +11 -2
- data/lib/quant/pivots_source.rb +28 -0
- data/lib/quant/refinements/array.rb +14 -0
- data/lib/quant/series.rb +8 -19
- data/lib/quant/settings/indicators.rb +11 -0
- data/lib/quant/version.rb +1 -1
- data/possibilities.png +0 -0
- data/quantitative.gemspec +39 -0
- metadata +27 -5
- data/lib/quant/indicators.rb +0 -14
- data/lib/quant/indicators_proxy.rb +0 -68
@@ -0,0 +1,22 @@
|
|
1
|
+
module Quant
|
2
|
+
class Indicators
|
3
|
+
class Pivots
|
4
|
+
class Fibbonacci < Pivot
|
5
|
+
def averaging_period
|
6
|
+
half_period
|
7
|
+
end
|
8
|
+
|
9
|
+
def fibbonacci_series
|
10
|
+
[0.146, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0, 1.146]
|
11
|
+
end
|
12
|
+
|
13
|
+
def compute_bands
|
14
|
+
fibbonacci_series.each_with_index do |ratio, index|
|
15
|
+
p0[index + 1] = p0.midpoint + ratio * p0.avg_range
|
16
|
+
p0[-index - 1] = p0.midpoint - ratio * p0.avg_range
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Quant
|
2
|
+
class Indicators
|
3
|
+
class Pivots
|
4
|
+
class Guppy < Pivot
|
5
|
+
def guppy_ema(period, band)
|
6
|
+
return p0.input unless p1[band]
|
7
|
+
|
8
|
+
alpha = bars_to_alpha(period)
|
9
|
+
alpha * p0.input + (1 - alpha) * p1[band]
|
10
|
+
end
|
11
|
+
|
12
|
+
def compute_midpoint
|
13
|
+
p0.midpoint = guppy_ema(3, 0)
|
14
|
+
end
|
15
|
+
|
16
|
+
# The short-term MAs are typically set at 3, 5, 8, 10, 12, and 15 periods. The
|
17
|
+
# longer-term MAs are typically set at 30, 35, 40, 45, 50, and 60.
|
18
|
+
def compute
|
19
|
+
p0[1] = guppy_ema(5, 1)
|
20
|
+
p0[2] = guppy_ema(8, 2)
|
21
|
+
p0[3] = guppy_ema(10, 3)
|
22
|
+
p0[4] = guppy_ema(12, 4)
|
23
|
+
p0[5] = guppy_ema(15, 5)
|
24
|
+
p0[6] = guppy_ema(20, 6)
|
25
|
+
p0[7] = guppy_ema(25, 7)
|
26
|
+
|
27
|
+
p0[-1] = guppy_ema(30, -1)
|
28
|
+
p0[-2] = guppy_ema(35, -2)
|
29
|
+
p0[-3] = guppy_ema(40, -3)
|
30
|
+
p0[-4] = guppy_ema(45, -4)
|
31
|
+
p0[-5] = guppy_ema(50, -5)
|
32
|
+
p0[-6] = guppy_ema(60, -6)
|
33
|
+
p0[-7] = guppy_ema(120, -7)
|
34
|
+
p0[-8] = guppy_ema(200, -8)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Quant
|
2
|
+
class Indicators
|
3
|
+
class Pivots
|
4
|
+
class Keltner < Pivot
|
5
|
+
depends_on Indicators::Atr
|
6
|
+
|
7
|
+
def atr_point
|
8
|
+
series.indicators[source].atr.points[t0]
|
9
|
+
end
|
10
|
+
|
11
|
+
def scale
|
12
|
+
5.0
|
13
|
+
end
|
14
|
+
|
15
|
+
def alpha
|
16
|
+
bars_to_alpha(min_period)
|
17
|
+
end
|
18
|
+
|
19
|
+
def compute_midpoint
|
20
|
+
p0.midpoint = alpha * p0.input + (1 - alpha) * p1.midpoint
|
21
|
+
end
|
22
|
+
|
23
|
+
def compute_bands
|
24
|
+
atr_value = atr_point.slow * scale
|
25
|
+
|
26
|
+
p0.h6 = p0.midpoint + 1.000 * atr_value
|
27
|
+
p0.h5 = p0.midpoint + 0.786 * atr_value
|
28
|
+
p0.h4 = p0.midpoint + 0.618 * atr_value
|
29
|
+
p0.h3 = p0.midpoint + 0.500 * atr_value
|
30
|
+
p0.h2 = p0.midpoint + 0.382 * atr_value
|
31
|
+
p0.h1 = p0.midpoint + 0.236 * atr_value
|
32
|
+
|
33
|
+
p0.l1 = p0.midpoint - 0.236 * atr_value
|
34
|
+
p0.l2 = p0.midpoint - 0.382 * atr_value
|
35
|
+
p0.l3 = p0.midpoint - 0.500 * atr_value
|
36
|
+
p0.l4 = p0.midpoint - 0.618 * atr_value
|
37
|
+
p0.l5 = p0.midpoint - 0.786 * atr_value
|
38
|
+
p0.l6 = p0.midpoint - 1.000 * atr_value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative '../pivot'
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
class Indicators
|
5
|
+
class Pivots
|
6
|
+
class Murrey < Pivot
|
7
|
+
def multiplier
|
8
|
+
0.125
|
9
|
+
end
|
10
|
+
|
11
|
+
def compute_midpoint
|
12
|
+
p0.input = (p0.highest - p0.lowest) * multiplier
|
13
|
+
p0.midpoint = p0.lowest + (p0.input * 4.0)
|
14
|
+
end
|
15
|
+
|
16
|
+
def compute_bands
|
17
|
+
p0.h6 = p0.midpoint + p0.input * 6.0
|
18
|
+
p0.h5 = p0.midpoint + p0.input * 5.0
|
19
|
+
p0.h4 = p0.midpoint + p0.input * 4.0
|
20
|
+
p0.h3 = p0.midpoint + p0.input * 3.0
|
21
|
+
p0.h2 = p0.midpoint + p0.input * 2.0
|
22
|
+
p0.h1 = p0.midpoint + p0.input * 1.0
|
23
|
+
|
24
|
+
p0.l1 = p0.midpoint - p0.input * 1.0
|
25
|
+
p0.l2 = p0.midpoint - p0.input * 2.0
|
26
|
+
p0.l3 = p0.midpoint - p0.input * 3.0
|
27
|
+
p0.l4 = p0.midpoint - p0.input * 4.0
|
28
|
+
p0.l5 = p0.midpoint - p0.input * 5.0
|
29
|
+
p0.l6 = p0.midpoint - p0.input * 6.0
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Quant
|
2
|
+
class Indicators
|
3
|
+
class Pivots
|
4
|
+
class Traditional < Pivot
|
5
|
+
def multiplier
|
6
|
+
2.0
|
7
|
+
end
|
8
|
+
|
9
|
+
# Pivot Point (PP) = (High + Low + Close) / 3
|
10
|
+
def compute_midpoint
|
11
|
+
p0.midpoint = p0.input
|
12
|
+
end
|
13
|
+
|
14
|
+
def compute_bands
|
15
|
+
# Third Resistance (R3) = High + 2 × (PP - Low)
|
16
|
+
p0.h3 = p0.high_price + (multiplier * (p0.midpoint - p0.low_price))
|
17
|
+
|
18
|
+
# Second Resistance (R2) = PP + (High - Low)
|
19
|
+
p0.h2 = p0.midpoint + p0.range
|
20
|
+
|
21
|
+
# First Resistance (R1) = (2 × PP) - Low
|
22
|
+
p0.h1 = p0.midpoint * multiplier - p0.low_price
|
23
|
+
|
24
|
+
# First Support (S1) = (2 × PP) - High
|
25
|
+
p0.l1 = p0.midpoint * multiplier - p0.high_price
|
26
|
+
|
27
|
+
# Second Support (S2) = PP - (High - Low)
|
28
|
+
p0.l2 = p0.midpoint - p0.range
|
29
|
+
|
30
|
+
# Third Support (S3) = Low - 2 × (High - PP)
|
31
|
+
p0.l3 = p0.low_price - (multiplier * (p0.high_price - p0.midpoint))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative '../pivot'
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
class Indicators
|
5
|
+
class Pivots
|
6
|
+
# One of the key differences in calculating Woodie's Pivot Point to other pivot
|
7
|
+
# points is that the current session's open price is used in the PP formula with
|
8
|
+
# the previous session's high and low. At the time-of-day that we calculate the
|
9
|
+
# pivot points on this site in our Daily Notes we do not have the opening price
|
10
|
+
# so we use the Classic formula for the Pivot Point and vary the R3 and R4
|
11
|
+
# formula as per Woodie's formulas.
|
12
|
+
|
13
|
+
# Formulas:
|
14
|
+
# R4 = R3 + RANGE
|
15
|
+
# R3 = H + 2 * (PP - L) (same as: R1 + RANGE)
|
16
|
+
# R2 = PP + RANGE
|
17
|
+
# R1 = (2 * PP) - LOW
|
18
|
+
|
19
|
+
# PP = (HIGH + LOW + (TODAY'S OPEN * 2)) / 4
|
20
|
+
# S1 = (2 * PP) - HIGH
|
21
|
+
# S2 = PP - RANGE
|
22
|
+
# S3 = L - 2 * (H - PP) (same as: S1 - RANGE)
|
23
|
+
# S4 = S3 - RANGE
|
24
|
+
class Woodie < Pivot
|
25
|
+
def compute_value
|
26
|
+
p0.input = (t1.high_price + t1.low_price + 2.0 * t0.open_price) / 4.0
|
27
|
+
end
|
28
|
+
|
29
|
+
def compute_bands
|
30
|
+
Quant.experimental("Woodie appears erratic, is unproven and may be incorrect.")
|
31
|
+
|
32
|
+
# R1 = (2 * PP) - LOW
|
33
|
+
p0.h1 = 2.0 * p0.midpoint - t1.low_price
|
34
|
+
|
35
|
+
# R2 = PP + RANGE
|
36
|
+
p0.h2 = p0.midpoint + p0.range
|
37
|
+
|
38
|
+
# R3 = H + 2 * (PP - L) (same as: R1 + RANGE)
|
39
|
+
p0.h3 = t1.high_price + 2.0 * (p0.midpoint - t1.low_price)
|
40
|
+
|
41
|
+
# R4 = R3 + RANGE
|
42
|
+
p0.h4 = p0.h3 + p0.range
|
43
|
+
|
44
|
+
# S1 = (2 * PP) - HIGH
|
45
|
+
p0.l1 = 2.0 * p0.midpoint - t1.high_price
|
46
|
+
|
47
|
+
# S2 = PP - RANGE
|
48
|
+
p0.l2 = p0.midpoint - p0.range
|
49
|
+
|
50
|
+
# S3 = L - 2 * (H - PP) (same as: S1 - RANGE)
|
51
|
+
p0.l3 = t1.low_price - 2.0 * (t1.high_price - p0.midpoint)
|
52
|
+
|
53
|
+
# S4 = S3 - RANGE
|
54
|
+
p0.l4 = p0.l3 - p0.range
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "dominant_cycles_source"
|
4
|
+
module Quant
|
5
|
+
# {Quant::IndicatorSource} holds a collection of {Quant::Indicators::Indicator} for a given input source.
|
6
|
+
# This class ensures dominant cycle computations come before other indicators that depend on them.
|
7
|
+
#
|
8
|
+
# The {Quant::IndicatorSource} class is responsible for lazily loading indicators
|
9
|
+
# so that not all indicators are always engaged and computing their values.
|
10
|
+
# If the indicator is never accessed, it's never computed, saving valuable
|
11
|
+
# processing CPU cycles.
|
12
|
+
#
|
13
|
+
# Indicators are generally built around the concept of a source input value and
|
14
|
+
# that source is designated by the source parameter when instantiating the
|
15
|
+
# {Quant::IndicatorSource} class.
|
16
|
+
#
|
17
|
+
# By design, the {Quant::Indicators::Indicator} class holds the {Quant::Ticks::Tick} instance
|
18
|
+
# alongside the indicator's computed values for that tick.
|
19
|
+
class IndicatorsSource
|
20
|
+
attr_reader :series, :source, :dominant_cycles, :pivots
|
21
|
+
|
22
|
+
def initialize(series:, source:)
|
23
|
+
@series = series
|
24
|
+
@source = source
|
25
|
+
@indicators = {}
|
26
|
+
@ordered_indicators = []
|
27
|
+
@dominant_cycles = DominantCyclesSource.new(indicator_source: self)
|
28
|
+
@pivots = PivotsSource.new(indicator_source: self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](indicator_class)
|
32
|
+
indicator(indicator_class)
|
33
|
+
end
|
34
|
+
|
35
|
+
def <<(tick)
|
36
|
+
@ordered_indicators.each { |indicator| indicator << tick }
|
37
|
+
end
|
38
|
+
|
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
|
+
# Attaches a given Indicator class and defines the method for
|
49
|
+
# accessing it using the given name. Indicators take care of
|
50
|
+
# computing their values when first attached to a populated
|
51
|
+
# series.
|
52
|
+
#
|
53
|
+
# The indicators shipped with the library are all wired into the framework, thus
|
54
|
+
# this method should be used for custom indicators not shipped with the library.
|
55
|
+
#
|
56
|
+
# @param name [Symbol] The name of the method to define for accessing the indicator.
|
57
|
+
# @param indicator_class [Class] The class of the indicator to attach.
|
58
|
+
# @example
|
59
|
+
# series.indicators.oc2.attach(name: :foo, indicator_class: Indicators::Foo)
|
60
|
+
def attach(name:, indicator_class:)
|
61
|
+
define_singleton_method(name) { indicator(indicator_class) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def dominant_cycle
|
65
|
+
indicator(dominant_cycle_indicator_class)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
attr_reader :indicators, :ordered_indicators
|
71
|
+
|
72
|
+
def dominant_cycle_indicator_class
|
73
|
+
Quant.config.indicators.dominant_cycle_indicator_class
|
74
|
+
end
|
75
|
+
|
76
|
+
# Instantiates the indicator class and stores it in the indicators hash. Once
|
77
|
+
# prepared, the indicator becomes active and all ticks pushed into the series
|
78
|
+
# are sent to the indicator for processing.
|
79
|
+
def indicator(indicator_class)
|
80
|
+
indicators[indicator_class] ||= new_indicator(indicator_class)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Instantiates a new indicator and adds it to the collection of indicators.
|
84
|
+
# This method is responsible for adding dependent indicators and the dominant cycle
|
85
|
+
# indicator.
|
86
|
+
def new_indicator(indicator_class)
|
87
|
+
indicator_class.new(series:, source:).tap do |indicator|
|
88
|
+
add_dominant_cycle_indicator(indicator.dominant_cycle_indicator_class, indicator)
|
89
|
+
add_dependent_indicators(indicator_class.dependent_indicator_classes, indicator)
|
90
|
+
add_indicator(indicator_class, indicator)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Adds a new indicator to the collection of indicators. Once added, every
|
95
|
+
# tick added to the series triggers the indicator's compute to fire.
|
96
|
+
# The ordered indicators list is adjusted after adding the new indicator.
|
97
|
+
def add_indicator(indicator_class, new_indicator)
|
98
|
+
return if indicators[indicator_class]
|
99
|
+
|
100
|
+
indicators[indicator_class] = new_indicator
|
101
|
+
@ordered_indicators = (ordered_indicators << new_indicator).sort_by(&:priority)
|
102
|
+
new_indicator
|
103
|
+
end
|
104
|
+
|
105
|
+
# Adds dependent indicators to the indicator collection. This method is reentrant and
|
106
|
+
# will also add depencies of the dependent indicators.
|
107
|
+
# Dependent indicators automatically adjust priority based on the dependency.
|
108
|
+
def add_dependent_indicators(indicator_classes, indicator)
|
109
|
+
return if indicator_classes.empty?
|
110
|
+
|
111
|
+
# Dependent indicators should come after dominant cycle indicators, but before the
|
112
|
+
# indicators that depend on them.
|
113
|
+
dependency_priority = (Quant::Indicators::Indicator::DEPENDENCY_PRIORITY + indicator.priority) / 2
|
114
|
+
|
115
|
+
indicator_classes.each_with_index do |indicator_class, index|
|
116
|
+
next if indicators[indicator_class]
|
117
|
+
|
118
|
+
new_indicator = indicator_class.new(series:, source:)
|
119
|
+
new_indicator.define_singleton_method(:priority) { dependency_priority + index }
|
120
|
+
add_dependent_indicators(indicator_class.dependent_indicator_classes, new_indicator)
|
121
|
+
add_indicator(indicator_class, new_indicator)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Adds the dominant cycle indicator to the collection of indicators. Indicators added
|
126
|
+
# by this method must be a subclass of {Quant::Indicators::DominantCycles::DominantCycle}.
|
127
|
+
def add_dominant_cycle_indicator(dominant_cycle_class, indicator)
|
128
|
+
return if indicator.is_a?(Indicators::DominantCycles::DominantCycle)
|
129
|
+
return unless dominant_cycle_class
|
130
|
+
return if indicators[dominant_cycle_class]
|
131
|
+
|
132
|
+
dominant_cycle = dominant_cycle_class.new(series:, source:)
|
133
|
+
add_indicator(dominant_cycle_class, dominant_cycle)
|
134
|
+
end
|
135
|
+
|
136
|
+
def invalid_source_error(source:)
|
137
|
+
raise InvalidIndicatorSource, "Invalid indicator source: #{source.inspect}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -1,28 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Quant
|
4
|
+
# {Quant::IndicatorSources} pairs a collection of {Quant::Indicators::Indicator} with an input source.
|
5
|
+
# This allows us to only compute indicators for the sources that are referenced at run-time.
|
6
|
+
# Any source explicitly used at run-time will have its indicator computed and only those indicators
|
7
|
+
# will be computed.
|
4
8
|
class IndicatorsSources
|
9
|
+
ALL_SOURCES = [
|
10
|
+
PRICE_SOURCES = %i[price open_price high_price low_price close_price].freeze,
|
11
|
+
VOLUME_SOURCES = %i[volume base_volume target_volume].freeze,
|
12
|
+
COMPUTED_SOURCES = %i[oc2 hl2 hlc3 ohlc4].freeze
|
13
|
+
].flatten.freeze
|
14
|
+
|
15
|
+
attr_reader :series
|
16
|
+
|
5
17
|
def initialize(series:)
|
6
18
|
@series = series
|
7
|
-
@
|
8
|
-
end
|
9
|
-
|
10
|
-
def new_indicator(indicator)
|
11
|
-
@indicator_sources[indicator.source] ||= Indicators.new(series: @series, source: indicator.source)
|
19
|
+
@sources = {}
|
12
20
|
end
|
13
21
|
|
14
22
|
def [](source)
|
15
|
-
|
23
|
+
raise invalid_source_error(source:) unless ALL_SOURCES.include?(source)
|
16
24
|
|
17
|
-
|
25
|
+
@sources[source] ||= IndicatorsSource.new(series:, source:)
|
18
26
|
end
|
19
27
|
|
20
28
|
def <<(tick)
|
21
|
-
@
|
29
|
+
@sources.each_value { |indicator| indicator << tick }
|
30
|
+
end
|
31
|
+
|
32
|
+
ALL_SOURCES.each do |source|
|
33
|
+
define_method(source) do
|
34
|
+
@sources[source] ||= IndicatorsSource.new(series:, source:)
|
35
|
+
end
|
22
36
|
end
|
23
37
|
|
24
|
-
def
|
25
|
-
|
38
|
+
def respond_to_missing?(method, *)
|
39
|
+
oc2.respond_to?(method)
|
40
|
+
end
|
41
|
+
|
42
|
+
def method_missing(method_name, *args, &block)
|
43
|
+
return super unless respond_to_missing?(method_name)
|
44
|
+
|
45
|
+
oc2.send(method_name, *args, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def invalid_source_error(source:)
|
51
|
+
raise Errors::InvalidIndicatorSource, "Invalid indicator source: #{source.inspect}"
|
26
52
|
end
|
27
53
|
end
|
28
54
|
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
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
class PivotsSource
|
5
|
+
def initialize(indicator_source:)
|
6
|
+
@indicator_source = indicator_source
|
7
|
+
end
|
8
|
+
|
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
|
+
private
|
23
|
+
|
24
|
+
def indicator(indicator_class)
|
25
|
+
@indicator_source[indicator_class]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -103,6 +103,7 @@ module Quant
|
|
103
103
|
|
104
104
|
# Computes the mean of the array. When +n+ is specified, the mean is computed over
|
105
105
|
# the last +n+ elements, otherwise it is computed over the entire array.
|
106
|
+
# If the array is empty, 0.0 is returned.
|
106
107
|
#
|
107
108
|
# @param n [Integer] the number of elements to compute the mean over
|
108
109
|
# @return [Float]
|
@@ -113,6 +114,19 @@ module Quant
|
|
113
114
|
subset.sum / subset.size.to_f
|
114
115
|
end
|
115
116
|
|
117
|
+
# Returns the largest absolute value in the array. When +n+ is specified, the peak is computed over
|
118
|
+
# the last +n+ elements, otherwise it is computed over the entire array.
|
119
|
+
# If the array is empty, 0.0 is returned.
|
120
|
+
#
|
121
|
+
# @param n [Integer] the number of elements to compute the peak over
|
122
|
+
# @return [Float]
|
123
|
+
def peak(n: size)
|
124
|
+
subset = last(n)
|
125
|
+
return 0.0 if subset.empty?
|
126
|
+
|
127
|
+
[subset.max.abs, subset.min.abs].max
|
128
|
+
end
|
129
|
+
|
116
130
|
# Computes the Exponential Moving Average (EMA) of the array. When +n+ is specified,
|
117
131
|
# the EMA is computed over the last +n+ elements, otherwise it is computed over the entire array.
|
118
132
|
# An Array of EMA's is returned, with the first entry always the first value in the subset.
|
data/lib/quant/series.rb
CHANGED
@@ -60,18 +60,20 @@ module Quant
|
|
60
60
|
@ticks = []
|
61
61
|
end
|
62
62
|
|
63
|
-
def
|
64
|
-
selected_ticks = ticks[start_iteration..stop_iteration]
|
63
|
+
def build_limited_series(selected_ticks)
|
65
64
|
return self if selected_ticks.size == ticks.size
|
66
65
|
|
67
66
|
self.class.from_ticks(symbol:, interval:, ticks: selected_ticks)
|
68
67
|
end
|
69
68
|
|
69
|
+
def limit_iterations(start_iteration, stop_iteration)
|
70
|
+
selected_ticks = ticks[start_iteration..stop_iteration]
|
71
|
+
build_limited_series selected_ticks
|
72
|
+
end
|
73
|
+
|
70
74
|
def limit(period)
|
71
75
|
selected_ticks = ticks.select{ |tick| period.cover?(tick.close_timestamp) }
|
72
|
-
|
73
|
-
|
74
|
-
self.class.from_ticks(symbol:, interval:, ticks: selected_ticks)
|
76
|
+
build_limited_series selected_ticks
|
75
77
|
end
|
76
78
|
|
77
79
|
def_delegator :@ticks, :[]
|
@@ -80,6 +82,7 @@ module Quant
|
|
80
82
|
def_delegator :@ticks, :select!
|
81
83
|
def_delegator :@ticks, :reject!
|
82
84
|
def_delegator :@ticks, :last
|
85
|
+
def_delegator :@ticks, :take
|
83
86
|
|
84
87
|
def highest
|
85
88
|
ticks.max_by(&:high_price)
|
@@ -101,20 +104,6 @@ module Quant
|
|
101
104
|
"#<#{self.class.name} symbol=#{symbol} interval=#{interval} ticks=#{ticks.size}>"
|
102
105
|
end
|
103
106
|
|
104
|
-
# When the first indicator is instantiated, it will also lead to instantiating
|
105
|
-
# the dominant cycle indicator. The `new_indicator_lock` prevents reentrant calls
|
106
|
-
# to the `new_indicator` method with infinite recursion.
|
107
|
-
def new_indicator(indicator)
|
108
|
-
return if @new_indicator_lock
|
109
|
-
|
110
|
-
begin
|
111
|
-
@new_indicator_lock = true
|
112
|
-
indicators.new_indicator(indicator)
|
113
|
-
ensure
|
114
|
-
@new_indicator_lock = false
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
107
|
def <<(tick)
|
119
108
|
tick = Ticks::Spot.new(price: tick) if tick.is_a?(Numeric)
|
120
109
|
indicators << tick unless tick.series?
|
@@ -59,6 +59,7 @@ module Quant
|
|
59
59
|
@half_period = settings[:half_period] || compute_half_period
|
60
60
|
@micro_period = settings[:micro_period] || Settings::MICRO_PERIOD
|
61
61
|
|
62
|
+
@dominant_cycle_indicator_class = nil
|
62
63
|
@dominant_cycle_kind = settings[:dominant_cycle_kind] || Settings::DOMINANT_CYCLE_KINDS.first
|
63
64
|
@pivot_kind = settings[:pivot_kind] || Settings::PIVOT_KINDS.first
|
64
65
|
end
|
@@ -68,6 +69,7 @@ module Quant
|
|
68
69
|
@min_period = settings.fetch(:min_period, @min_period)
|
69
70
|
compute_half_period
|
70
71
|
@micro_period = settings.fetch(:micro_period, @micro_period)
|
72
|
+
@dominant_cycle_indicator_class = nil
|
71
73
|
@dominant_cycle_kind = settings.fetch(:dominant_cycle_kind, @dominant_cycle_kind)
|
72
74
|
@pivot_kind = settings.fetch(:pivot_kind, @pivot_kind)
|
73
75
|
end
|
@@ -83,6 +85,15 @@ module Quant
|
|
83
85
|
def compute_half_period
|
84
86
|
@half_period = (max_period + min_period) / 2
|
85
87
|
end
|
88
|
+
|
89
|
+
def dominant_cycle_indicator_class
|
90
|
+
return @dominant_cycle_indicator_class if @dominant_cycle_indicator_class
|
91
|
+
|
92
|
+
base_class_name = dominant_cycle_kind.to_s.split("_").map(&:capitalize).join
|
93
|
+
class_name = "Quant::Indicators::DominantCycles::#{base_class_name}"
|
94
|
+
|
95
|
+
@dominant_cycle_indicator_class = Object.const_get(class_name)
|
96
|
+
end
|
86
97
|
end
|
87
98
|
end
|
88
99
|
end
|
data/lib/quant/version.rb
CHANGED
data/possibilities.png
ADDED
Binary file
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/quant/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "quantitative"
|
7
|
+
spec.version = Quant::VERSION
|
8
|
+
spec.authors = ["Michael Lang"]
|
9
|
+
spec.email = ["mwlang@cybrains.net"]
|
10
|
+
|
11
|
+
spec.summary = "Quantitative and statistical tools written for Ruby 3.2+ for trading and finance."
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = "https://github.com/mwlang/quantitative"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.2"
|
16
|
+
|
17
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
|
+
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
21
|
+
spec.metadata["changelog_uri"] = spec.homepage
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
spec.bindir = "exe"
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
# Uncomment to register a new dependency of your gem
|
35
|
+
spec.add_dependency "oj", "~> 3.10"
|
36
|
+
|
37
|
+
# For more information and examples about making a new gem, check out our
|
38
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
39
|
+
end
|