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 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