quantitative 0.2.2 → 0.3.1
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/README.md +5 -0
- data/lib/quant/asset.rb +0 -2
- data/lib/quant/{dominant_cycle_indicators.rb → dominant_cycles_source.rb} +18 -10
- data/lib/quant/experimental.rb +11 -2
- data/lib/quant/indicators/adx.rb +3 -4
- data/lib/quant/indicators/atr.rb +1 -4
- data/lib/quant/indicators/cci.rb +1 -1
- data/lib/quant/indicators/decycler.rb +17 -31
- data/lib/quant/indicators/dominant_cycles/acr.rb +3 -6
- data/lib/quant/indicators/dominant_cycles/band_pass.rb +2 -4
- data/lib/quant/indicators/dominant_cycles/differential.rb +2 -2
- data/lib/quant/indicators/dominant_cycles/dominant_cycle.rb +11 -4
- data/lib/quant/indicators/dominant_cycles/half_period.rb +2 -4
- data/lib/quant/indicators/dominant_cycles/homodyne.rb +2 -5
- data/lib/quant/indicators/dominant_cycles/phase_accumulator.rb +2 -4
- data/lib/quant/indicators/frama.rb +6 -9
- data/lib/quant/indicators/indicator.rb +46 -3
- data/lib/quant/indicators/indicator_point.rb +1 -1
- data/lib/quant/indicators/mama.rb +1 -1
- data/lib/quant/indicators/mesa.rb +1 -1
- data/lib/quant/indicators/ping.rb +1 -1
- 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 +33 -0
- data/lib/quant/indicators/pivots/traditional.rb +36 -0
- data/lib/quant/indicators/pivots/woodie.rb +59 -0
- data/lib/quant/indicators.rb +1 -13
- data/lib/quant/indicators_source.rb +139 -0
- data/lib/quant/indicators_sources.rb +36 -10
- data/lib/quant/mixins/filters.rb +0 -3
- data/lib/quant/mixins/moving_averages.rb +0 -3
- 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/ticks/ohlc.rb +0 -2
- data/lib/quant/ticks/spot.rb +0 -2
- data/lib/quant/version.rb +1 -1
- data/lib/quantitative.rb +21 -4
- data/possibilities.png +0 -0
- metadata +34 -5
- data/lib/quant/indicators_proxy.rb +0 -68
@@ -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
|
data/lib/quant/mixins/filters.rb
CHANGED
@@ -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/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,31 @@ 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
|
-
|
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
|
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
|
13
30
|
|
14
|
-
#
|
15
|
-
%w(refinements
|
31
|
+
# Refinements aren't autoloaded by Zeitwerk, so we need to require them manually.
|
32
|
+
%w(refinements).each do |sub_folder|
|
16
33
|
Dir.glob(File.join(quant_folder, sub_folder, "**/*.rb")).each { |fn| require fn }
|
17
34
|
end
|
data/possibilities.png
ADDED
Binary file
|
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.
|
4
|
+
version: 0.3.1
|
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-
|
11
|
+
date: 2024-06-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oj
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: zeitwerk
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.6'
|
27
41
|
description: Quantitative and statistical tools written for Ruby 3.2+ for trading
|
28
42
|
and finance.
|
29
43
|
email:
|
@@ -48,7 +62,7 @@ files:
|
|
48
62
|
- lib/quant/asset_class.rb
|
49
63
|
- lib/quant/attributes.rb
|
50
64
|
- lib/quant/config.rb
|
51
|
-
- lib/quant/
|
65
|
+
- lib/quant/dominant_cycles_source.rb
|
52
66
|
- lib/quant/errors.rb
|
53
67
|
- lib/quant/experimental.rb
|
54
68
|
- lib/quant/indicators.rb
|
@@ -69,7 +83,20 @@ files:
|
|
69
83
|
- lib/quant/indicators/mama.rb
|
70
84
|
- lib/quant/indicators/mesa.rb
|
71
85
|
- lib/quant/indicators/ping.rb
|
72
|
-
- lib/quant/
|
86
|
+
- lib/quant/indicators/pivot.rb
|
87
|
+
- lib/quant/indicators/pivots/atr.rb
|
88
|
+
- lib/quant/indicators/pivots/bollinger.rb
|
89
|
+
- lib/quant/indicators/pivots/camarilla.rb
|
90
|
+
- lib/quant/indicators/pivots/classic.rb
|
91
|
+
- lib/quant/indicators/pivots/demark.rb
|
92
|
+
- lib/quant/indicators/pivots/donchian.rb
|
93
|
+
- lib/quant/indicators/pivots/fibbonacci.rb
|
94
|
+
- lib/quant/indicators/pivots/guppy.rb
|
95
|
+
- lib/quant/indicators/pivots/keltner.rb
|
96
|
+
- lib/quant/indicators/pivots/murrey.rb
|
97
|
+
- lib/quant/indicators/pivots/traditional.rb
|
98
|
+
- lib/quant/indicators/pivots/woodie.rb
|
99
|
+
- lib/quant/indicators_source.rb
|
73
100
|
- lib/quant/indicators_sources.rb
|
74
101
|
- lib/quant/interval.rb
|
75
102
|
- lib/quant/mixins/butterworth_filters.rb
|
@@ -86,6 +113,7 @@ files:
|
|
86
113
|
- lib/quant/mixins/super_smoother.rb
|
87
114
|
- lib/quant/mixins/universal_filters.rb
|
88
115
|
- lib/quant/mixins/weighted_moving_average.rb
|
116
|
+
- lib/quant/pivots_source.rb
|
89
117
|
- lib/quant/refinements/array.rb
|
90
118
|
- lib/quant/series.rb
|
91
119
|
- lib/quant/settings.rb
|
@@ -101,6 +129,7 @@ files:
|
|
101
129
|
- lib/quant/time_period.rb
|
102
130
|
- lib/quant/version.rb
|
103
131
|
- lib/quantitative.rb
|
132
|
+
- possibilities.png
|
104
133
|
homepage: https://github.com/mwlang/quantitative
|
105
134
|
licenses:
|
106
135
|
- MIT
|
@@ -124,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
153
|
- !ruby/object:Gem::Version
|
125
154
|
version: '0'
|
126
155
|
requirements: []
|
127
|
-
rubygems_version: 3.5.
|
156
|
+
rubygems_version: 3.5.11
|
128
157
|
signing_key:
|
129
158
|
specification_version: 4
|
130
159
|
summary: Quantitative and statistical tools written for Ruby 3.2+ for trading and
|
@@ -1,68 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Quant
|
4
|
-
# The {Quant::IndicatorsProxy} class is responsible for lazily loading indicators
|
5
|
-
# so that not all indicators are always engaged and computing their values.
|
6
|
-
# If the indicator is never accessed, it's never computed, saving valuable
|
7
|
-
# processing CPU cycles.
|
8
|
-
#
|
9
|
-
# Indicators are generally built around the concept of a source input value and
|
10
|
-
# that source is designated by the source parameter when instantiating the
|
11
|
-
# {Quant::IndicatorsProxy} class.
|
12
|
-
#
|
13
|
-
# By design, the {Quant::Indicator} class holds the {Quant::Ticks::Tick} instance
|
14
|
-
# alongside the indicator's computed values for that tick.
|
15
|
-
class IndicatorsProxy
|
16
|
-
attr_reader :series, :source, :dominant_cycle, :indicators
|
17
|
-
|
18
|
-
def initialize(series:, source:)
|
19
|
-
@series = series
|
20
|
-
@source = source
|
21
|
-
@indicators = {}
|
22
|
-
@dominant_cycle = dominant_cycle_indicator
|
23
|
-
end
|
24
|
-
|
25
|
-
def dominant_cycle_indicator
|
26
|
-
kind = Quant.config.indicators.dominant_cycle_kind.to_s
|
27
|
-
base_class_name = kind.split("_").map(&:capitalize).join
|
28
|
-
class_name = "Quant::Indicators::DominantCycles::#{base_class_name}"
|
29
|
-
indicator_class = Object.const_get(class_name)
|
30
|
-
indicator_class.new(series:, source:)
|
31
|
-
end
|
32
|
-
|
33
|
-
# Instantiates the indicator class and stores it in the indicators hash. Once
|
34
|
-
# prepared, the indicator becomes active and all ticks pushed into the series
|
35
|
-
# are sent to the indicator for processing.
|
36
|
-
def indicator(indicator_class)
|
37
|
-
indicators[indicator_class] ||= indicator_class.new(series:, source:)
|
38
|
-
end
|
39
|
-
|
40
|
-
# Adds the tick to all active indicators, triggering them to compute
|
41
|
-
# new values against the latest tick.
|
42
|
-
#
|
43
|
-
# NOTE: Dominant cycle indicators must be computed first as many
|
44
|
-
# indicators are adaptive and require the dominant cycle period.
|
45
|
-
# The IndicatorsProxy class is not responsible for enforcing
|
46
|
-
# this order of events.
|
47
|
-
def <<(tick)
|
48
|
-
dominant_cycle << tick
|
49
|
-
indicators.each_value { |indicator| indicator << tick }
|
50
|
-
end
|
51
|
-
|
52
|
-
# Attaches a given Indicator class and defines the method for
|
53
|
-
# accessing it using the given name. Indicators take care of
|
54
|
-
# computing their values when first attached to a populated
|
55
|
-
# series.
|
56
|
-
#
|
57
|
-
# The indicators shipped with the library are all wired into the framework, thus
|
58
|
-
# this method should be used for custom indicators not shipped with the library.
|
59
|
-
#
|
60
|
-
# @param name [Symbol] The name of the method to define for accessing the indicator.
|
61
|
-
# @param indicator_class [Class] The class of the indicator to attach.
|
62
|
-
# @example
|
63
|
-
# series.indicators.oc2.attach(name: :foo, indicator_class: Indicators::Foo)
|
64
|
-
def attach(name:, indicator_class:)
|
65
|
-
define_singleton_method(name) { indicator(indicator_class) }
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|