quantitative 0.1.5 → 0.1.6
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/.rubocop.yml +2 -0
- data/Gemfile.lock +1 -1
- data/lib/quant/attributes.rb +92 -8
- data/lib/quant/indicators/indicator.rb +8 -2
- data/lib/quant/indicators/indicator_point.rb +1 -1
- data/lib/quant/indicators/ma.rb +2 -0
- data/lib/quant/indicators/ping.rb +6 -0
- data/lib/quant/indicators_proxy.rb +38 -0
- data/lib/quant/mixins/moving_averages.rb +86 -0
- data/lib/quant/mixins/super_smoother.rb +1 -2
- data/lib/quant/refinements/array.rb +4 -3
- data/lib/quant/settings.rb +2 -0
- data/lib/quant/ticks/ohlc.rb +1 -1
- data/lib/quant/version.rb +1 -1
- data/lib/quantitative.rb +1 -1
- metadata +2 -2
- data/lib/quant/mixins/weighted_average.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72d65b65fdcbb81650fcce3233f3fbde9c99c4b9b9ace1ce3bbc3b8e565152f9
|
4
|
+
data.tar.gz: f687ab3c32e88ffc579a9f92ff428846f91e60ff166d3b4379963deb15a88425
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 594d57a819ce8d561c064f685353d27ef4dfd1cb2e76e5cf07758bcedb1c501d2bc06859aff89cf6f4bc744e327ede1290e31b4d4ffb1daed99d614735eb0a76
|
7
|
+
data.tar.gz: 5fbfb29d1501a3562788abf83ef4fcb78bcb17fcfc030e7543b42f3c29c2a7a5c3c8fba187494d4655691e117e383af59bca428764f49fd7e41fcef911ef6d40
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
data/lib/quant/attributes.rb
CHANGED
@@ -1,10 +1,60 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Quant
|
4
|
+
# {Quant::Attributes} is similar to an +attr_accessor+ definition. It provides a simple DSL
|
5
|
+
# for defining attributes or properies on an {Quant::Indicators::IndicatorPoint} class.
|
6
|
+
#
|
7
|
+
# {Quant::Attributes} tracks all defined attributes from child to parent classes,
|
8
|
+
# allowing child classes to inherit their parent's attributes as well as redefine them.
|
9
|
+
#
|
10
|
+
# The exception on redefining is that a serialized key cannot be redefined. Experience
|
11
|
+
# has proven that this leads to serialization surprises where what was written to a specific
|
12
|
+
# key is not what was expected!
|
13
|
+
#
|
14
|
+
# NOTE: The above design constraint could be improved with a force or overwrite option.
|
15
|
+
#
|
16
|
+
# If :default is an immediate value (Integer, Float, Boolean, etc.), it will be used as the
|
17
|
+
# initial value for the attribute. If :default is a Symbol, it will send a message on
|
18
|
+
# current instance of the class get the default value.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# class FooPoint < IndicatorPoint
|
22
|
+
# # will not serialize to a key
|
23
|
+
# attribute :bar
|
24
|
+
# # serializes to "bzz" key
|
25
|
+
# attribute :baz, key: "bzz"
|
26
|
+
# # calls the random method on the instance for the default value
|
27
|
+
# attribute :foobar, default: :random
|
28
|
+
# # delegated to the tick's high_price method
|
29
|
+
# attribute :high, default: :high_price
|
30
|
+
# # calls the lambda bound to instance for default
|
31
|
+
# attribute :low, default: -> { high_price - 5 }
|
32
|
+
#
|
33
|
+
# def random
|
34
|
+
# rand(100)
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# class BarPoint < FooPoint
|
39
|
+
# attribute :bar, key: "brr" # redefines and sets the key for bar
|
40
|
+
# attribute :qux, key: "qxx", default: 5.0 # serializes to "qxx" and defaults to 5.0
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# FooPoint.attributes
|
44
|
+
# # => { bar: { key: nil, default: nil },
|
45
|
+
# baz: { key: "bzz", default: nil } }
|
46
|
+
#
|
47
|
+
# BarPoint.attributes
|
48
|
+
# # => { bar: { key: "brr", default: nil },
|
49
|
+
# # baz: { key: "bzz", default: nil },
|
50
|
+
# # qux: { key: "qxx", default: nil } }
|
51
|
+
#
|
52
|
+
# BarPoint.new.bar # => nil
|
53
|
+
# BarPoint.new.qux # => 5.0
|
54
|
+
# BarPoint.new.bar = 2.0 => 2.0
|
4
55
|
module Attributes
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# and the attribute's key and default value.
|
56
|
+
# The +registry+ key is the class registering an attrbritute and is itself
|
57
|
+
# a hash of the attribute name and the attribute's key and default value.
|
8
58
|
# Internal use only.
|
9
59
|
#
|
10
60
|
# @example
|
@@ -47,9 +97,18 @@ module Quant
|
|
47
97
|
end
|
48
98
|
|
49
99
|
module InstanceMethods
|
50
|
-
|
51
|
-
|
52
|
-
|
100
|
+
# Makes some assumptions about the class's initialization having a +tick+ keyword argument.
|
101
|
+
#
|
102
|
+
# The challenge here is that we prepend this module to the class, and we are
|
103
|
+
# initializing attributes before the owning class gets the opportunity to initialize
|
104
|
+
# variables that we wanted to depend on with being able to define a default
|
105
|
+
# value that could set default values from a +tick+ method.
|
106
|
+
#
|
107
|
+
# Ok for now. May need to be more flexible in the future. Alternative strategy could be
|
108
|
+
# to lazy eval the default value the first time it is accessed.
|
109
|
+
def initialize(*args, **kwargs)
|
110
|
+
initialize_attributes(tick: kwargs[:tick])
|
111
|
+
super(*args, **kwargs)
|
53
112
|
end
|
54
113
|
|
55
114
|
# Iterates over all defined attributes in a child => parent hierarchy,
|
@@ -65,17 +124,42 @@ module Quant
|
|
65
124
|
end
|
66
125
|
end
|
67
126
|
|
127
|
+
# The default value can be one of the following:
|
128
|
+
# - A symbol that is a method on the instance responds to
|
129
|
+
# - A symbol that is a method that the instance's tick responds to
|
130
|
+
# - A Proc that is bound to the instance
|
131
|
+
# - An immediate value (Integer, Float, Boolean, etc.)
|
132
|
+
def default_value_for(entry, new_tick)
|
133
|
+
# let's not assume tick is always available/implemented
|
134
|
+
# can get from instance or from initializer passed here as `new_tick`
|
135
|
+
current_tick = new_tick
|
136
|
+
current_tick ||= tick if respond_to?(:tick)
|
137
|
+
|
138
|
+
if entry[:default].is_a?(Symbol) && respond_to?(entry[:default])
|
139
|
+
send(entry[:default])
|
140
|
+
|
141
|
+
elsif entry[:default].is_a?(Symbol) && current_tick&.respond_to?(entry[:default])
|
142
|
+
current_tick.send(entry[:default])
|
143
|
+
|
144
|
+
elsif entry[:default].is_a?(Proc)
|
145
|
+
instance_exec(&entry[:default])
|
146
|
+
|
147
|
+
else
|
148
|
+
entry[:default]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
68
152
|
# Initializes the defined attributes with default values and
|
69
153
|
# defines accessor methods for each attribute.
|
70
154
|
# If a child class redefines a parent's attribute, the child's
|
71
155
|
# definition will be used.
|
72
|
-
def initialize_attributes
|
156
|
+
def initialize_attributes(tick:)
|
73
157
|
each_attribute do |name, entry|
|
74
158
|
# use the child's definition, skipping the parent's
|
75
159
|
next if respond_to?(name)
|
76
160
|
|
77
161
|
ivar_name = "@#{name}"
|
78
|
-
instance_variable_set(ivar_name, entry
|
162
|
+
instance_variable_set(ivar_name, default_value_for(entry, tick))
|
79
163
|
define_singleton_method(name) { instance_variable_get(ivar_name) }
|
80
164
|
define_singleton_method("#{name}=") { |value| instance_variable_set(ivar_name, value) }
|
81
165
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Quant
|
2
4
|
class Indicators
|
3
5
|
class Indicator
|
@@ -27,6 +29,10 @@ module Quant
|
|
27
29
|
@points.keys
|
28
30
|
end
|
29
31
|
|
32
|
+
def [](index)
|
33
|
+
values[index]
|
34
|
+
end
|
35
|
+
|
30
36
|
def values
|
31
37
|
@points.values
|
32
38
|
end
|
@@ -59,7 +65,7 @@ module Quant
|
|
59
65
|
end
|
60
66
|
|
61
67
|
def inspect
|
62
|
-
"#<#{self.class.name} symbol=#{series.symbol} source=#{source} #{
|
68
|
+
"#<#{self.class.name} symbol=#{series.symbol} source=#{source} #{ticks.size} ticks>"
|
63
69
|
end
|
64
70
|
|
65
71
|
def compute
|
@@ -168,4 +174,4 @@ module Quant
|
|
168
174
|
# end
|
169
175
|
end
|
170
176
|
end
|
171
|
-
end
|
177
|
+
end
|
data/lib/quant/indicators/ma.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Quant
|
2
4
|
class Indicators
|
5
|
+
# A simple point used primarily to test the indicator system in unit tests.
|
6
|
+
# It has a simple computation that just sets the pong value to the input value
|
7
|
+
# and increments the compute_count by 1 each time compute is called.
|
8
|
+
# Sometimes you just gotta play ping pong to win.
|
3
9
|
class PingPoint < IndicatorPoint
|
4
10
|
attribute :pong
|
5
11
|
attribute :compute_count, default: 0
|
@@ -1,6 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
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.
|
4
15
|
class IndicatorsProxy
|
5
16
|
attr_reader :series, :source, :indicators
|
6
17
|
|
@@ -10,14 +21,41 @@ module Quant
|
|
10
21
|
@indicators = {}
|
11
22
|
end
|
12
23
|
|
24
|
+
# Instantiates the indicator class and stores it in the indicators hash. Once
|
25
|
+
# prepared, the indicator becomes active and all ticks pushed into the series
|
26
|
+
# are sent to the indicator for processing.
|
13
27
|
def indicator(indicator_class)
|
14
28
|
indicators[indicator_class] ||= indicator_class.new(series: series, source: source)
|
15
29
|
end
|
16
30
|
|
31
|
+
# Adds the tick to all active indicators, triggering them to compute
|
32
|
+
# new values against the latest tick.
|
33
|
+
#
|
34
|
+
# NOTE: Dominant cycle indicators must be computed first as many
|
35
|
+
# indicators are adaptive and require the dominant cycle period.
|
36
|
+
# The IndicatorsProxy class is not responsible for enforcing
|
37
|
+
# this order of events.
|
17
38
|
def <<(tick)
|
18
39
|
indicators.each_value { |indicator| indicator << tick }
|
19
40
|
end
|
20
41
|
|
42
|
+
# Attaches a given Indicator class and defines the method for
|
43
|
+
# accessing it using the given name. Indicators take care of
|
44
|
+
# computing their values when first attached to a populated
|
45
|
+
# series.
|
46
|
+
#
|
47
|
+
# The indicators shipped with the library are all wired into the framework, thus
|
48
|
+
# this method should be used for custom indicators not shipped with the library.
|
49
|
+
#
|
50
|
+
# @param name [Symbol] The name of the method to define for accessing the indicator.
|
51
|
+
# @param indicator_class [Class] The class of the indicator to attach.
|
52
|
+
# @example
|
53
|
+
# series.indicators.oc2.attach(name: :foo, indicator_class: Indicators::Foo)
|
54
|
+
def attach(name:, indicator_class:)
|
55
|
+
define_singleton_method(name) { indicator(indicator_class) }
|
56
|
+
end
|
57
|
+
|
21
58
|
def ma; indicator(Indicators::Ma) end
|
59
|
+
def ping; indicator(Indicators::Ping) end
|
22
60
|
end
|
23
61
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
module Mixins
|
5
|
+
module MovingAverages
|
6
|
+
using Quant
|
7
|
+
|
8
|
+
# Computes the Weighted Moving Average (WMA) of the series, using the four most recent data points.
|
9
|
+
#
|
10
|
+
# @param source [Symbol] the source of the data points to be used in the calculation.
|
11
|
+
# @return [Float] the weighted average of the series.
|
12
|
+
# @raise [ArgumentError] if the source is not a Symbol.
|
13
|
+
# @example
|
14
|
+
# p0.wma = weighted_average(:close_price)
|
15
|
+
def weighted_moving_average(source)
|
16
|
+
raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
|
17
|
+
|
18
|
+
[4.0 * p0.send(source),
|
19
|
+
3.0 * p1.send(source),
|
20
|
+
2.0 * p2.send(source),
|
21
|
+
p3.send(source)].sum / 10.0
|
22
|
+
end
|
23
|
+
alias wma weighted_moving_average
|
24
|
+
|
25
|
+
# Computes the Weighted Moving Average (WMA) of the series, using the seven most recent data points.
|
26
|
+
#
|
27
|
+
# @param source [Symbol] the source of the data points to be used in the calculation.
|
28
|
+
# @return [Float] the weighted average of the series.
|
29
|
+
# @raise [ArgumentError] if the source is not a Symbol.
|
30
|
+
# @example
|
31
|
+
# p0.wma = weighted_average(:close_price)
|
32
|
+
def extended_weighted_moving_average(source)
|
33
|
+
raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
|
34
|
+
|
35
|
+
[7.0 * p0.send(source),
|
36
|
+
6.0 * p1.send(source),
|
37
|
+
5.0 * p2.send(source),
|
38
|
+
4.0 * p3.send(source),
|
39
|
+
3.0 * p(4).send(source),
|
40
|
+
2.0 * p(5).send(source),
|
41
|
+
p(6).send(source)].sum / 28.0
|
42
|
+
end
|
43
|
+
alias ewma extended_weighted_moving_average
|
44
|
+
|
45
|
+
# Computes the Simple Moving Average (SMA) of the given period.
|
46
|
+
#
|
47
|
+
# @param source [Symbol] the source of the data points to be used in the calculation.
|
48
|
+
# @param period [Integer] the number of elements to compute the SMA over.
|
49
|
+
# @return [Float] the simple moving average of the period.
|
50
|
+
def simple_moving_average(source, period:)
|
51
|
+
raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
|
52
|
+
|
53
|
+
values.last(period).map { |value| value.send(source) }.mean
|
54
|
+
end
|
55
|
+
alias sma simple_moving_average
|
56
|
+
|
57
|
+
# Computes the Exponential Moving Average (EMA) of the given period.
|
58
|
+
#
|
59
|
+
# The EMA computation is optimized to compute using just the last two
|
60
|
+
# indicator data points and is expected to be called in each indicator's
|
61
|
+
# `#compute` method for each iteration on the series.
|
62
|
+
#
|
63
|
+
# @param source [Symbol] the source of the data points to be used in the calculation.
|
64
|
+
# @param previous [Symbol] the previous EMA value.
|
65
|
+
# @param period [Integer] the number of elements to compute the EMA over.
|
66
|
+
# @return [Float] the exponential moving average of the period.
|
67
|
+
# @raise [ArgumentError] if the source is not a Symbol.
|
68
|
+
# @example
|
69
|
+
# def compute
|
70
|
+
# p0.ema = exponential_moving_average(:close_price, period: 3)
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# def compute
|
74
|
+
# p0.ema = exponential_moving_average(:close_price, previous: :ema, period: 3)
|
75
|
+
# end
|
76
|
+
def exponential_moving_average(source, previous: :ema, period:)
|
77
|
+
raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
|
78
|
+
raise ArgumentError, "previous must be a Symbol" unless previous.is_a?(Symbol)
|
79
|
+
|
80
|
+
alpha = 2.0 / (period + 1)
|
81
|
+
p0.send(source) * alpha + p1.send(previous) * (1.0 - alpha)
|
82
|
+
end
|
83
|
+
alias ema exponential_moving_average
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -66,9 +66,8 @@ module Quant
|
|
66
66
|
p3 = points[-4] || p2
|
67
67
|
|
68
68
|
v0 = source.is_a?(Symbol) ? p0.send(source) : source
|
69
|
-
return v0 if
|
69
|
+
return v0 if p0 == p3
|
70
70
|
|
71
|
-
debugger if points.size > 4
|
72
71
|
a1 = Math.exp(-Math::PI / ssperiod)
|
73
72
|
b1 = 2 * a1 * Math.cos(Math::PI * Math.sqrt(3) / ssperiod)
|
74
73
|
c1 = a1**2
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Quant
|
2
4
|
module Refinements
|
3
5
|
# Refinements for the standard Ruby {Quant::Array} class.
|
@@ -32,7 +34,6 @@ module Quant
|
|
32
34
|
# The refined behavior generally only exists within the library's scope, but if you call `using Quant` in your
|
33
35
|
# own code, you may encounter the changed behavior unexpectedly.
|
34
36
|
module Array
|
35
|
-
|
36
37
|
# Overrides the standard +<<+ method to track the +maximum+ and +minimum+ values
|
37
38
|
# while also respecting the +max_size+ setting.
|
38
39
|
def <<(value)
|
@@ -109,7 +110,7 @@ module Quant
|
|
109
110
|
subset = last(n)
|
110
111
|
return 0.0 if subset.empty?
|
111
112
|
|
112
|
-
|
113
|
+
subset.sum / subset.size.to_f
|
113
114
|
end
|
114
115
|
|
115
116
|
# Computes the Exponential Moving Average (EMA) of the array. When +n+ is specified,
|
@@ -175,7 +176,7 @@ module Quant
|
|
175
176
|
# @param n [Integer] the number of elements to compute the Standard Deviation over.
|
176
177
|
# @return [Float]
|
177
178
|
def stddev(reference_value, n: size)
|
178
|
-
variance(reference_value, n: n)
|
179
|
+
variance(reference_value, n: n)**0.5
|
179
180
|
end
|
180
181
|
|
181
182
|
def variance(reference_value, n: size)
|
data/lib/quant/settings.rb
CHANGED
data/lib/quant/ticks/ohlc.rb
CHANGED
@@ -89,7 +89,7 @@ module Quant
|
|
89
89
|
# This method is useful for comparing the volatility of different assets.
|
90
90
|
# @return [Float]
|
91
91
|
def daily_price_change_ratio
|
92
|
-
@
|
92
|
+
@daily_price_change_ratio ||= ((open_price - close_price) / oc2).abs
|
93
93
|
end
|
94
94
|
|
95
95
|
# Set the #green? property to true when the close_price is greater than or equal to the open_price.
|
data/lib/quant/version.rb
CHANGED
data/lib/quantitative.rb
CHANGED
@@ -14,4 +14,4 @@ Dir.glob(File.join(quant_folder, "*.rb")).each { |fn| require fn }
|
|
14
14
|
# require sub-folders and their sub-folders
|
15
15
|
%w(refinements mixins settings ticks indicators).each do |sub_folder|
|
16
16
|
Dir.glob(File.join(quant_folder, sub_folder, "**/*.rb")).each { |fn| require fn }
|
17
|
-
end
|
17
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quantitative
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Lang
|
@@ -62,10 +62,10 @@ files:
|
|
62
62
|
- lib/quant/mixins/fisher_transform.rb
|
63
63
|
- lib/quant/mixins/high_pass_filter.rb
|
64
64
|
- lib/quant/mixins/hilbert_transform.rb
|
65
|
+
- lib/quant/mixins/moving_averages.rb
|
65
66
|
- lib/quant/mixins/stochastic.rb
|
66
67
|
- lib/quant/mixins/super_smoother.rb
|
67
68
|
- lib/quant/mixins/trig.rb
|
68
|
-
- lib/quant/mixins/weighted_average.rb
|
69
69
|
- lib/quant/refinements/array.rb
|
70
70
|
- lib/quant/series.rb
|
71
71
|
- lib/quant/settings.rb
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Quant
|
4
|
-
module Mixins
|
5
|
-
module WeightedAverage
|
6
|
-
def weighted_average(source)
|
7
|
-
value = source.is_a?(Symbol) ? p0.send(source) : source
|
8
|
-
[4.0 * value,
|
9
|
-
3.0 * p1.send(source),
|
10
|
-
2.0 * p2.send(source),
|
11
|
-
p3.send(source),].sum / 10.0
|
12
|
-
end
|
13
|
-
|
14
|
-
def extended_weighted_average(source)
|
15
|
-
value = source.is_a?(Symbol) ? p0.send(source) : source
|
16
|
-
[7.0 * value,
|
17
|
-
6.0 * p1.send(source),
|
18
|
-
5.0 * p2.send(source),
|
19
|
-
4.0 * p3.send(source),
|
20
|
-
3.0 * prev(4).send(source),
|
21
|
-
2.0 * prev(5).send(source),
|
22
|
-
prev(6).send(source),].sum / 28.0
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|