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