quantitative 0.1.4 → 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/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
|