quantitative 0.1.4 → 0.1.5

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.
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ class IndicatorsProxy
5
+ attr_reader :series, :source, :indicators
6
+
7
+ def initialize(series:, source:)
8
+ @series = series
9
+ @source = source
10
+ @indicators = {}
11
+ end
12
+
13
+ def indicator(indicator_class)
14
+ indicators[indicator_class] ||= indicator_class.new(series: series, source: source)
15
+ end
16
+
17
+ def <<(tick)
18
+ indicators.each_value { |indicator| indicator << tick }
19
+ end
20
+
21
+ def ma; indicator(Indicators::Ma) end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ class IndicatorsSources
5
+ def initialize(series:)
6
+ @series = series
7
+ @indicator_sources = {}
8
+ end
9
+
10
+ def <<(tick)
11
+ @indicator_sources.each_value { |indicator| indicator << tick }
12
+ end
13
+
14
+ def oc2
15
+ @indicator_sources[:oc2] ||= IndicatorsProxy.new(series: @series, source: :oc2)
16
+ end
17
+ end
18
+ end
@@ -39,13 +39,13 @@
39
39
  # pp series
40
40
  #
41
41
  module Quant
42
- # +Quant::Interval+ abstracts away the concept of ticks (candles, bars, etc.) and their duration and offers some basic utilities for
43
- # working with multiple timeframes. Intervals are used in +Tick+ and +Series+ classes to define the duration of the ticks.
42
+ # {Quant::Interval} abstracts away the concept of ticks (candles, bars, etc.) and their duration and offers some basic utilities for
43
+ # working with multiple timeframes. Intervals are used in {Quant::Ticks::Tick} and {Quant::Series} classes to define the duration of the ticks.
44
44
  #
45
- # When the +Interval+ is unknown, it is set to +'na'+ (not available) and the duration is set to 0. The shorthand for this is
45
+ # When the {Quant::Interval} is unknown, it is set to +'na'+ (not available) and the duration is set to 0. The shorthand for this is
46
46
  # +Interval.na+. and +Interval[:na]+. and +Interval[nil]+.
47
47
  #
48
- # +Interval+ are instantiated in multple ways to support a wide variety of use-cases. Here's an example:
48
+ # {Quant::Interval} are instantiated in multple ways to support a wide variety of use-cases. Here's an example:
49
49
  # Quant::Interval.new("1d") # => #<Quant::Interval @interval="1d"> (daily interval)
50
50
  # Quant::Interval.new(:daily) # => #<Quant::Interval @interval="1d">
51
51
  # Quant::Interval[:daily] # => #<Quant::Interval @interval="1d">
@@ -122,7 +122,7 @@ module Quant
122
122
 
123
123
  # Instantiates an Interval from a resolution. For example, TradingView uses resolutions
124
124
  # like "1", "3", "5", "15", "30", "60", "240", "D", "1D" to represent the duration of a
125
- # candlestick. +from_resolution+ translates resolutions to the appropriate +Interval+.
125
+ # candlestick. +from_resolution+ translates resolutions to the appropriate {Quant::Interval}.
126
126
  def self.from_resolution(resolution)
127
127
  ensure_valid_resolution!(resolution)
128
128
 
@@ -130,7 +130,7 @@ module Quant
130
130
  end
131
131
 
132
132
  # Instantiates an Interval from a string or symbol. If the value is already
133
- # an +Interval+, it is returned as-is.
133
+ # an {Quant::Interval}, it is returned as-is.
134
134
  def self.[](value)
135
135
  return value if value.is_a? Interval
136
136
 
@@ -153,19 +153,28 @@ module Quant
153
153
  @interval = (interval || "na").to_s
154
154
  end
155
155
 
156
+ # Returns true when the duration of the interval is zero, such as for the `na` interval.
156
157
  def nil?
157
- interval == "na"
158
+ duration.zero?
158
159
  end
159
160
 
160
161
  def to_s
161
162
  interval
162
163
  end
163
164
 
165
+ # Returns the total span of seconds or duration for the interval.
166
+ # @example
167
+ # Quant::Interval.new("1d").duration => 86400
168
+ # Quant::Interval.new("1h").duration => 3600
169
+ # Quant::Interval.new("1m").duration => 60
170
+ # Quant::Interval.new("1s").duration => 1
171
+ # Quant::Interval.new("na").duration => 0
164
172
  def duration
165
173
  INTERVAL_DISTANCE[interval]
166
174
  end
167
175
  alias seconds duration
168
176
 
177
+ # Compares the interval to another interval, string, or symbol and returns true if they are equal.
169
178
  def ==(other)
170
179
  if other.is_a? String
171
180
  interval.to_s == other
@@ -176,13 +185,22 @@ module Quant
176
185
  end
177
186
  end
178
187
 
188
+ # Returns the number of ticks this interval represents per minute.
189
+ # @example
190
+ # Quant::Interval.new("1d").ticks_per_minute => 0.0006944444444444445
191
+ # Quant::Interval.new("1h").ticks_per_minute => 0.016666666666666666
192
+ # Quant::Interval.new("1m").ticks_per_minute => 1.0
193
+ # Quant::Interval.new("1s").ticks_per_minute => 60.0
179
194
  def ticks_per_minute
180
195
  60.0 / seconds
181
196
  end
182
197
 
198
+ # Returns the half-life of the interval in seconds.
199
+ # @example
200
+ # Quant::Interval.new("1d").half_life => 43200.0
201
+ # Quant::Interval.new("1h").half_life => 1800.0
202
+ # Quant::Interval.new("1m").half_life => 30.0
183
203
  def half_life
184
- raise "bad interval #{interval}" if duration.nil?
185
-
186
204
  duration / 2.0
187
205
  end
188
206
 
@@ -193,11 +211,16 @@ module Quant
193
211
  Interval.new intervals[intervals.index(interval) + 1] || intervals[-1]
194
212
  end
195
213
 
214
+ # Returns the full list of valid interval Strings that can be used to instantiate an {Quant::Interval}.
196
215
  def self.valid_intervals
197
216
  INTERVAL_DISTANCE.keys
198
217
  end
199
218
 
200
- # NOTE: if timestamp doesn't cover a full interval, it will be rounded up to 1
219
+ # Computes the number of ticks from present to given timestamp.
220
+ # If timestamp doesn't cover a full interval, it will be rounded up to 1
221
+ # @example
222
+ # interval = Quant::Interval.new("1d")
223
+ # interval.ticks_to(Time.now + 5.days) # => 5 NOTE: `5.days` is an ActiveSupport method
201
224
  def ticks_to(timestamp)
202
225
  ((timestamp - Quant.current_time) / duration).round(2).ceil
203
226
  end
@@ -209,7 +232,8 @@ module Quant
209
232
  def self.ensure_valid_resolution!(resolution)
210
233
  return if RESOLUTIONS.keys.include? resolution
211
234
 
212
- raise InvalidResolution, "resolution (#{resolution}) not a valid resolution. Should be one of: (#{RESOLUTIONS.keys.join(", ")})"
235
+ should_be_one_of = "Should be one of: (#{RESOLUTIONS.keys.join(", ")})"
236
+ raise Errors::InvalidResolution, "resolution (#{resolution}) not a valid resolution. #{should_be_one_of}"
213
237
  end
214
238
 
215
239
  private
@@ -221,7 +245,8 @@ module Quant
221
245
  def ensure_valid_interval!(interval)
222
246
  return if interval.nil? || valid_intervals.include?(interval.to_s)
223
247
 
224
- raise InvalidInterval, "interval (#{interval.inspect}) not a valid interval. Should be one of: (#{valid_intervals.join(", ")})"
248
+ should_be_one_of = "Should be one of: (#{valid_intervals.join(", ")})"
249
+ raise Errors::InvalidInterval, "interval (#{interval.inspect}) not a valid interval. #{should_be_one_of}"
225
250
  end
226
251
 
227
252
  def ensure_valid_resolution!(resolution)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "trig"
4
+
3
5
  module Quant
4
6
  module Mixins
5
7
  # 1. All the common filters useful for traders have a transfer response that can be written
@@ -1,6 +1,6 @@
1
1
  module Quant
2
2
  module Refinements
3
- # Refinements for the standard Ruby +Array+ class.
3
+ # Refinements for the standard Ruby {Quant::Array} class.
4
4
  # These refinements add statistical methods to the Array class as well as some optimizations that greatly
5
5
  # speed up some of the computations performed by the various indicators.
6
6
  #
@@ -67,7 +67,7 @@ module Quant
67
67
  end
68
68
 
69
69
  # Treats the tail of the array as starting at zero and counting up. Does not overflow the head of the array.
70
- # That is, if the +Array+ has 5 elements, prev(10) would return the first element in the array.
70
+ # That is, if the {Quant::Array} has 5 elements, prev(10) would return the first element in the array.
71
71
  #
72
72
  # @example
73
73
  # series = [1, 2, 3, 4]
@@ -89,11 +89,12 @@ module Quant
89
89
  # +max_size+, the first element is removed from the array.
90
90
  # This setting modifies :<< and :push methods.
91
91
  def max_size!(max_size)
92
- # These guards are maybe not necessary, but they are here until a use-case is found.
93
- # My concern lies with indicators that are built specifically against the +max_size+ of a given array.
94
- raise Quant::ArrayMaxSizeError, 'cannot set max_size to nil.' unless max_size
95
- raise Quant::ArrayMaxSizeError, 'can only max_size! once.' if @max_size
96
- raise Quant::ArrayMaxSizeError, "size of Array #{size} exceeds max_size #{max_size}." if size > max_size
92
+ # These guards are maybe unnecessary, but they are here until a use-case is found.
93
+ # Some indicators are built specifically against the +max_size+ of a given array.
94
+ # Adjusting the +max_size+ after the fact could lead to unexpected, unintended behavior.
95
+ raise Errors::ArrayMaxSizeError, "Cannot set max_size to nil." unless max_size
96
+ raise Errors::ArrayMaxSizeError, "The max_size can only be set once." if @max_size
97
+ raise Errors::ArrayMaxSizeError, "The size of Array #{size} exceeds max_size #{max_size}." if size > max_size
97
98
 
98
99
  @max_size = max_size
99
100
  self
data/lib/quant/series.rb CHANGED
@@ -3,34 +3,47 @@
3
3
  module Quant
4
4
  # Ticks belong to the first series they're associated with always.
5
5
  # There are no provisions for series merging their ticks to one series!
6
- # Indicators will be computed against the parent series of a list of ticks, so we
6
+ # {Indicators} will be computed against the parent series of a list of ticks, so we
7
7
  # can safely work with subsets of a series and indicators will compute just once.
8
8
  class Series
9
9
  include Enumerable
10
10
  extend Forwardable
11
11
 
12
- def self.from_file(filename:, symbol:, interval:, folder: nil)
13
- symbol = symbol.to_s.upcase
14
- interval = Interval[interval]
15
-
16
- filename = Rails.root.join("historical", folder, "#{symbol.upcase}.txt") if filename.nil?
12
+ # Loads a series of ticks when each line is a parsible JSON string that represents a tick.
13
+ # A {Quant::Ticks::TickSerializer} may be passed to convert the parsed JSON to {Quant::Ticks::Tick} object.
14
+ # @param filename [String] The filename to load the ticks from.
15
+ # @param symbol [String] The symbol of the series.
16
+ # @param interval [String] The interval of the series.
17
+ # @param serializer_class [Class] {Quant::Ticks::TickSerializer} class to use for the conversion.
18
+ def self.from_file(filename:, symbol:, interval:, serializer_class: nil)
17
19
  raise "File #{filename} does not exist" unless File.exist?(filename)
18
20
 
19
- lines = File.read(filename).split("\n")
20
- ticks = lines.map{ |line| Quant::Ticks::OHLC.from_json(line) }
21
-
22
- from_ticks(symbol: symbol, interval: interval, ticks: ticks)
21
+ ticks = File.read(filename).split("\n").map{ |line| Oj.load(line) }
22
+ from_hash symbol: symbol, interval: interval, hash: ticks, serializer_class: serializer_class
23
23
  end
24
24
 
25
- def self.from_json(symbol:, interval:, json:)
26
- from_hash symbol: symbol, interval: interval, hash: Oj.load(json)
25
+ # Loads a series of ticks when the JSON string represents an array of ticks.
26
+ # A {Quant::Ticks::TickSerializer} may be passed to convert the parsed JSON to {Quant::Ticks::Tick} object.
27
+ # @param symbol [String] The symbol of the series.
28
+ # @param interval [String] The interval of the series.
29
+ # @param json [String] The JSON string to parse into ticks.
30
+ # @param serializer_class [Class] {Quant::Ticks::TickSerializer} class to use for the conversion.
31
+ def self.from_json(symbol:, interval:, json:, serializer_class: nil)
32
+ ticks = Oj.load(json)
33
+ from_hash symbol: symbol, interval: interval, hash: ticks, serializer_class: serializer_class
27
34
  end
28
35
 
29
- def self.from_hash(symbol:, interval:, hash:)
30
- ticks = hash.map { |tick_hash| Quant::Ticks::OHLC.from(tick_hash) }
31
- from_ticks(symbol: symbol, interval: interval, ticks: ticks)
36
+ # Loads a series of ticks where the hash must be cast to an array of {Quant::Ticks::Tick} objects.
37
+ # @param symbol [String] The symbol of the series.
38
+ # @param interval [String] The interval of the series.
39
+ # @param hash [Array<Hash>] The array of hashes to convert to {Quant::Ticks::Tick} objects.
40
+ # @param serializer_class [Class] {Quant::Ticks::TickSerializer} class to use for the conversion.
41
+ def self.from_hash(symbol:, interval:, hash:, serializer_class: nil)
42
+ ticks = hash.map { |tick_hash| Quant::Ticks::OHLC.from(tick_hash, serializer_class: serializer_class) }
43
+ from_ticks symbol: symbol, interval: interval, ticks: ticks
32
44
  end
33
45
 
46
+ # Loads a series of ticks where the array represents an array of {Quant::Ticks::Tick} objects.
34
47
  def self.from_ticks(symbol:, interval:, ticks:)
35
48
  ticks = ticks.sort_by(&:close_timestamp)
36
49
 
@@ -43,7 +56,7 @@ module Quant
43
56
 
44
57
  def initialize(symbol:, interval:)
45
58
  @symbol = symbol
46
- @interval = interval
59
+ @interval = Interval[interval]
47
60
  @ticks = []
48
61
  end
49
62
 
@@ -64,11 +77,8 @@ module Quant
64
77
  def_delegator :@ticks, :[]
65
78
  def_delegator :@ticks, :size
66
79
  def_delegator :@ticks, :each
67
- def_delegator :@ticks, :select
68
80
  def_delegator :@ticks, :select!
69
- def_delegator :@ticks, :reject
70
81
  def_delegator :@ticks, :reject!
71
- def_delegator :@ticks, :first
72
82
  def_delegator :@ticks, :last
73
83
 
74
84
  def highest
@@ -92,7 +102,14 @@ module Quant
92
102
  end
93
103
 
94
104
  def <<(tick)
105
+ tick = Ticks::Spot.new(price: tick) if tick.is_a?(Numeric)
106
+ indicators << tick unless tick.series?
95
107
  @ticks << tick.assign_series(self)
108
+ self
109
+ end
110
+
111
+ def indicators
112
+ @indicators ||= IndicatorsSources.new(series: self)
96
113
  end
97
114
 
98
115
  def to_h
@@ -4,14 +4,15 @@ require_relative "tick"
4
4
 
5
5
  module Quant
6
6
  module Ticks
7
- # An +OHLC+ is a bar or candle for a point in time that has an open, high, low, and close price.
8
- # It is the most common form of a +Tick+ and is usually used to representa time period such as a
9
- # minute, hour, day, week, or month. The +OHLC+ is used to represent the price action of an asset
10
- # The interval of the +OHLC+ is the time period that the +OHLC+ represents, such has hourly, daily, weekly, etc.
7
+ # An {Quant::Ticks::OHLC} is a bar or candle for a point in time that has an open, high, low, and close price.
8
+ # It is the most common form of a {Quant::Ticks::Tick} and is usually used to representa time period such as a
9
+ # minute, hour, day, week, or month. The {Quant::Ticks::OHLC} is used to represent the price action of an asset
10
+ # The interval of the {Quant::Ticks::OHLC} is the time period that the {Quant::Ticks::OHLC} represents,
11
+ # such has hourly, daily, weekly, etc.
11
12
  class OHLC < Tick
12
13
  include TimeMethods
13
14
 
14
- attr_reader :interval, :series
15
+ attr_reader :series
15
16
  attr_reader :close_timestamp, :open_timestamp
16
17
  attr_reader :open_price, :high_price, :low_price, :close_price
17
18
  attr_reader :base_volume, :target_volume, :trades
@@ -26,8 +27,6 @@ module Quant
26
27
  low_price:,
27
28
  close_price:,
28
29
 
29
- interval: nil,
30
-
31
30
  volume: nil,
32
31
  base_volume: nil,
33
32
  target_volume: nil,
@@ -44,8 +43,6 @@ module Quant
44
43
  @low_price = low_price.to_f
45
44
  @close_price = close_price.to_f
46
45
 
47
- @interval = Interval[interval]
48
-
49
46
  @base_volume = (volume || base_volume).to_i
50
47
  @target_volume = (target_volume || @base_volume).to_i
51
48
  @trades = trades.to_i
@@ -131,7 +128,7 @@ module Quant
131
128
  end
132
129
 
133
130
  def inspect
134
- "#<#{self.class.name} #{interval} ct=#{close_timestamp.iso8601} o=#{open_price} h=#{high_price} l=#{low_price} c=#{close_price} v=#{volume}>"
131
+ "#<#{self.class.name} ct=#{close_timestamp.iso8601} o=#{open_price} h=#{high_price} l=#{low_price} c=#{close_price} v=#{volume}>"
135
132
  end
136
133
  end
137
134
  end
@@ -16,13 +16,44 @@ module Quant
16
16
  from(hash, tick_class: tick_class)
17
17
  end
18
18
 
19
+ # Instantiates a tick from a +Hash+. The hash keys are expected to be the same as the serialized keys.
20
+ #
21
+ # Serialized Keys:
22
+ # - ot: open timestamp
23
+ # - ct: close timestamp
24
+ # - o: open price
25
+ # - h: high price
26
+ # - l: low price
27
+ # - c: close price
28
+ # - bv: base volume
29
+ # - tv: target volume
30
+ # - t: trades
31
+ # - g: green
32
+ # - j: doji
33
+ def self.from(hash, tick_class:)
34
+ tick_class.new \
35
+ open_timestamp: hash["ot"],
36
+ close_timestamp: hash["ct"],
37
+
38
+ open_price: hash["o"],
39
+ high_price: hash["h"],
40
+ low_price: hash["l"],
41
+ close_price: hash["c"],
42
+
43
+ base_volume: hash["bv"],
44
+ target_volume: hash["tv"],
45
+
46
+ trades: hash["t"],
47
+ green: hash["g"],
48
+ doji: hash["j"]
49
+ end
50
+
19
51
  # Returns a +Hash+ of the Spot tick's key properties
20
52
  #
21
53
  # Serialized Keys:
22
54
  #
23
55
  # - ot: open timestamp
24
56
  # - ct: close timestamp
25
- # - iv: interval
26
57
  # - o: open price
27
58
  # - h: high price
28
59
  # - l: low price
@@ -37,12 +68,11 @@ module Quant
37
68
  # @return [Hash]
38
69
  # @example
39
70
  # Quant::Ticks::Serializers::Tick.to_h(tick)
40
- # # => { "ot" => [Time], "ct" => [Time], "iv" => "1m", "o" => 1.0, "h" => 2.0,
71
+ # # => { "ot" => [Time], "ct" => [Time], "o" => 1.0, "h" => 2.0,
41
72
  # # "l" => 0.5, "c" => 1.5, "bv" => 6.0, "tv" => 5.0, "t" => 1, "g" => true, "j" => true }
42
73
  def self.to_h(tick)
43
74
  { "ot" => tick.open_timestamp,
44
75
  "ct" => tick.close_timestamp,
45
- "iv" => tick.interval.to_s,
46
76
 
47
77
  "o" => tick.open_price,
48
78
  "h" => tick.high_price,
@@ -56,25 +86,6 @@ module Quant
56
86
  "g" => tick.green,
57
87
  "j" => tick.doji }
58
88
  end
59
-
60
- def self.from(hash, tick_class:)
61
- tick_class.new \
62
- open_timestamp: hash["ot"],
63
- close_timestamp: hash["ct"],
64
- interval: hash["iv"],
65
-
66
- open_price: hash["o"],
67
- high_price: hash["h"],
68
- low_price: hash["l"],
69
- close_price: hash["c"],
70
-
71
- base_volume: hash["bv"],
72
- target_volume: hash["tv"],
73
-
74
- trades: hash["t"],
75
- green: hash["g"],
76
- doji: hash["j"]
77
- end
78
89
  end
79
90
  end
80
91
  end
@@ -21,7 +21,6 @@ module Quant
21
21
  # Serialized Keys:
22
22
  #
23
23
  # - ct: close timestamp
24
- # - iv: interval
25
24
  # - cp: close price
26
25
  # - bv: base volume
27
26
  # - tv: target volume
@@ -31,11 +30,10 @@ module Quant
31
30
  # @return [Hash]
32
31
  # @example
33
32
  # Quant::Ticks::Serializers::Tick.to_h(tick)
34
- # # => { "ct" => "2024-02-13 03:12:23 UTC", "cp" => 5.0, "iv" => "1d", "bv" => 0.0, "tv" => 0.0, "t" => 0 }
33
+ # # => { "ct" => "2024-02-13 03:12:23 UTC", "cp" => 5.0, "bv" => 0.0, "tv" => 0.0, "t" => 0 }
35
34
  def self.to_h(tick)
36
35
  { "ct" => tick.close_timestamp,
37
36
  "cp" => tick.close_price,
38
- "iv" => tick.interval.to_s,
39
37
  "bv" => tick.base_volume,
40
38
  "tv" => tick.target_volume,
41
39
  "t" => tick.trades }
@@ -45,7 +43,6 @@ module Quant
45
43
  tick_class.new(
46
44
  close_timestamp: hash["ct"],
47
45
  close_price: hash["cp"],
48
- interval: hash["iv"],
49
46
  base_volume: hash["bv"],
50
47
  target_volume: hash["tv"],
51
48
  trades: hash["t"]
@@ -3,10 +3,10 @@
3
3
  module Quant
4
4
  module Ticks
5
5
  module Serializers
6
- # The +Tick+ class serializes and deserializes +Tick+ objects to and from various formats.
7
- # These classes are wired into the Tick classes and are used to convert +Tick+ objects to and from
6
+ # The {Quant::Ticks::Tick} class serializes and deserializes {Quant::Ticks::Tick} objects to and from various formats.
7
+ # These classes are wired into the Tick classes and are used to convert {Quant::Ticks::Tick} objects to and from
8
8
  # Ruby hashes, JSON strings, and CSV strings. They're not typically used directly, but are extracted
9
- # to make it easier to provide custom serialization and deserialization for +Tick+ objects not shipped
9
+ # to make it easier to provide custom serialization and deserialization for {Quant::Ticks::Tick} objects not shipped
10
10
  # with the library.
11
11
  # @abstract
12
12
  class Tick
@@ -4,7 +4,7 @@ require_relative "tick"
4
4
 
5
5
  module Quant
6
6
  module Ticks
7
- # A +Spot+ is a single price point in time. It is the most basic form of a +Tick+ and is usually used to represent
7
+ # A +Spot+ is a single price point in time. It is the most basic form of a {Quant::Ticks::Tick} and is usually used to represent
8
8
  # a continuously streaming tick that just has a single price point at a given point in time.
9
9
  # @example
10
10
  # spot = Quant::Ticks::Spot.new(price: 100.0, timestamp: Time.now)
@@ -19,9 +19,9 @@ module Quant
19
19
  class Spot < Tick
20
20
  include TimeMethods
21
21
 
22
- attr_reader :interval, :series
22
+ attr_reader :series
23
23
  attr_reader :close_timestamp, :open_timestamp
24
- attr_reader :open_price, :high_price, :low_price, :close_price
24
+ attr_reader :close_price
25
25
  attr_reader :base_volume, :target_volume, :trades
26
26
 
27
27
  def initialize(
@@ -30,7 +30,6 @@ module Quant
30
30
  close_price: nil,
31
31
  close_timestamp: nil,
32
32
  volume: nil,
33
- interval: nil,
34
33
  base_volume: nil,
35
34
  target_volume: nil,
36
35
  trades: nil
@@ -39,8 +38,6 @@ module Quant
39
38
 
40
39
  @close_price = (close_price || price).to_f
41
40
 
42
- @interval = Interval[interval]
43
-
44
41
  @close_timestamp = extract_time(timestamp || close_timestamp || Quant.current_time)
45
42
  @open_timestamp = @close_timestamp
46
43
 
@@ -53,6 +50,9 @@ module Quant
53
50
 
54
51
  alias timestamp close_timestamp
55
52
  alias price close_price
53
+ alias high_price close_price
54
+ alias low_price close_price
55
+ alias open_price close_price
56
56
  alias oc2 close_price
57
57
  alias hl2 close_price
58
58
  alias hlc3 close_price
@@ -73,7 +73,7 @@ module Quant
73
73
  end
74
74
 
75
75
  def inspect
76
- "#<#{self.class.name} #{interval} ct=#{close_timestamp} c=#{close_price.to_f} v=#{volume}>"
76
+ "#<#{self.class.name} ct=#{close_timestamp} c=#{close_price.to_f} v=#{volume}>"
77
77
  end
78
78
  end
79
79
  end
@@ -2,12 +2,12 @@
2
2
 
3
3
  module Quant
4
4
  module Ticks
5
- # +Tick+ is the abstract ancestor for all Ticks and holds the logic for interacting with series and indicators.
5
+ # {Quant::Ticks::Tick} is the abstract ancestor for all Ticks and holds the logic for interacting with series and indicators.
6
6
  # The public interface is devoid of properties around price, volume, and timestamp, etc. Descendant classes
7
7
  # are responsible for defining the properties and how they are represented.
8
8
  #
9
- # The +Tick+ class is designed to be immutable and is intended to be used as a value object. This means that
10
- # once a +Tick+ is created, it cannot be changed. This is important for the integrity of the series and
9
+ # The {Quant::Ticks::Tick} class is designed to be immutable and is intended to be used as a value object. This means that
10
+ # once a {Quant::Ticks::Tick} is created, it cannot be changed. This is important for the integrity of the series and
11
11
  # indicators that depend on the ticks within the series.
12
12
  #
13
13
  # When a tick is added to a series, it is locked into the series and ownership cannot be changed. This is important
@@ -17,7 +17,7 @@ module Quant
17
17
  #
18
18
  # Ticks can be serialized to and from Ruby Hash, JSON strings, and CSV strings.
19
19
  class Tick
20
- # Returns a +Tick+ from a Ruby +Hash+. The default serializer is used to generate the +Tick+.
20
+ # Returns a {Quant::Ticks::Tick} from a Ruby +Hash+. The default serializer is used to generate the {Quant::Ticks::Tick}.
21
21
  # @param hash [Hash]
22
22
  # @param serializer_class [Class] The serializer class to use for the conversion.
23
23
  # @return [Quant::Ticks::Tick]
@@ -25,11 +25,12 @@ module Quant
25
25
  # hash = { "timestamp" => "2018-01-01 12:00:00 UTC", "price" => 100.0, "volume" => 1000 }
26
26
  # Quant::Ticks::Tick.from(hash)
27
27
  # # => #<Quant::Ticks::Spot:0x00007f9e3b8b3e08 @timestamp=2018-01-01 12:00:00 UTC, @price=100.0, @volume=1000>
28
- def self.from(hash, serializer_class: default_serializer_class)
28
+ def self.from(hash, serializer_class: nil)
29
+ serializer_class ||= default_serializer_class
29
30
  serializer_class.from(hash, tick_class: self)
30
31
  end
31
32
 
32
- # Returns a +Tick+ from a JSON string. The default serializer is used to generate the +Tick+.
33
+ # Returns a {Quant::Ticks::Tick} from a JSON string. The default serializer is used to generate the {Quant::Ticks::Tick}.
33
34
  # @param json [String]
34
35
  # @param serializer_class [Class] The serializer class to use for the conversion.
35
36
  # @return [Quant::Ticks::Tick]
@@ -41,21 +42,33 @@ module Quant
41
42
  serializer_class.from_json(json, tick_class: self)
42
43
  end
43
44
 
44
- attr_reader :series
45
+ attr_reader :series, :indicators
45
46
 
46
47
  def initialize
47
48
  # Set the series by appending to the series or calling #assign_series method
48
49
  @series = nil
50
+ @interval = nil
51
+ @indicators = {}
52
+ end
53
+
54
+ def interval
55
+ @series&.interval || Interval[nil]
49
56
  end
50
57
 
51
58
  # Ticks always belong to the first series they're assigned so we can easily spin off
52
59
  # sub-sets or new series with the same ticks while allowing each series to have
53
60
  # its own state and full control over the ticks within its series
54
61
  def assign_series(new_series)
55
- assign_series!(new_series) if @series.nil?
62
+ assign_series!(new_series) unless series?
56
63
  self
57
64
  end
58
65
 
66
+ # Returns true if the tick is assigned to a series. The first series a tick is assigned
67
+ # to is the series against which the indicators compute.
68
+ def series?
69
+ !!@series
70
+ end
71
+
59
72
  # Ticks always belong to the first series they're assigned so we can easily spin off
60
73
  # sub-sets or new series with the same ticks. However, if you need to reassign the
61
74
  # series, you can use this method to force the change of series ownership.
@@ -63,7 +76,7 @@ module Quant
63
76
  # The series interval is also assigned to the tick if it is not already set.
64
77
  def assign_series!(new_series)
65
78
  @series = new_series
66
- @interval = new_series.interval if @interval.nil?
79
+ @interval ||= new_series.interval
67
80
  self
68
81
  end
69
82
 
data/lib/quant/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quant
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.5"
5
5
  end
data/lib/quantitative.rb CHANGED
@@ -12,6 +12,6 @@ quant_folder = File.join(lib_folder, "quant")
12
12
  Dir.glob(File.join(quant_folder, "*.rb")).each { |fn| require fn }
13
13
 
14
14
  # require sub-folders and their sub-folders
15
- %w(refinements settings ticks indicators).each do |sub_folder|
15
+ %w(refinements mixins settings ticks indicators).each do |sub_folder|
16
16
  Dir.glob(File.join(quant_folder, sub_folder, "**/*.rb")).each { |fn| require fn }
17
17
  end