quantitative 0.1.3 → 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.
- checksums.yaml +4 -4
- 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 +134 -0
- data/lib/quant/config.rb +32 -0
- data/lib/quant/errors.rb +30 -5
- data/lib/quant/indicators/indicator.rb +171 -0
- data/lib/quant/indicators/indicator_point.rb +24 -0
- data/lib/quant/indicators/ma.rb +38 -0
- data/lib/quant/indicators/ping.rb +16 -0
- data/lib/quant/indicators.rb +7 -0
- data/lib/quant/indicators_proxy.rb +23 -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/refinements/array.rb +8 -7
- data/lib/quant/series.rb +36 -19
- data/lib/quant/settings/indicators.rb +78 -0
- data/lib/quant/settings.rb +48 -0
- data/lib/quant/ticks/ohlc.rb +7 -10
- 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 +15 -4
- data/lib/quant/security.rb +0 -80
data/lib/quant/interval.rb
CHANGED
@@ -39,13 +39,13 @@
|
|
39
39
|
# pp series
|
40
40
|
#
|
41
41
|
module Quant
|
42
|
-
#
|
43
|
-
# working with multiple timeframes. Intervals are used in
|
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
|
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
|
-
#
|
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
|
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
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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)
|
data/lib/quant/mixins/filters.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Quant
|
2
2
|
module Refinements
|
3
|
-
# Refinements for the standard Ruby
|
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
|
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
|
93
|
-
#
|
94
|
-
|
95
|
-
raise
|
96
|
-
raise
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
20
|
-
|
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
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
module Settings
|
5
|
+
# Indicator settings provide a way to configure the default settings for indicators.
|
6
|
+
# Many of the indicators are built in adaptive measuring of the dominant cycle and these settings
|
7
|
+
# provide a way to configure your choices for the indicators. The default values come from various
|
8
|
+
# papers and books on the subject of technical analysis by John Ehlers where he variously suggests
|
9
|
+
# a minimum period of 8 or 10 and a max period of 48.
|
10
|
+
#
|
11
|
+
# The half period is the average of the max_period and min_period.
|
12
|
+
# The micro period comes from Ehler's writings on Swami charts and auto-correlation computations, which
|
13
|
+
# is a period of 3 bars. It is useful enough in various indicators to be its own setting.
|
14
|
+
#
|
15
|
+
# The dominant cycle kind is the kind of dominant cycle to use in the indicator. The default is +:settings+
|
16
|
+
# which means the dominant cycle is whatever the +max_period+ is set to. It is not adaptive when configured
|
17
|
+
# this way. The other kinds are adaptive and are computed from the series data. The choices are:
|
18
|
+
# * +:settings+ - the max_period is the dominant cycle and is not adaptive
|
19
|
+
# * +:band_pass+ - The zero crossings of the band pass filter are used to compute the dominant cycle
|
20
|
+
# * +:auto_correlation_reversal+ - The dominant cycle is computed from the auto-correlation of the series.
|
21
|
+
# * +:homodyne+ - The dominant cycle is computed from the homodyne discriminator.
|
22
|
+
# * +:differential+ - The dominant cycle is computed from the differential discriminator.
|
23
|
+
# * +:phase_accumulator+ - The dominant cycle is computed from the phase accumulator.
|
24
|
+
#
|
25
|
+
# All of the above are adaptive and are computed from the series data and are described in John Ehlers' books
|
26
|
+
# and published papers.
|
27
|
+
#
|
28
|
+
# Pivot kinds are started as the classic pivot points and then expanded to include other kinds of bands that
|
29
|
+
# follow along with price action such as Donchian channels, Fibonacci bands, Bollinger bands, Keltner bands,
|
30
|
+
# etc. The choices are as follows:
|
31
|
+
# * +:pivot+ - Classic pivot points
|
32
|
+
# * +:donchian+ - Donchian channels
|
33
|
+
# * +:fibbonacci+ - Fibonacci bands
|
34
|
+
# * +:woodie+ - Woodie's pivot points
|
35
|
+
# * +:classic+ - Classic pivot points
|
36
|
+
# * +:camarilla+ - Camarilla pivot points
|
37
|
+
# * +:demark+ - Demark pivot points
|
38
|
+
# * +:murrey+ - Murrey math pivot points
|
39
|
+
# * +:keltner+ - Keltner bands
|
40
|
+
# * +:bollinger+ - Bollinger bands
|
41
|
+
# * +:guppy+ - Guppy bands
|
42
|
+
# * +:atr+ - ATR bands
|
43
|
+
#
|
44
|
+
class Indicators
|
45
|
+
# Returns an instance of the settings for indicators configured with defaults derived from
|
46
|
+
# defined constants in the +Quant::Settings+ module.
|
47
|
+
def self.defaults
|
48
|
+
new
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_accessor :max_period, :min_period, :half_period, :micro_period
|
52
|
+
attr_accessor :dominant_cycle_kind, :pivot_kind
|
53
|
+
|
54
|
+
def initialize(**settings)
|
55
|
+
@max_period = settings[:max_period] || Settings::MAX_PERIOD
|
56
|
+
@min_period = settings[:min_period] || Settings::MIN_PERIOD
|
57
|
+
@half_period = settings[:half_period] || compute_half_period
|
58
|
+
@micro_period = settings[:micro_period] || Settings::MICRO_PERIOD
|
59
|
+
|
60
|
+
@dominant_cycle_kind = settings[:dominant_cycle_kind] || Settings::DOMINANT_CYCLE_KINDS.first
|
61
|
+
@pivot_kind = settings[:pivot_kind] || Settings::PIVOT_KINDS.first
|
62
|
+
end
|
63
|
+
|
64
|
+
def apply_settings(**settings)
|
65
|
+
@max_period = settings.fetch(:max_period, @max_period)
|
66
|
+
@min_period = settings.fetch(:min_period, @min_period)
|
67
|
+
@half_period = settings.fetch(:half_period, @half_period || compute_half_period)
|
68
|
+
@micro_period = settings.fetch(:micro_period, @micro_period)
|
69
|
+
@dominant_cycle_kind = settings.fetch(:dominant_cycle_kind, @dominant_cycle_kind)
|
70
|
+
@pivot_kind = settings.fetch(:pivot_kind, @pivot_kind)
|
71
|
+
end
|
72
|
+
|
73
|
+
def compute_half_period
|
74
|
+
(max_period + min_period) / 2
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Quant
|
2
|
+
module Settings
|
3
|
+
MAX_PERIOD = 48
|
4
|
+
MIN_PERIOD = 10
|
5
|
+
HALF_PERIOD = (MAX_PERIOD + MIN_PERIOD) / 2
|
6
|
+
MICRO_PERIOD = 3
|
7
|
+
|
8
|
+
PIVOT_KINDS = %i(
|
9
|
+
pivot
|
10
|
+
donchian
|
11
|
+
fibbonacci
|
12
|
+
woodie
|
13
|
+
classic
|
14
|
+
camarilla
|
15
|
+
demark
|
16
|
+
murrey
|
17
|
+
keltner
|
18
|
+
bollinger
|
19
|
+
guppy
|
20
|
+
atr
|
21
|
+
).freeze
|
22
|
+
|
23
|
+
DOMINANT_CYCLE_KINDS = %i(
|
24
|
+
settings
|
25
|
+
band_pass
|
26
|
+
auto_correlation_reversal
|
27
|
+
homodyne
|
28
|
+
differential
|
29
|
+
phase_accumulator
|
30
|
+
).freeze
|
31
|
+
|
32
|
+
# ---- Risk Management Ratio Settings ----
|
33
|
+
# Risk Reward Breakeven Win Rate %
|
34
|
+
# 50 1 98%
|
35
|
+
# 10 1 91%
|
36
|
+
# 5 1 83%
|
37
|
+
# 3 1 75%
|
38
|
+
# 2 1 67%
|
39
|
+
# 1 1 50%
|
40
|
+
# 1 2 33%
|
41
|
+
# 1 3 25%
|
42
|
+
# 1 5 17%
|
43
|
+
# 1 10 9%
|
44
|
+
# 1 50 2%
|
45
|
+
PROFIT_TARGET_PCT = 0.03
|
46
|
+
STOP_LOSS_PCT = 0.01
|
47
|
+
end
|
48
|
+
end
|
data/lib/quant/ticks/ohlc.rb
CHANGED
@@ -4,14 +4,15 @@ require_relative "tick"
|
|
4
4
|
|
5
5
|
module Quant
|
6
6
|
module Ticks
|
7
|
-
# An
|
8
|
-
# It is the most common form of a
|
9
|
-
# minute, hour, day, week, or month. The
|
10
|
-
# The interval of the
|
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 :
|
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}
|
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], "
|
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, "
|
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
|
7
|
-
# These classes are wired into the Tick classes and are used to convert
|
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
|
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
|
data/lib/quant/ticks/spot.rb
CHANGED
@@ -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
|
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 :
|
22
|
+
attr_reader :series
|
23
23
|
attr_reader :close_timestamp, :open_timestamp
|
24
|
-
attr_reader :
|
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}
|
76
|
+
"#<#{self.class.name} ct=#{close_timestamp} c=#{close_price.to_f} v=#{volume}>"
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|