quantitative 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []