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 +4 -4
- data/.rubocop.yml +2 -0
- data/Gemfile.lock +1 -1
- data/lib/quant/asset.rb +88 -0
- data/lib/quant/{security_class.rb → asset_class.rb} +14 -14
- data/lib/quant/attributes.rb +218 -0
- data/lib/quant/errors.rb +30 -5
- data/lib/quant/indicators/indicator.rb +94 -53
- data/lib/quant/indicators/indicator_point.rb +9 -21
- data/lib/quant/indicators/ma.rb +14 -20
- data/lib/quant/indicators/ping.rb +22 -0
- data/lib/quant/indicators.rb +1 -23
- data/lib/quant/indicators_proxy.rb +61 -0
- data/lib/quant/indicators_sources.rb +18 -0
- data/lib/quant/interval.rb +37 -12
- data/lib/quant/mixins/filters.rb +2 -0
- data/lib/quant/mixins/moving_averages.rb +86 -0
- data/lib/quant/mixins/super_smoother.rb +1 -2
- data/lib/quant/refinements/array.rb +12 -10
- data/lib/quant/series.rb +36 -19
- data/lib/quant/settings.rb +2 -0
- data/lib/quant/ticks/ohlc.rb +8 -11
- data/lib/quant/ticks/serializers/ohlc.rb +33 -22
- data/lib/quant/ticks/serializers/spot.rb +1 -4
- data/lib/quant/ticks/serializers/tick.rb +3 -3
- data/lib/quant/ticks/spot.rb +7 -7
- data/lib/quant/ticks/tick.rb +22 -9
- data/lib/quant/version.rb +1 -1
- data/lib/quantitative.rb +2 -2
- metadata +9 -5
- data/lib/quant/mixins/weighted_average.rb +0 -26
- data/lib/quant/security.rb +0 -80
@@ -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
|
data/lib/quant/ticks/tick.rb
CHANGED
@@ -2,12 +2,12 @@
|
|
2
2
|
|
3
3
|
module Quant
|
4
4
|
module Ticks
|
5
|
-
#
|
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
|
10
|
-
# once a
|
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
|
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:
|
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
|
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)
|
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
|
79
|
+
@interval ||= new_series.interval
|
67
80
|
self
|
68
81
|
end
|
69
82
|
|
data/lib/quant/version.rb
CHANGED
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
|
-
end
|
17
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quantitative
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Lang
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oj
|
@@ -44,25 +44,29 @@ files:
|
|
44
44
|
- LICENSE
|
45
45
|
- README.md
|
46
46
|
- Rakefile
|
47
|
+
- lib/quant/asset.rb
|
48
|
+
- lib/quant/asset_class.rb
|
49
|
+
- lib/quant/attributes.rb
|
47
50
|
- lib/quant/config.rb
|
48
51
|
- lib/quant/errors.rb
|
49
52
|
- lib/quant/indicators.rb
|
50
53
|
- lib/quant/indicators/indicator.rb
|
51
54
|
- lib/quant/indicators/indicator_point.rb
|
52
55
|
- lib/quant/indicators/ma.rb
|
56
|
+
- lib/quant/indicators/ping.rb
|
57
|
+
- lib/quant/indicators_proxy.rb
|
58
|
+
- lib/quant/indicators_sources.rb
|
53
59
|
- lib/quant/interval.rb
|
54
60
|
- lib/quant/mixins/direction.rb
|
55
61
|
- lib/quant/mixins/filters.rb
|
56
62
|
- lib/quant/mixins/fisher_transform.rb
|
57
63
|
- lib/quant/mixins/high_pass_filter.rb
|
58
64
|
- lib/quant/mixins/hilbert_transform.rb
|
65
|
+
- lib/quant/mixins/moving_averages.rb
|
59
66
|
- lib/quant/mixins/stochastic.rb
|
60
67
|
- lib/quant/mixins/super_smoother.rb
|
61
68
|
- lib/quant/mixins/trig.rb
|
62
|
-
- lib/quant/mixins/weighted_average.rb
|
63
69
|
- lib/quant/refinements/array.rb
|
64
|
-
- lib/quant/security.rb
|
65
|
-
- lib/quant/security_class.rb
|
66
70
|
- lib/quant/series.rb
|
67
71
|
- lib/quant/settings.rb
|
68
72
|
- lib/quant/settings/indicators.rb
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Quant
|
4
|
-
module Mixins
|
5
|
-
module WeightedAverage
|
6
|
-
def weighted_average(source)
|
7
|
-
value = source.is_a?(Symbol) ? p0.send(source) : source
|
8
|
-
[4.0 * value,
|
9
|
-
3.0 * p1.send(source),
|
10
|
-
2.0 * p2.send(source),
|
11
|
-
p3.send(source),].sum / 10.0
|
12
|
-
end
|
13
|
-
|
14
|
-
def extended_weighted_average(source)
|
15
|
-
value = source.is_a?(Symbol) ? p0.send(source) : source
|
16
|
-
[7.0 * value,
|
17
|
-
6.0 * p1.send(source),
|
18
|
-
5.0 * p2.send(source),
|
19
|
-
4.0 * p3.send(source),
|
20
|
-
3.0 * prev(4).send(source),
|
21
|
-
2.0 * prev(5).send(source),
|
22
|
-
prev(6).send(source),].sum / 28.0
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
data/lib/quant/security.rb
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "security_class"
|
4
|
-
|
5
|
-
module Quant
|
6
|
-
# A +Security+ 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
|
-
# @example
|
9
|
-
# security = Quant::Security.new(symbol: "AAPL", name: "Apple Inc.", security_class: :stock, exchange: "NASDAQ")
|
10
|
-
# security.symbol # => "AAPL"
|
11
|
-
# security.name # => "Apple Inc."
|
12
|
-
# security.stock? # => true
|
13
|
-
# security.option? # => false
|
14
|
-
# security.future? # => false
|
15
|
-
# security.currency? # => false
|
16
|
-
# security.exchange # => "NASDAQ"
|
17
|
-
# security.to_h # => { "s" => "AAPL", "n" => "Apple Inc.", "sc" => "stock", "x" => "NASDAQ" }
|
18
|
-
class Security
|
19
|
-
attr_reader :symbol, :name, :security_class, :id, :exchange, :source, :meta, :created_at, :updated_at
|
20
|
-
|
21
|
-
def initialize(
|
22
|
-
symbol:,
|
23
|
-
name: nil,
|
24
|
-
id: nil,
|
25
|
-
active: true,
|
26
|
-
tradeable: true,
|
27
|
-
exchange: nil,
|
28
|
-
source: nil,
|
29
|
-
security_class: nil,
|
30
|
-
created_at: Quant.current_time,
|
31
|
-
updated_at: Quant.current_time,
|
32
|
-
meta: {}
|
33
|
-
)
|
34
|
-
raise ArgumentError, "symbol is required" unless symbol
|
35
|
-
|
36
|
-
@symbol = symbol.to_s.upcase
|
37
|
-
@name = name
|
38
|
-
@id = id
|
39
|
-
@tradeable = tradeable
|
40
|
-
@active = active
|
41
|
-
@exchange = exchange
|
42
|
-
@source = source
|
43
|
-
@security_class = SecurityClass.new(security_class)
|
44
|
-
@created_at = created_at
|
45
|
-
@updated_at = updated_at
|
46
|
-
@meta = meta
|
47
|
-
end
|
48
|
-
|
49
|
-
def active?
|
50
|
-
!!@active
|
51
|
-
end
|
52
|
-
|
53
|
-
def tradeable?
|
54
|
-
!!@tradeable
|
55
|
-
end
|
56
|
-
|
57
|
-
SecurityClass::CLASSES.each do |class_name|
|
58
|
-
define_method("#{class_name}?") do
|
59
|
-
security_class == class_name
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def to_h(full: false)
|
64
|
-
return { "s" => symbol } unless full
|
65
|
-
|
66
|
-
{ "s" => symbol,
|
67
|
-
"n" => name,
|
68
|
-
"id" => id,
|
69
|
-
"t" => tradeable?,
|
70
|
-
"a" => active?,
|
71
|
-
"x" => exchange,
|
72
|
-
"sc" => security_class.to_s,
|
73
|
-
"src" => source.to_s }
|
74
|
-
end
|
75
|
-
|
76
|
-
def to_json(*args, full: false)
|
77
|
-
Oj.dump(to_h(full: full), *args)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|