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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +25 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/DISCLAIMER.txt +14 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +126 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +53 -0
- data/Rakefile +12 -0
- data/lib/quant/errors.rb +7 -0
- data/lib/quant/interval.rb +226 -0
- data/lib/quant/mixins/direction.rb +45 -0
- data/lib/quant/mixins/filters.rb +112 -0
- data/lib/quant/mixins/fisher_transform.rb +38 -0
- data/lib/quant/mixins/high_pass_filter.rb +54 -0
- data/lib/quant/mixins/hilbert_transform.rb +14 -0
- data/lib/quant/mixins/stochastic.rb +30 -0
- data/lib/quant/mixins/super_smoother.rb +133 -0
- data/lib/quant/mixins/trig.rb +43 -0
- data/lib/quant/mixins/weighted_average.rb +26 -0
- data/lib/quant/ticks/core.rb +8 -0
- data/lib/quant/ticks/indicator_points.rb +22 -0
- data/lib/quant/ticks/ohlc.rb +152 -0
- data/lib/quant/ticks/serializers/value.rb +23 -0
- data/lib/quant/ticks/spot.rb +33 -0
- data/lib/quant/ticks/value.rb +88 -0
- data/lib/quant/time_methods.rb +53 -0
- data/lib/quant/time_period.rb +78 -0
- data/lib/quant/version.rb +5 -0
- data/lib/quantitative.rb +17 -0
- metadata +149 -0
@@ -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
|
data/lib/quantitative.rb
ADDED
@@ -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: []
|