quantitative 0.2.2 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|