quantitative 0.1.0

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,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ module Ticks
5
+ # Value ticks are the most basic ticks and are used to represent a single price point (no open, high, low, close, etc.)
6
+ # and a single timestamp. Usually, these are best used in streaming data where ticks are flowing in every second or whatever
7
+ # interval that's appropriate for the data source.
8
+ # Often indicators and charts still want a universal public interface (i.e. open_price, high_price, volume, etc.), so we add
9
+ # those methods here and inherit and redefine upstream as appropriate.
10
+ #
11
+ # For Value ticks:
12
+ # * The +price+ given is set for all *_price fields.
13
+ # * The +volume+ is set for both base and target volume.
14
+ # * The +timestamp+ is set for both open and close timestamps.
15
+ class Value
16
+ include TimeMethods
17
+
18
+ attr_reader :interval, :series
19
+ attr_reader :close_timestamp, :open_timestamp
20
+ attr_reader :open_price, :high_price, :low_price, :close_price
21
+ attr_reader :base_volume, :target_volume, :trades
22
+ attr_reader :green, :doji
23
+
24
+ def initialize(price:, timestamp: Quant.current_time, interval: nil, volume: 0, trades: 0)
25
+ @interval = Interval[interval]
26
+
27
+ @close_timestamp = extract_time(timestamp)
28
+ @open_timestamp = @close_timestamp
29
+
30
+ @close_price = price.to_f
31
+ @open_price = close_price
32
+ @high_price = close_price
33
+ @low_price = close_price
34
+
35
+ @base_volume = volume.to_i
36
+ @target_volume = volume.to_i
37
+ @trades = trades.to_i
38
+
39
+ # Set the series by appending to the series
40
+ @series = nil
41
+ end
42
+
43
+ alias oc2 close_price
44
+ alias hl2 close_price
45
+ alias hlc3 close_price
46
+ alias ohlc4 close_price
47
+ alias delta close_price
48
+ alias volume base_volume
49
+
50
+ def corresponding?(other)
51
+ close_timestamp == other.close_timestamp
52
+ end
53
+
54
+ def ==(other)
55
+ to_h == other.to_h
56
+ end
57
+
58
+ # ticks are immutable across series so we can easily initialize sub-sets or new series
59
+ # with the same ticks while allowing each series to have its own state and full
60
+ # control over the ticks within its series
61
+ def assign_series(new_series)
62
+ assign_series!(new_series) if @series.nil?
63
+
64
+ # dup.tap do |new_tick|
65
+ # # new_tick.instance_variable_set(:@series, new_series)
66
+ # new_tick.instance_variable_set(:@indicators, indicators)
67
+ # end
68
+ end
69
+
70
+ def assign_series!(new_series)
71
+ @series = new_series
72
+ self
73
+ end
74
+
75
+ def inspect
76
+ "#<#{self.class.name} iv=#{interval} ct=#{close_timestamp.strftime("%Y-%m-%d")} o=#{open_price} c=#{close_price} v=#{volume}>"
77
+ end
78
+
79
+ def to_h
80
+ Quant::Ticks::Serializers::Value.to_h(self)
81
+ end
82
+
83
+ def to_json(*_args)
84
+ Quant::Ticks::Serializers::Value.to_json(self)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ module_function
5
+
6
+ # The library is designed to work with UTC time. This method provides a single point of
7
+ # access for the current time. This is useful for testing and for retrieving the current time in
8
+ # one place for the entire library.
9
+ def current_time
10
+ Time.now.utc
11
+ end
12
+
13
+ # This method, similar to +current_time+, provides a single point of access for the current date.
14
+ def current_date
15
+ Date.today
16
+ end
17
+
18
+ module TimeMethods
19
+ # Provides lower-bounds for dates and times. See +Quant::TimePeriod+ for example use-case.
20
+ EPOCH_DATE = Date.civil(1492, 10, 12).freeze # arbitrary! (blame #co-pilot)
21
+ EPOCH_TIME = Time.new(EPOCH_DATE.year, EPOCH_DATE.month, EPOCH_DATE.day, 0, 0, 0, "+00:00").utc.freeze
22
+
23
+ # The epoch date is a NULL object +Date+ for the library. It is used to represent the
24
+ # beginning of time. That is, a date that is without bound and helps avoid +nil+ checks,
25
+ # +NULL+ database entries, and such when working with dates.
26
+ def self.epoch_date
27
+ EPOCH_DATE
28
+ end
29
+
30
+ # The epoch time is a NULL object +Time+ for the library. It is used to represent the
31
+ # beginning of time. That is, a time that is without bound and helps avoid +nil+ checks,
32
+ # +NULL+ database entries, and such when working with time.
33
+ def self.epoch_time
34
+ EPOCH_TIME
35
+ end
36
+
37
+ # When streaming or extracting a time entry from a payload, Time can already be parsed into a +Time+ object.
38
+ # Or it may be an +Integer+ representing the number of seconds since the epoch. Or it may be a +String+ that
39
+ # can be parsed into a +Time+ object. This method normalizes the time into a +Time+ object on the UTC timezone.
40
+ def extract_time(value)
41
+ case value
42
+ when Time
43
+ value.utc
44
+ when Integer
45
+ Time.at(value).utc
46
+ when String
47
+ Time.parse(value).utc
48
+ else
49
+ raise ArgumentError, "Invalid time: #{value.inspect}"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ class TimePeriod
5
+ LOWER_BOUND = TimeMethods::EPOCH_TIME
6
+
7
+ def initialize(start_at: nil, end_at: nil, span: nil)
8
+ @start_at = as_start_time(start_at)
9
+ @end_at = as_end_time(end_at)
10
+ validate_bounds!
11
+
12
+ @start_at = @end_at - span if !lower_bound? && span
13
+ @end_at = @start_at + span if !upper_bound? && span
14
+ end
15
+
16
+ def as_start_time(value)
17
+ return value if value.nil? || value.is_a?(Time)
18
+
19
+ value.is_a?(Date) ? value.to_time.beginning_of_day : value.to_time
20
+ end
21
+
22
+ def as_end_time(value)
23
+ return value if value.nil? || value.is_a?(Time)
24
+
25
+ value.is_a?(Date) ? value.to_time.end_of_day : value.to_time
26
+ end
27
+
28
+ def validate_bounds!
29
+ return if lower_bound? || upper_bound?
30
+
31
+ raise "TimePeriod cannot be unbounded at start_at and end_at"
32
+ end
33
+
34
+ def cover?(value)
35
+ (start_at..end_at).cover?(value)
36
+ end
37
+
38
+ def start_at
39
+ (@start_at || LOWER_BOUND).round
40
+ end
41
+
42
+ def lower_bound?
43
+ !!@start_at
44
+ end
45
+
46
+ def upper_bound?
47
+ !!@end_at
48
+ end
49
+
50
+ def end_at
51
+ (@end_at || Time.now.utc).round
52
+ end
53
+
54
+ def duration
55
+ end_at - start_at
56
+ end
57
+
58
+ def ==(other)
59
+ return false unless other.is_a?(TimePeriod)
60
+
61
+ if lower_bound?
62
+ other.lower_bound? && start_at == other.start_at
63
+ elsif upper_bound?
64
+ oher.upper_bound? && end_at == other.end_at
65
+ else
66
+ [start_at, end_at] == [other.start_at, other.end_at]
67
+ end
68
+ end
69
+
70
+ def eql?(other)
71
+ self == other
72
+ end
73
+
74
+ def to_h
75
+ { start_at: start_at, end_at: end_at }
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require "date"
5
+ require "oj"
6
+
7
+ lib_folder = File.expand_path(File.join(File.dirname(__FILE__)))
8
+ quant_folder = File.join(lib_folder, "quant")
9
+
10
+ # require top-level files
11
+ Dir.glob(File.join(quant_folder, "*.rb")).each { |fn| require fn }
12
+
13
+ # require sub-folders and their sub-folders
14
+ %w(ticks).each do |sub_folder|
15
+ Dir.glob(File.join(quant_folder, sub_folder, "*.rb")).each { |fn| require fn }
16
+ Dir.glob(File.join(quant_folder, sub_folder, "**/*.rb")).each { |fn| require fn }
17
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quantitative
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Lang
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-02-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: oj
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: debug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: guard-rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ description: Quantitative and statistical tools written for Ruby 3.x for trading and
84
+ finance.
85
+ email:
86
+ - mwlang@cybrains.net
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".rspec"
92
+ - ".rubocop.yml"
93
+ - CODE_OF_CONDUCT.md
94
+ - DISCLAIMER.txt
95
+ - Gemfile
96
+ - Gemfile.lock
97
+ - Guardfile
98
+ - LICENSE.txt
99
+ - README.md
100
+ - Rakefile
101
+ - lib/quant/errors.rb
102
+ - lib/quant/interval.rb
103
+ - lib/quant/mixins/direction.rb
104
+ - lib/quant/mixins/filters.rb
105
+ - lib/quant/mixins/fisher_transform.rb
106
+ - lib/quant/mixins/high_pass_filter.rb
107
+ - lib/quant/mixins/hilbert_transform.rb
108
+ - lib/quant/mixins/stochastic.rb
109
+ - lib/quant/mixins/super_smoother.rb
110
+ - lib/quant/mixins/trig.rb
111
+ - lib/quant/mixins/weighted_average.rb
112
+ - lib/quant/ticks/core.rb
113
+ - lib/quant/ticks/indicator_points.rb
114
+ - lib/quant/ticks/ohlc.rb
115
+ - lib/quant/ticks/serializers/value.rb
116
+ - lib/quant/ticks/spot.rb
117
+ - lib/quant/ticks/value.rb
118
+ - lib/quant/time_methods.rb
119
+ - lib/quant/time_period.rb
120
+ - lib/quant/version.rb
121
+ - lib/quantitative.rb
122
+ homepage: https://github.com/mwlang/quantitative
123
+ licenses:
124
+ - MIT
125
+ metadata:
126
+ allowed_push_host: https://rubygems.org
127
+ homepage_uri: https://github.com/mwlang/quantitative
128
+ source_code_uri: https://github.com/mwlang/quantitative
129
+ changelog_uri: https://github.com/mwlang/quantitative
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: 3.0.0
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubygems_version: 3.3.7
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: Quantitative and statistical tools written for Ruby 3.x for trading and finance.
149
+ test_files: []