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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 266a7aa51d29b809224fa76091d26c52d58f6e42959aeeb4ca5ffb6376828773
4
- data.tar.gz: 5b3ac92706338a77a098e5dee72f3ecf57f2c4032f946533225e6061d07082d5
3
+ metadata.gz: 72d65b65fdcbb81650fcce3233f3fbde9c99c4b9b9ace1ce3bbc3b8e565152f9
4
+ data.tar.gz: f687ab3c32e88ffc579a9f92ff428846f91e60ff166d3b4379963deb15a88425
5
5
  SHA512:
6
- metadata.gz: 624d6945e690d51b5afef5ebc7939eb3f444fd7d4d79a31e73df5a42e0f6c351f8d4ac451a455a657c96a07448a0c724ba1481156d2e859ca720c8994328547a
7
- data.tar.gz: 51b9fd16424fe1fd1b48c72f350c89cfb9e26e0a082eb750ae864c436b51eeab2142c64744deee93b91aaafe119354f19a4159ecda84b066d75f1d242945a9a2
6
+ metadata.gz: 594d57a819ce8d561c064f685353d27ef4dfd1cb2e76e5cf07758bcedb1c501d2bc06859aff89cf6f4bc744e327ede1290e31b4d4ffb1daed99d614735eb0a76
7
+ data.tar.gz: 5fbfb29d1501a3562788abf83ef4fcb78bcb17fcfc030e7543b42f3c29c2a7a5c3c8fba187494d4655691e117e383af59bca428764f49fd7e41fcef911ef6d40
data/.rubocop.yml CHANGED
@@ -3,6 +3,8 @@ inherit_gem:
3
3
 
4
4
  AllCops:
5
5
  TargetRubyVersion: 3.0
6
+ Exclude:
7
+ - 'spec/performance/*.rb'
6
8
 
7
9
  Style/AccessorGrouping:
8
10
  Enabled: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- quantitative (0.1.4)
4
+ quantitative (0.1.6)
5
5
  oj (~> 3.10)
6
6
 
7
7
  GEM
@@ -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 security and operate on
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 SecurityClass
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 :security_class
60
+ attr_reader :asset_class
61
61
 
62
62
  def initialize(name)
63
- return if @security_class = from_standard(name)
63
+ return if @asset_class = from_standard(name)
64
64
 
65
- @security_class = from_alternate(name.to_s.downcase.to_sym) unless name.nil?
66
- raise_unknown_security_class_error(name) unless security_class
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
- security_class == class_name
71
+ asset_class == class_name
72
72
  end
73
73
  end
74
74
 
75
- def raise_unknown_security_class_error(name)
76
- raise SecurityClassError, "Unknown security class: #{name.inspect}"
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
- security_class.to_s
80
+ asset_class.to_s
81
81
  end
82
82
 
83
83
  def to_h
84
- { "sc" => security_class }
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) == security_class
94
- when Symbol then from_alternate(other) == security_class
95
- when SecurityClass then other.security_class == security_class
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
- class Error < StandardError; end
5
- class InvalidInterval < Error; end
6
- class InvalidResolution < Error; end
7
- class ArrayMaxSizeError < Error; end
8
- class SecurityClassError < Error; end
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
- # include Enumerable
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
- # def inspect
18
- # "#<#{self.class.name} #{symbol} #{interval} #{points.size} points>"
19
- # end
19
+ attr_reader :source, :series
20
20
 
21
- # def compute
22
- # raise NotImplementedError
23
- # end
21
+ def initialize(series:, source:)
22
+ @series = series
23
+ @source = source
24
+ @points = {}
25
+ series.each { |tick| self << tick }
26
+ end
24
27
 
25
- # def [](index)
26
- # points[index]
27
- # end
28
+ def ticks
29
+ @points.keys
30
+ end
28
31
 
29
- # def after_append
30
- # # NoOp
31
- # end
32
+ def [](index)
33
+ values[index]
34
+ end
32
35
 
33
- # def points_class
34
- # "Quant::Indicators::#{indicator_name}Point".constantize
35
- # end
36
+ def values
37
+ @points.values
38
+ end
36
39
 
37
- # def indicator_name
38
- # self.class.name.demodulize
39
- # end
40
+ def size
41
+ @points.size
42
+ end
40
43
 
41
- # def warmed_up?
42
- # true
43
- # end
44
+ attr_reader :p0, :p1, :p2, :p3
45
+ attr_reader :t0, :t1, :t2, :t3
44
46
 
45
- # def initial_max_size
46
- # value = [series.size, series.max_size].max
47
- # value.zero? ? settings.initial_max_size : value
48
- # end
47
+ def <<(tick)
48
+ @t0 = tick
49
+ @p0 = points_class.new(tick: tick, source: source)
50
+ @points[tick] = @p0
49
51
 
50
- attr_reader :series #, :settings, :max_size, :points, :dc_period
51
- # delegate :p0, :p1, :p2, :p3, :prev, :iteration, to: :points
52
- # delegate :each, :size, :[], :last, :first, to: :points
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
- def initialize(series:) # settings: Settings::Indicators.defaults, cloning: false)
56
- @series = series
57
- # @settings = settings
58
- # @max_size = initial_max_size
59
- # @points = Points.new(indicator: self)
60
- # return if cloning
61
-
62
- # after_initialization
63
- # parent_series.each { |ohlc| append ohlc }
64
- # @points_for_cache = {}
65
- # @dc_period = nil
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
- extend Forwardable
6
+ include Quant::Attributes
7
7
 
8
- attr_reader :tick, :source
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 = @tick.send(source)
14
+ @source = source
15
+ @input = @tick.send(source)
16
+ initialize_data_points
13
17
  end
14
18
 
15
- def volume
16
- @tick.base_volume
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