quantitative 0.1.4 → 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/asset.rb +88 -0
- data/lib/quant/{security_class.rb → asset_class.rb} +14 -14
- data/lib/quant/attributes.rb +218 -0
- data/lib/quant/errors.rb +30 -5
- data/lib/quant/indicators/indicator.rb +94 -53
- data/lib/quant/indicators/indicator_point.rb +9 -21
- data/lib/quant/indicators/ma.rb +14 -20
- data/lib/quant/indicators/ping.rb +22 -0
- data/lib/quant/indicators.rb +1 -23
- data/lib/quant/indicators_proxy.rb +61 -0
- data/lib/quant/indicators_sources.rb +18 -0
- data/lib/quant/interval.rb +37 -12
- data/lib/quant/mixins/filters.rb +2 -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 +12 -10
- data/lib/quant/series.rb +36 -19
- data/lib/quant/settings.rb +2 -0
- data/lib/quant/ticks/ohlc.rb +8 -11
- data/lib/quant/ticks/serializers/ohlc.rb +33 -22
- data/lib/quant/ticks/serializers/spot.rb +1 -4
- data/lib/quant/ticks/serializers/tick.rb +3 -3
- data/lib/quant/ticks/spot.rb +7 -7
- data/lib/quant/ticks/tick.rb +22 -9
- data/lib/quant/version.rb +1 -1
- data/lib/quantitative.rb +2 -2
- metadata +9 -5
- data/lib/quant/mixins/weighted_average.rb +0 -26
- data/lib/quant/security.rb +0 -80
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/asset.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "asset_class"
|
4
|
+
|
5
|
+
module Quant
|
6
|
+
# A {Quant::Asset} is a representation of a financial instrument such as a stock, option, future, or currency.
|
7
|
+
# It is used to represent the instrument that is being traded, analyzed, or managed.
|
8
|
+
#
|
9
|
+
# Not all data sources have a rich set of attributes for their assets or securities. The {Quant::Asset} is designed
|
10
|
+
# to be flexible and to support a wide variety of data sources and use-cases. The most common use-cases are supported
|
11
|
+
# while allowing for additional attributes to be added via the +meta+ attribute, which is tyipically just a Hash,
|
12
|
+
# but can be any object that can hold useful information about the asset such as currency formatting, precision, etc.
|
13
|
+
# @example
|
14
|
+
# asset = Quant::Asset.new(symbol: "AAPL", name: "Apple Inc.", asset_class: :stock, exchange: "NASDAQ")
|
15
|
+
# asset.symbol # => "AAPL"
|
16
|
+
# asset.name # => "Apple Inc."
|
17
|
+
# asset.stock? # => true
|
18
|
+
# asset.option? # => false
|
19
|
+
# asset.future? # => false
|
20
|
+
# asset.currency? # => false
|
21
|
+
# asset.exchange # => "NASDAQ"
|
22
|
+
#
|
23
|
+
# # Can serialize two ways:
|
24
|
+
# asset.to_h # => { "s" => "AAPL" }
|
25
|
+
# asset.to_h(full: true) # => { "s" => "AAPL", "n" => "Apple Inc.", "sc" => "stock", "x" => "NASDAQ" }
|
26
|
+
class Asset
|
27
|
+
attr_reader :symbol, :name, :asset_class, :id, :exchange, :source, :meta, :created_at, :updated_at
|
28
|
+
|
29
|
+
def initialize(
|
30
|
+
symbol:,
|
31
|
+
name: nil,
|
32
|
+
id: nil,
|
33
|
+
active: true,
|
34
|
+
tradeable: true,
|
35
|
+
exchange: nil,
|
36
|
+
source: nil,
|
37
|
+
asset_class: nil,
|
38
|
+
created_at: Quant.current_time,
|
39
|
+
updated_at: Quant.current_time,
|
40
|
+
meta: {}
|
41
|
+
)
|
42
|
+
raise ArgumentError, "symbol is required" unless symbol
|
43
|
+
|
44
|
+
@symbol = symbol.to_s.upcase
|
45
|
+
@name = name
|
46
|
+
@id = id
|
47
|
+
@tradeable = tradeable
|
48
|
+
@active = active
|
49
|
+
@exchange = exchange
|
50
|
+
@source = source
|
51
|
+
@asset_class = AssetClass.new(asset_class)
|
52
|
+
@created_at = created_at
|
53
|
+
@updated_at = updated_at
|
54
|
+
@meta = meta
|
55
|
+
end
|
56
|
+
|
57
|
+
def active?
|
58
|
+
!!@active
|
59
|
+
end
|
60
|
+
|
61
|
+
def tradeable?
|
62
|
+
!!@tradeable
|
63
|
+
end
|
64
|
+
|
65
|
+
AssetClass::CLASSES.each do |class_name|
|
66
|
+
define_method("#{class_name}?") do
|
67
|
+
asset_class == class_name
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_h(full: false)
|
72
|
+
return { "s" => symbol } unless full
|
73
|
+
|
74
|
+
{ "s" => symbol,
|
75
|
+
"n" => name,
|
76
|
+
"id" => id,
|
77
|
+
"t" => tradeable?,
|
78
|
+
"a" => active?,
|
79
|
+
"x" => exchange,
|
80
|
+
"sc" => asset_class.to_s,
|
81
|
+
"src" => source.to_s }
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_json(*args, full: false)
|
85
|
+
Oj.dump(to_h(full: full), *args)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -24,7 +24,7 @@ module Quant
|
|
24
24
|
# real estate. Investors can buy shares in a REIT, which provides them with a share of the
|
25
25
|
# income produced by the real estate.
|
26
26
|
|
27
|
-
# Cryptocurrencies: Digital or virtual currencies that use cryptography for
|
27
|
+
# Cryptocurrencies: Digital or virtual currencies that use cryptography for asset and operate on
|
28
28
|
# decentralized networks, typically based on blockchain technology. Examples include Bitcoin, Ethereum, and Ripple.
|
29
29
|
|
30
30
|
# Preferred Stock: A type of stock that has priority over common stock in terms of dividend
|
@@ -40,7 +40,7 @@ module Quant
|
|
40
40
|
|
41
41
|
# Foreign Exchange (Forex): The market where currencies are traded. Investors can buy and sell currencies to
|
42
42
|
# profit from changes in exchange rates.
|
43
|
-
class
|
43
|
+
class AssetClass
|
44
44
|
CLASSES = %i(
|
45
45
|
bond
|
46
46
|
commodity
|
@@ -57,31 +57,31 @@ module Quant
|
|
57
57
|
treasury_note
|
58
58
|
).freeze
|
59
59
|
|
60
|
-
attr_reader :
|
60
|
+
attr_reader :asset_class
|
61
61
|
|
62
62
|
def initialize(name)
|
63
|
-
return if @
|
63
|
+
return if @asset_class = from_standard(name)
|
64
64
|
|
65
|
-
@
|
66
|
-
|
65
|
+
@asset_class = from_alternate(name.to_s.downcase.to_sym) unless name.nil?
|
66
|
+
raise_unknown_asset_class_error(name) unless asset_class
|
67
67
|
end
|
68
68
|
|
69
69
|
CLASSES.each do |class_name|
|
70
70
|
define_method("#{class_name}?") do
|
71
|
-
|
71
|
+
asset_class == class_name
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
-
def
|
76
|
-
raise
|
75
|
+
def raise_unknown_asset_class_error(name)
|
76
|
+
raise Errors::AssetClassError, "Unknown asset class: #{name.inspect}"
|
77
77
|
end
|
78
78
|
|
79
79
|
def to_s
|
80
|
-
|
80
|
+
asset_class.to_s
|
81
81
|
end
|
82
82
|
|
83
83
|
def to_h
|
84
|
-
{ "sc" =>
|
84
|
+
{ "sc" => asset_class }
|
85
85
|
end
|
86
86
|
|
87
87
|
def to_json(*args)
|
@@ -90,9 +90,9 @@ module Quant
|
|
90
90
|
|
91
91
|
def ==(other)
|
92
92
|
case other
|
93
|
-
when String then from_alternate(other.to_sym) ==
|
94
|
-
when Symbol then from_alternate(other) ==
|
95
|
-
when
|
93
|
+
when String then from_alternate(other.to_sym) == asset_class
|
94
|
+
when Symbol then from_alternate(other) == asset_class
|
95
|
+
when AssetClass then other.asset_class == asset_class
|
96
96
|
else
|
97
97
|
false
|
98
98
|
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
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
|
55
|
+
module Attributes
|
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.
|
58
|
+
# Internal use only.
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# { Quant::Indicators::IndicatorPoint => {
|
62
|
+
# tick: { key: nil, default: nil },
|
63
|
+
# source: { key: "src", default: nil },
|
64
|
+
# input: { key: "in", default: nil }
|
65
|
+
# },
|
66
|
+
# Quant::Indicators::PingPoint => {
|
67
|
+
# pong: { key: nil, default: nil },
|
68
|
+
# compute_count: { key: nil, default: 0 }
|
69
|
+
# }
|
70
|
+
# }
|
71
|
+
# @return [Hash] The registry of all defined attributes.
|
72
|
+
def self.registry
|
73
|
+
@registry ||= {}
|
74
|
+
end
|
75
|
+
|
76
|
+
# Removes the given class from the registry. Useful for testing.
|
77
|
+
def self.deregister(klass)
|
78
|
+
registry.delete(klass)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Registers an attribute for a class in the registry.
|
82
|
+
# Internal use only.
|
83
|
+
#
|
84
|
+
# @param klass [Class] The class registering the attribute
|
85
|
+
# @param name [Symbol] The name of the attribute
|
86
|
+
# @param key [String] The key to use when serializing the attribute
|
87
|
+
# @param default [Object] The default value for the attribute
|
88
|
+
def self.register(klass, name, key, default)
|
89
|
+
# Disallow redefining or replacing a key as it is easy to miss the overwrite
|
90
|
+
# and leads to serialization surprises.
|
91
|
+
if key && registry.values.flat_map(&:values).map{ |entry| entry[:key] }.include?(key)
|
92
|
+
raise Errors::DuplicateAttributesKeyError, "Attribute Key #{key} already defined!"
|
93
|
+
end
|
94
|
+
|
95
|
+
registry[klass] ||= {}
|
96
|
+
registry[klass][name] = { key: key, default: default }
|
97
|
+
end
|
98
|
+
|
99
|
+
module InstanceMethods
|
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)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Iterates over all defined attributes in a child => parent hierarchy,
|
115
|
+
# and yields the name and entry for each.
|
116
|
+
def each_attribute(&block)
|
117
|
+
klass = self.class
|
118
|
+
loop do
|
119
|
+
attributes = Attributes.registry[klass]
|
120
|
+
break if attributes.nil?
|
121
|
+
|
122
|
+
attributes.each{ |name, entry| block.call(name, entry) }
|
123
|
+
klass = klass.superclass
|
124
|
+
end
|
125
|
+
end
|
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
|
+
|
152
|
+
# Initializes the defined attributes with default values and
|
153
|
+
# defines accessor methods for each attribute.
|
154
|
+
# If a child class redefines a parent's attribute, the child's
|
155
|
+
# definition will be used.
|
156
|
+
def initialize_attributes(tick:)
|
157
|
+
each_attribute do |name, entry|
|
158
|
+
# use the child's definition, skipping the parent's
|
159
|
+
next if respond_to?(name)
|
160
|
+
|
161
|
+
ivar_name = "@#{name}"
|
162
|
+
instance_variable_set(ivar_name, default_value_for(entry, tick))
|
163
|
+
define_singleton_method(name) { instance_variable_get(ivar_name) }
|
164
|
+
define_singleton_method("#{name}=") { |value| instance_variable_set(ivar_name, value) }
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Serializes keys that have been defined as serializeable attributes
|
169
|
+
# Key values that are nil are removed from the hash
|
170
|
+
# @return [Hash] The serialized attributes as a Ruby Hash.
|
171
|
+
def to_h
|
172
|
+
{}.tap do |key_values|
|
173
|
+
each_attribute do |name, entry|
|
174
|
+
next unless entry[:key]
|
175
|
+
|
176
|
+
ivar_name = "@#{name}"
|
177
|
+
value = instance_variable_get(ivar_name)
|
178
|
+
|
179
|
+
key_values[entry[:key]] = value if value
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Serializes keys that have been defined as serializeable attributes
|
185
|
+
# Key values that are nil are removed from the hash
|
186
|
+
# @return [String] The serialized attributes as a JSON string.
|
187
|
+
def to_json(*args)
|
188
|
+
Oj.dump(to_h, *args)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
module ClassMethods
|
193
|
+
# Define an +attribute+ for the class that can optionally be serialized.
|
194
|
+
# Works much like an attr_accessor does, but also manages serialization for
|
195
|
+
# #to_h and #to_json methods.
|
196
|
+
#
|
197
|
+
# An +attribute+ will result in a same-named instance on the class when
|
198
|
+
# it is instantiated and it will set a default value if one is provided.
|
199
|
+
#
|
200
|
+
# @param name [Symbol] The name of the attribute and it's accessor methods
|
201
|
+
# @param key [String] The key to use when serializing the attribute
|
202
|
+
# @param default [Object] The default value for the attribute
|
203
|
+
#
|
204
|
+
# @examples
|
205
|
+
# attribute :tick # will not serialize to a key
|
206
|
+
# attribute :source, key: "src" # serializes to "src" key
|
207
|
+
# attribute :input, key: "in" # serializes to "in" key
|
208
|
+
def attribute(name, key: nil, default: nil)
|
209
|
+
Attributes.register(self, name, key, default)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def self.included(base) # :nodoc:
|
214
|
+
base.extend(ClassMethods)
|
215
|
+
base.prepend(InstanceMethods)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
data/lib/quant/errors.rb
CHANGED
@@ -1,9 +1,34 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Quant
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
module Errors
|
5
|
+
# {Error} is the base class for all errors in the Quant gem.
|
6
|
+
# It is a subclass of the Ruby {StandardError}.
|
7
|
+
class Error < StandardError; end
|
8
|
+
|
9
|
+
# {InvalidInterval} is raised when attempting to instantiate an
|
10
|
+
# {Quant::Interval} with an invalid value.
|
11
|
+
class InvalidInterval < Error; end
|
12
|
+
|
13
|
+
# {InvalidResolution} is raised when attempting to instantiate
|
14
|
+
# an {Quant::Resolution} with a resolution value that has not been defined.
|
15
|
+
class InvalidResolution < Error; end
|
16
|
+
|
17
|
+
# {ArrayMaxSizeError} is raised when attempting to set the +max_size+ on
|
18
|
+
# the refined {Array} class to an invalid value or when attempting to
|
19
|
+
# redefine the +max_size+ on the refined {Array} class.
|
20
|
+
class ArrayMaxSizeError < Error; end
|
21
|
+
|
22
|
+
# {AssetClassError} is raised when attempting to instantiate a
|
23
|
+
# {Quant::Asset} with an attribute that is not a valid {Quant::Asset} attribute.
|
24
|
+
class AssetClassError < Error; end
|
25
|
+
|
26
|
+
# {DuplicateAttributesKeyError} is raised when attempting to define an
|
27
|
+
# attribute with a key that has already been defined.
|
28
|
+
class DuplicateAttributesKeyError < Error; end
|
29
|
+
|
30
|
+
# {DuplicateAttributesNameError} is raised when attempting to define an
|
31
|
+
# attribute with a name that has already been defined.
|
32
|
+
class DuplicateAttributesNameError < Error; end
|
33
|
+
end
|
9
34
|
end
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Quant
|
2
4
|
class Indicators
|
3
5
|
class Indicator
|
4
|
-
|
6
|
+
include Enumerable
|
5
7
|
|
6
8
|
# # include Mixins::TrendMethods
|
7
9
|
# include Mixins::Trig
|
@@ -14,57 +16,107 @@ module Quant
|
|
14
16
|
# include Mixins::Direction
|
15
17
|
# include Mixins::Filters
|
16
18
|
|
17
|
-
|
18
|
-
# "#<#{self.class.name} #{symbol} #{interval} #{points.size} points>"
|
19
|
-
# end
|
19
|
+
attr_reader :source, :series
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
def initialize(series:, source:)
|
22
|
+
@series = series
|
23
|
+
@source = source
|
24
|
+
@points = {}
|
25
|
+
series.each { |tick| self << tick }
|
26
|
+
end
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
def ticks
|
29
|
+
@points.keys
|
30
|
+
end
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
+
def [](index)
|
33
|
+
values[index]
|
34
|
+
end
|
32
35
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
+
def values
|
37
|
+
@points.values
|
38
|
+
end
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
+
def size
|
41
|
+
@points.size
|
42
|
+
end
|
40
43
|
|
41
|
-
|
42
|
-
|
43
|
-
# end
|
44
|
+
attr_reader :p0, :p1, :p2, :p3
|
45
|
+
attr_reader :t0, :t1, :t2, :t3
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
def <<(tick)
|
48
|
+
@t0 = tick
|
49
|
+
@p0 = points_class.new(tick: tick, source: source)
|
50
|
+
@points[tick] = @p0
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
# delegate :oc2, :high_price, :low_price, :open_price, :close_price, :volume, to: :p0
|
52
|
+
@p1 = values[-2] || @p0
|
53
|
+
@p2 = values[-3] || @p1
|
54
|
+
@p3 = values[-4] || @p2
|
54
55
|
|
55
|
-
|
56
|
-
@
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
56
|
+
@t1 = ticks[-2] || @t0
|
57
|
+
@t2 = ticks[-3] || @t1
|
58
|
+
@t3 = ticks[-4] || @t2
|
59
|
+
|
60
|
+
compute
|
61
|
+
end
|
62
|
+
|
63
|
+
def each(&block)
|
64
|
+
@points.each_value(&block)
|
65
|
+
end
|
66
|
+
|
67
|
+
def inspect
|
68
|
+
"#<#{self.class.name} symbol=#{series.symbol} source=#{source} #{ticks.size} ticks>"
|
66
69
|
end
|
67
70
|
|
71
|
+
def compute
|
72
|
+
raise NotImplementedError
|
73
|
+
end
|
74
|
+
|
75
|
+
def indicator_name
|
76
|
+
self.class.name.split("::").last
|
77
|
+
end
|
78
|
+
|
79
|
+
def points_class
|
80
|
+
Object.const_get "Quant::Indicators::#{indicator_name}Point"
|
81
|
+
end
|
82
|
+
|
83
|
+
# p(0) => values[-1]
|
84
|
+
# p(1) => values[-2]
|
85
|
+
# p(2) => values[-3]
|
86
|
+
# p(3) => values[-4]
|
87
|
+
def p(offset)
|
88
|
+
raise ArgumentError, "offset must be a positive value" if offset < 0
|
89
|
+
|
90
|
+
index = offset + 1
|
91
|
+
values[[-index, -size].max]
|
92
|
+
end
|
93
|
+
|
94
|
+
# t(0) => ticks[-1]
|
95
|
+
# t(1) => ticks[-2]
|
96
|
+
# t(2) => ticks[-3]
|
97
|
+
# t(3) => ticks[-4]
|
98
|
+
def t(offset)
|
99
|
+
raise ArgumentError, "offset must be a positive value" if offset < 0
|
100
|
+
|
101
|
+
index = offset + 1
|
102
|
+
ticks[[-index, -size].max]
|
103
|
+
end
|
104
|
+
|
105
|
+
# The input is the value derived from the source for the indicator
|
106
|
+
# for the current tick.
|
107
|
+
# For example, if the source is :oc2, then the input is the
|
108
|
+
# value of the current tick's (open + close) / 2
|
109
|
+
# @return [Numeric]
|
110
|
+
def input
|
111
|
+
t0.send(source)
|
112
|
+
end
|
113
|
+
|
114
|
+
# def warmed_up?
|
115
|
+
# true
|
116
|
+
# end
|
117
|
+
|
118
|
+
# attr_reader :dc_period
|
119
|
+
|
68
120
|
# def points_for(series:)
|
69
121
|
# @points_for_cache[series] ||= self.class.new(series:, settings:, cloning: true).tap do |indicator|
|
70
122
|
# series.ticks.each { |tick| indicator.points.push(tick.indicators[self]) }
|
@@ -77,10 +129,6 @@ module Quant
|
|
77
129
|
# series.ticks.empty? ? series : series.ticks.first.series
|
78
130
|
# end
|
79
131
|
|
80
|
-
# def after_initialization
|
81
|
-
# # NoOp
|
82
|
-
# end
|
83
|
-
|
84
132
|
# # Returns the last point of the current indicator rather than the entire series
|
85
133
|
# # This is used for indicators that depend on dominant cycle or other indicators
|
86
134
|
# # to compute their data points.
|
@@ -103,13 +151,6 @@ module Quant
|
|
103
151
|
# raise 'Dominant Cycle Indicators cannot use the thing they compute!'
|
104
152
|
# end
|
105
153
|
|
106
|
-
# # Sets the dominant cycle period for the current indicator's point
|
107
|
-
# # @dc_period gets set before each #compute call.
|
108
|
-
# def update_dc_period
|
109
|
-
# ensure_not_dominant_cycler_indicator
|
110
|
-
# @dc_period = current_dominant_cycle.period
|
111
|
-
# end
|
112
|
-
|
113
154
|
# # Returns the dominant cycle point for the current indicator's point
|
114
155
|
# def current_dominant_cycle
|
115
156
|
# dominant_cycle_indicator[current_point]
|
@@ -133,4 +174,4 @@ module Quant
|
|
133
174
|
# end
|
134
175
|
end
|
135
176
|
end
|
136
|
-
end
|
177
|
+
end
|
@@ -3,33 +3,21 @@
|
|
3
3
|
module Quant
|
4
4
|
class Indicators
|
5
5
|
class IndicatorPoint
|
6
|
-
|
6
|
+
include Quant::Attributes
|
7
7
|
|
8
|
-
attr_reader :tick
|
8
|
+
attr_reader :tick
|
9
|
+
attribute :source, key: "src"
|
10
|
+
attribute :input, key: "in"
|
9
11
|
|
10
12
|
def initialize(tick:, source:)
|
11
13
|
@tick = tick
|
12
|
-
@source =
|
14
|
+
@source = source
|
15
|
+
@input = @tick.send(source)
|
16
|
+
initialize_data_points
|
13
17
|
end
|
14
18
|
|
15
|
-
def
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
def timestamp
|
20
|
-
@tick.close_timestamp
|
21
|
-
end
|
22
|
-
|
23
|
-
def initialize_data_points(indicator:)
|
24
|
-
# NoOp
|
25
|
-
end
|
26
|
-
|
27
|
-
def to_h
|
28
|
-
raise NotImplementedError
|
29
|
-
end
|
30
|
-
|
31
|
-
def to_json(*args)
|
32
|
-
Oj.dump(to_h, *args)
|
19
|
+
def initialize_data_points
|
20
|
+
# No-Op - Override in subclass if needed.
|
33
21
|
end
|
34
22
|
end
|
35
23
|
end
|