quantitative 0.2.2 → 0.3.0

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +5 -0
  4. data/lib/quant/{dominant_cycle_indicators.rb → dominant_cycles_source.rb} +18 -10
  5. data/lib/quant/experimental.rb +8 -1
  6. data/lib/quant/indicators/adx.rb +3 -0
  7. data/lib/quant/indicators/decycler.rb +16 -30
  8. data/lib/quant/indicators/dominant_cycles/dominant_cycle.rb +9 -0
  9. data/lib/quant/indicators/frama.rb +5 -8
  10. data/lib/quant/indicators/indicator.rb +45 -2
  11. data/lib/quant/indicators/pivot.rb +107 -0
  12. data/lib/quant/indicators/pivots/atr.rb +41 -0
  13. data/lib/quant/indicators/pivots/bollinger.rb +45 -0
  14. data/lib/quant/indicators/pivots/camarilla.rb +61 -0
  15. data/lib/quant/indicators/pivots/classic.rb +24 -0
  16. data/lib/quant/indicators/pivots/demark.rb +50 -0
  17. data/lib/quant/indicators/pivots/donchian.rb +40 -0
  18. data/lib/quant/indicators/pivots/fibbonacci.rb +22 -0
  19. data/lib/quant/indicators/pivots/guppy.rb +39 -0
  20. data/lib/quant/indicators/pivots/keltner.rb +43 -0
  21. data/lib/quant/indicators/pivots/murrey.rb +34 -0
  22. data/lib/quant/indicators/pivots/traditional.rb +36 -0
  23. data/lib/quant/indicators/pivots/woodie.rb +59 -0
  24. data/lib/quant/indicators_source.rb +140 -0
  25. data/lib/quant/indicators_sources.rb +36 -10
  26. data/lib/quant/pivots_source.rb +28 -0
  27. data/lib/quant/refinements/array.rb +14 -0
  28. data/lib/quant/series.rb +8 -19
  29. data/lib/quant/settings/indicators.rb +11 -0
  30. data/lib/quant/version.rb +1 -1
  31. data/possibilities.png +0 -0
  32. data/quantitative.gemspec +39 -0
  33. metadata +20 -5
  34. data/lib/quant/indicators.rb +0 -18
  35. data/lib/quant/indicators_proxy.rb +0 -68
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ class Indicators
5
+ class Pivots
6
+ class Donchian < Pivot
7
+ using Quant
8
+
9
+ def st_period; min_period end
10
+ def mt_period; half_period end
11
+ def lt_period; max_period end
12
+
13
+ def st_highs; @st_highs ||= [].max_size!(st_period) end
14
+ def st_lows; @st_lows ||= [].max_size!(st_period) end
15
+ def mt_highs; @mt_highs ||= [].max_size!(mt_period) end
16
+ def mt_lows; @mt_lows ||= [].max_size!(mt_period) end
17
+ def lt_highs; @lt_highs ||= [].max_size!(lt_period) end
18
+ def lt_lows; @lt_lows ||= [].max_size!(lt_period) end
19
+
20
+ def compute_bands
21
+ st_highs << p0.high_price
22
+ st_lows << p0.low_price
23
+ mt_highs << p0.high_price
24
+ mt_lows << p0.low_price
25
+ lt_highs << p0.high_price
26
+ lt_lows << p0.low_price
27
+
28
+ p0.h1 = @st_highs.maximum
29
+ p0.l1 = @st_lows.minimum
30
+
31
+ p0.h2 = @mt_highs.maximum
32
+ p0.l2 = @mt_lows.minimum
33
+
34
+ p0.h3 = @lt_highs.maximum
35
+ p0.l3 = @lt_lows.minimum
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -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
- @indicator_sources = {}
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
- return @indicator_sources[source] if @indicator_sources.key?(source)
23
+ raise invalid_source_error(source:) unless ALL_SOURCES.include?(source)
16
24
 
17
- raise Quant::Errors::InvalidIndicatorSource, "Invalid source, #{source.inspect}."
25
+ @sources[source] ||= IndicatorsSource.new(series:, source:)
18
26
  end
19
27
 
20
28
  def <<(tick)
21
- @indicator_sources.each_value { |indicator| indicator << tick }
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 oc2
25
- @indicator_sources[:oc2] ||= Indicators.new(series: @series, source: :oc2)
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
@@ -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 limit_iterations(start_iteration, stop_iteration)
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
- return self if selected_ticks.size == ticks.size
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quant
4
- VERSION = "0.2.2"
4
+ VERSION = "0.3.0"
5
5
  end
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