quantitative 0.1.7 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/Gemfile +4 -0
- data/Gemfile.lock +17 -1
- data/README.md +14 -2
- data/Rakefile +29 -0
- data/lib/quant/asset.rb +1 -1
- data/lib/quant/attributes.rb +2 -2
- data/lib/quant/indicators/indicator.rb +2 -1
- data/lib/quant/indicators/indicator_point.rb +1 -0
- data/lib/quant/indicators_proxy.rb +1 -1
- data/lib/quant/mixins/butterworth_filters.rb +48 -0
- data/lib/quant/mixins/exponential_moving_average.rb +1 -1
- data/lib/quant/mixins/filters.rb +1 -51
- data/lib/quant/mixins/fisher_transform.rb +27 -18
- data/lib/quant/mixins/hilbert_transform.rb +3 -3
- data/lib/quant/mixins/stochastic.rb +15 -19
- data/lib/quant/mixins/super_smoother.rb +1 -1
- data/lib/quant/mixins/weighted_moving_average.rb +2 -2
- data/lib/quant/refinements/array.rb +1 -1
- data/lib/quant/series.rb +9 -9
- data/lib/quant/ticks/serializers/ohlc.rb +2 -2
- data/lib/quant/ticks/serializers/spot.rb +1 -1
- data/lib/quant/ticks/serializers/tick.rb +1 -1
- data/lib/quant/ticks/tick.rb +1 -1
- data/lib/quant/time_period.rb +11 -3
- data/lib/quant/version.rb +1 -1
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7ddfcfd200564c3ce9764a7ba635635c56649c6681ac6d76b93390734da4fc9
|
4
|
+
data.tar.gz: 04d93778a91c74ee4c3459009f4990644121974b77079d79a3816c5f63a63f6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8facaa3efaef73c00f98f03b4482b876cf9a6155f46fb56f1c0d3534c996b6c33fefa067ee07ef8f326e76dfbdd41b0fa7d04f349eef49add179444bbe0d9db6
|
7
|
+
data.tar.gz: 3fa7bb3d2e34cd3f2ebd718105ab20ffb60e044c45fb5c3909a14cdd1d2876288ae23d4eb7c4211f35890ddd250772068f1c0e0463db71d1c84c490a06edc2d8
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
quantitative (0.1.
|
4
|
+
quantitative (0.1.9)
|
5
5
|
oj (~> 3.10)
|
6
6
|
|
7
7
|
GEM
|
@@ -15,6 +15,7 @@ GEM
|
|
15
15
|
irb (>= 1.5.0)
|
16
16
|
reline (>= 0.3.1)
|
17
17
|
diff-lcs (1.5.0)
|
18
|
+
docile (1.4.0)
|
18
19
|
ffi (1.16.3)
|
19
20
|
formatador (1.1.0)
|
20
21
|
guard (2.18.1)
|
@@ -79,6 +80,8 @@ GEM
|
|
79
80
|
rspec-expectations (3.12.3)
|
80
81
|
diff-lcs (>= 1.2.0, < 2.0)
|
81
82
|
rspec-support (~> 3.12.0)
|
83
|
+
rspec-github (2.4.0)
|
84
|
+
rspec-core (~> 3.0)
|
82
85
|
rspec-mocks (3.12.6)
|
83
86
|
diff-lcs (>= 1.2.0, < 2.0)
|
84
87
|
rspec-support (~> 3.12.0)
|
@@ -106,6 +109,15 @@ GEM
|
|
106
109
|
rubocop-factory_bot (~> 2.22)
|
107
110
|
ruby-progressbar (1.13.0)
|
108
111
|
shellany (0.0.1)
|
112
|
+
simplecov (0.22.0)
|
113
|
+
docile (~> 1.1)
|
114
|
+
simplecov-html (~> 0.11)
|
115
|
+
simplecov_json_formatter (~> 0.1)
|
116
|
+
simplecov-cobertura (2.1.0)
|
117
|
+
rexml
|
118
|
+
simplecov (~> 0.19)
|
119
|
+
simplecov-html (0.12.3)
|
120
|
+
simplecov_json_formatter (0.1.4)
|
109
121
|
stringio (3.1.0)
|
110
122
|
thor (1.3.0)
|
111
123
|
unicode-display_width (2.5.0)
|
@@ -113,6 +125,7 @@ GEM
|
|
113
125
|
|
114
126
|
PLATFORMS
|
115
127
|
arm64-darwin-22
|
128
|
+
x86_64-linux
|
116
129
|
|
117
130
|
DEPENDENCIES
|
118
131
|
benchmark-ips (~> 2.9)
|
@@ -122,8 +135,11 @@ DEPENDENCIES
|
|
122
135
|
rake (~> 13.0)
|
123
136
|
relaxed-rubocop
|
124
137
|
rspec (~> 3.0)
|
138
|
+
rspec-github
|
125
139
|
rubocop (~> 1.21)
|
126
140
|
rubocop-rspec
|
141
|
+
simplecov
|
142
|
+
simplecov-cobertura
|
127
143
|
yard (~> 0.9)
|
128
144
|
|
129
145
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# Quantitative
|
2
2
|
|
3
|
-
[![Gem Version](https://badge.fury.io/rb/quantitative.svg)](https://badge.fury.io/rb/quantitative)
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/quantitative.svg)](https://badge.fury.io/rb/quantitative) [![codecov](https://codecov.io/gh/mwlang/quantitative/graph/badge.svg?token=ZXMSKQZKD5)](https://codecov.io/gh/mwlang/quantitative)
|
4
4
|
|
5
5
|
STATUS: ALPHA - very early stages! The framework is very much a work in progress and I am rapidly introducing new things and changing existing things around.
|
6
6
|
|
7
|
-
Quantitative is a statistical and quantitative library for Ruby 3.
|
7
|
+
Quantitative is a statistical and quantitative library for Ruby 3.2+ for trading stocks, cryptocurrency, and forex. It provides a number of classes and modules for working with time-series data, financial data, and other quantitative data. It is designed to be fast, efficient, and easy to use.
|
8
8
|
|
9
9
|
It has been highly optimized for fairly high-frequency trading purely in Ruby (no external numerical/statistical native extensions). The one exception is that I opted to depend on `Oj` which is a high-performant JSON parser that greatly speeds up serializing data between disk and memory. In practice, Quantitative is performant enough to trade one minute tickers on down to 30 second ticks for around 100 or so ticker symbols. Trading anything lower depends on the amount of analysis you're doing and your mileage may vary. It is possible, but you will find yourself with tradeoffs between the amount of data you can crunch and how fast you can react to live trading situations.
|
10
10
|
|
@@ -46,6 +46,18 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
46
46
|
|
47
47
|
Bug reports and pull requests are welcome on GitHub at https://github.com/mwlang/quantitative. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/mwlang/quantitative/blob/main/CODE_OF_CONDUCT.md).
|
48
48
|
|
49
|
+
The Relaxed Ruby Style Guide is adopted for RuboCop.
|
50
|
+
|
51
|
+
### Keeping Test Coverage High
|
52
|
+
|
53
|
+
TDD/BDD is fully embraced for this project. If you opt to contribute, please include tests to coverage new features and behavior tests.
|
54
|
+
|
55
|
+
RSpec is the test framework. SimpleCov is used for coverage reports.
|
56
|
+
|
57
|
+
### Test Driven / Behavior Driven development Coverage Map:
|
58
|
+
|
59
|
+
![Coverage Map](https://codecov.io/gh/mwlang/quantitative/graphs/sunburst.svg?token=ZXMSKQZKD5)
|
60
|
+
|
49
61
|
## License
|
50
62
|
|
51
63
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -10,3 +10,32 @@ require "rubocop/rake_task"
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
|
12
12
|
task default: %i[spec rubocop]
|
13
|
+
|
14
|
+
namespace :gem do
|
15
|
+
desc "Build the gem"
|
16
|
+
task :build do
|
17
|
+
sh "gem build quantitative.gemspec"
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "Tag the release in git"
|
21
|
+
task :tag do
|
22
|
+
version = Quant::VERSION
|
23
|
+
sh "git tag -a v#{version} -m 'Release #{version}'"
|
24
|
+
sh "git push origin v#{version}"
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Install local *.gem file"
|
28
|
+
task :install do
|
29
|
+
sh "gem install quantitative-#{Quant::VERSION}.gem"
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Remove local *.gem files"
|
33
|
+
task :clean do
|
34
|
+
sh "rm -f quantitative-#{Quant::VERSION}.gem"
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Release #{Quant::VERSION} to rubygems.org"
|
38
|
+
task release: [:build, :tag] do
|
39
|
+
sh "gem push quantitative-#{Quant::VERSION}.gem"
|
40
|
+
end
|
41
|
+
end
|
data/lib/quant/asset.rb
CHANGED
data/lib/quant/attributes.rb
CHANGED
@@ -93,7 +93,7 @@ module Quant
|
|
93
93
|
end
|
94
94
|
|
95
95
|
registry[klass] ||= {}
|
96
|
-
registry[klass][name] = { key
|
96
|
+
registry[klass][name] = { key:, default: }
|
97
97
|
end
|
98
98
|
|
99
99
|
module InstanceMethods
|
@@ -138,7 +138,7 @@ module Quant
|
|
138
138
|
if entry[:default].is_a?(Symbol) && respond_to?(entry[:default])
|
139
139
|
send(entry[:default])
|
140
140
|
|
141
|
-
elsif entry[:default].is_a?(Symbol) && current_tick
|
141
|
+
elsif entry[:default].is_a?(Symbol) && current_tick.respond_to?(entry[:default])
|
142
142
|
current_tick.send(entry[:default])
|
143
143
|
|
144
144
|
elsif entry[:default].is_a?(Proc)
|
@@ -5,6 +5,7 @@ module Quant
|
|
5
5
|
class Indicator
|
6
6
|
include Enumerable
|
7
7
|
include Mixins::Functions
|
8
|
+
include Mixins::Filters
|
8
9
|
include Mixins::MovingAverages
|
9
10
|
# include Mixins::HilbertTransform
|
10
11
|
# include Mixins::SuperSmoother
|
@@ -44,7 +45,7 @@ module Quant
|
|
44
45
|
|
45
46
|
def <<(tick)
|
46
47
|
@t0 = tick
|
47
|
-
@p0 = points_class.new(tick
|
48
|
+
@p0 = points_class.new(tick:, source:)
|
48
49
|
@points[tick] = @p0
|
49
50
|
|
50
51
|
@p1 = values[-2] || @p0
|
@@ -25,7 +25,7 @@ module Quant
|
|
25
25
|
# prepared, the indicator becomes active and all ticks pushed into the series
|
26
26
|
# are sent to the indicator for processing.
|
27
27
|
def indicator(indicator_class)
|
28
|
-
indicators[indicator_class] ||= indicator_class.new(series
|
28
|
+
indicators[indicator_class] ||= indicator_class.new(series:, source:)
|
29
29
|
end
|
30
30
|
|
31
31
|
# Adds the tick to all active indicators, triggering them to compute
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
module Mixins
|
5
|
+
module ButterworthFilters
|
6
|
+
def two_pole_butterworth(source, period:, previous: :bw)
|
7
|
+
raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
|
8
|
+
|
9
|
+
v0 = p0.send(source)
|
10
|
+
|
11
|
+
v1 = 0.5 * (v0 + p1.send(source))
|
12
|
+
v2 = p1.send(previous)
|
13
|
+
v3 = p2.send(previous)
|
14
|
+
|
15
|
+
radians = Math.sqrt(2) * Math::PI / period
|
16
|
+
a = Math.exp(-radians)
|
17
|
+
b = 2 * a * Math.cos(radians)
|
18
|
+
|
19
|
+
c2 = b
|
20
|
+
c3 = -a**2
|
21
|
+
c1 = 1.0 - c2 - c3
|
22
|
+
|
23
|
+
(c1 * v1) + (c2 * v2) + (c3 * v3)
|
24
|
+
end
|
25
|
+
|
26
|
+
def three_pole_butterworth(source, period:, previous: :bw)
|
27
|
+
raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
|
28
|
+
|
29
|
+
v0 = p0.send(source)
|
30
|
+
v1 = p1.send(previous)
|
31
|
+
v2 = p2.send(previous)
|
32
|
+
v3 = p3.send(previous)
|
33
|
+
|
34
|
+
radians = Math.sqrt(3) * Math::PI / period
|
35
|
+
a = Math.exp(-radians)
|
36
|
+
b = 2 * a * Math.cos(radians)
|
37
|
+
c = a**2
|
38
|
+
|
39
|
+
d4 = c**2
|
40
|
+
d3 = -(c + (b * c))
|
41
|
+
d2 = b + c
|
42
|
+
d1 = 1.0 - d2 - d3 - d4
|
43
|
+
|
44
|
+
(d1 * v0) + (d2 * v1) + (d3 * v2) + (d4 * v3)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -27,7 +27,7 @@ module Quant
|
|
27
27
|
raise ArgumentError, "previous must be a Symbol" unless previous.is_a?(Symbol)
|
28
28
|
|
29
29
|
alpha = bars_to_alpha(period)
|
30
|
-
p0.send(source) * alpha + p1.send(previous) * (1.0 - alpha)
|
30
|
+
(p0.send(source) * alpha) + (p1.send(previous) * (1.0 - alpha))
|
31
31
|
end
|
32
32
|
alias ema exponential_moving_average
|
33
33
|
end
|
data/lib/quant/mixins/filters.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "functions"
|
4
|
-
|
5
3
|
module Quant
|
6
4
|
module Mixins
|
7
5
|
# 1. All the common filters useful for traders have a transfer response
|
@@ -47,55 +45,7 @@ module Quant
|
|
47
45
|
# Band-stop (1+σ)/2 -2λ*(1+σ)/2 (1+σ)/2 1 -λ*(1+σ) σ
|
48
46
|
#
|
49
47
|
module Filters
|
50
|
-
include Mixins::
|
51
|
-
|
52
|
-
def ema(source, prev_source, period)
|
53
|
-
alpha = bars_to_alpha(period)
|
54
|
-
v0 = source.is_a?(Symbol) ? p0.send(source) : source
|
55
|
-
v1 = p1.send(prev_source)
|
56
|
-
(v0 * alpha) + (v1 * (1 - alpha))
|
57
|
-
end
|
58
|
-
|
59
|
-
def band_pass(source, prev_source, period, bandwidth); end
|
60
|
-
|
61
|
-
def two_pole_butterworth(source, prev_source, period)
|
62
|
-
v0 = source.is_a?(Symbol) ? p0.send(source) : source
|
63
|
-
|
64
|
-
v1 = 0.5 * (v0 + p1.send(source))
|
65
|
-
v2 = p1.send(prev_source)
|
66
|
-
v3 = p2.send(prev_source)
|
67
|
-
|
68
|
-
radians = Math.sqrt(2) * Math::PI / period
|
69
|
-
a = Math.exp(-radians)
|
70
|
-
b = 2 * a * Math.cos(radians)
|
71
|
-
|
72
|
-
c2 = b
|
73
|
-
c3 = -a**2
|
74
|
-
c1 = 1.0 - c2 - c3
|
75
|
-
|
76
|
-
(c1 * v1) + (c2 * v2) + (c3 * v3)
|
77
|
-
end
|
78
|
-
|
79
|
-
def three_pole_butterworth(source, prev_source, period)
|
80
|
-
v0 = source.is_a?(Symbol) ? p0.send(source) : source
|
81
|
-
return v0 if p2 == p3
|
82
|
-
|
83
|
-
v1 = p1.send(prev_source)
|
84
|
-
v2 = p2.send(prev_source)
|
85
|
-
v3 = p3.send(prev_source)
|
86
|
-
|
87
|
-
radians = Math.sqrt(3) * Math::PI / period
|
88
|
-
a = Math.exp(-radians)
|
89
|
-
b = 2 * a * Math.cos(radians)
|
90
|
-
c = a**2
|
91
|
-
|
92
|
-
d4 = c**2
|
93
|
-
d3 = -(c + (b * c))
|
94
|
-
d2 = b + c
|
95
|
-
d1 = 1.0 - d2 - d3 - d4
|
96
|
-
|
97
|
-
(d1 * v0) + (d2 * v1) + (d3 * v2) + (d4 * v3)
|
98
|
-
end
|
48
|
+
include Mixins::ButterworthFilters
|
99
49
|
end
|
100
50
|
end
|
101
51
|
end
|
@@ -3,36 +3,45 @@
|
|
3
3
|
module Quant
|
4
4
|
module Mixins
|
5
5
|
# Fisher Transforms
|
6
|
-
# • Price is not a Gaussian (Bell Curve) distribution, even though many
|
7
|
-
# falsely assume that it is. Bell Curve tails
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
6
|
+
# • Price is not a Gaussian (Bell Curve) distribution, even though many
|
7
|
+
# technical analysis formulas falsely assume that it is. Bell Curve tails
|
8
|
+
# are missing.
|
9
|
+
# – If $10 stock were Gaussian, it could go up or down $20
|
10
|
+
# – Standard deviation based indicators like Bollinger Bands
|
11
|
+
# and zScore make the Gaussian assumption error
|
12
|
+
#
|
13
|
+
# • TheFisher Transform converts almost any probability distribution
|
14
|
+
# in a Gaussian-like one
|
15
|
+
# – Expands the distribution and creates tails
|
16
|
+
#
|
17
|
+
# • The Inverse Fisher Transform converts almost any probability
|
18
|
+
# distribution into a square wave
|
19
|
+
# – Compresses, removes low amplitude variations
|
14
20
|
module FisherTransform
|
15
21
|
# inverse fisher transform
|
16
22
|
# https://www.mql5.com/en/articles/303
|
17
|
-
def
|
23
|
+
def inverse_fisher_transform(value, scale_factor: 1.0)
|
18
24
|
r = (Math.exp(2.0 * scale_factor * value) - 1.0) / (Math.exp(2.0 * scale_factor * value) + 1.0)
|
19
25
|
r.nan? ? 0.0 : r
|
20
26
|
end
|
27
|
+
alias ift inverse_fisher_transform
|
21
28
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# r.nan? ? 0.0 : r
|
27
|
-
# end
|
29
|
+
def relative_fisher_transform(value, max_value:)
|
30
|
+
max_value.zero? ? 0.0 : fisher_transform(value / max_value)
|
31
|
+
end
|
32
|
+
alias rft relative_fisher_transform
|
28
33
|
|
29
34
|
# The absolute value passed must be < 1.0
|
30
35
|
def fisher_transform(value)
|
31
|
-
|
32
|
-
|
36
|
+
raise ArgumentError, "value (#{value}) must be between -1.0 and 1.0" unless value.abs <= 1.0
|
37
|
+
|
38
|
+
result = 0.5 * Math.log((1.0 + value) / (1.0 - value))
|
39
|
+
result.nan? ? 0.0 : result
|
33
40
|
rescue Math::DomainError => e
|
34
|
-
raise "
|
41
|
+
raise Math::DomainError, "#{e.message}: cannot take the Log of #{value}: #{(1 + value) / (1 - value)}"
|
35
42
|
end
|
43
|
+
alias fisher fisher_transform
|
44
|
+
alias ft fisher_transform
|
36
45
|
end
|
37
46
|
end
|
38
47
|
end
|
@@ -3,11 +3,11 @@
|
|
3
3
|
module Quant
|
4
4
|
module Mixins
|
5
5
|
module HilbertTransform
|
6
|
-
def hilbert_transform(source, period
|
6
|
+
def hilbert_transform(source, period:)
|
7
7
|
[0.0962 * p0.send(source),
|
8
8
|
0.5769 * p2.send(source),
|
9
|
-
-0.5769 *
|
10
|
-
-0.0962 *
|
9
|
+
-0.5769 * p(4).send(source),
|
10
|
+
-0.0962 * p(6).send(source),].sum * ((0.075 * period) + 0.54)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
@@ -3,28 +3,24 @@
|
|
3
3
|
module Quant
|
4
4
|
module Mixins
|
5
5
|
module Stochastic
|
6
|
-
|
7
|
-
stoch_period = [points.size, period.to_i].min
|
8
|
-
return 0.0 if stoch_period < 1
|
6
|
+
using Quant
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
# The Stochastic Oscillator is a momentum indicator that compares a particular
|
9
|
+
# closing price of a security to a range of its prices over a certain
|
10
|
+
# period of time. It was developed by George C. Lane in the 1950s.
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
# The main idea behind the Stochastic Oscillator is that closing
|
13
|
+
# prices should close near the same direction as the current trend.
|
14
|
+
# In a market trending up, prices will likely close near their
|
15
|
+
# high, and in a market trending down, prices close near their low.
|
16
|
+
def stochastic(source, period:)
|
17
|
+
subset = values.last(period).map{ |p| p.send(source) }
|
18
|
+
|
19
|
+
lowest, highest = subset.minimum, subset.maximum
|
20
|
+
return 0.0 if (highest - lowest).zero?
|
17
21
|
|
18
|
-
|
19
|
-
|
20
|
-
# property inst_stoch : Float64 = 0.0
|
21
|
-
# @[JSON::Field(key: "sh")]
|
22
|
-
# property stoch : Float64 = 0.0
|
23
|
-
# @[JSON::Field(key: "su")]
|
24
|
-
# property stoch_up : Bool = false
|
25
|
-
# @[JSON::Field(key: "st")]
|
26
|
-
# property stoch_turned : Bool = false
|
27
|
-
# end
|
22
|
+
100.0 * (subset[-1] - lowest) / (highest - lowest)
|
23
|
+
end
|
28
24
|
end
|
29
25
|
end
|
30
26
|
end
|
@@ -13,7 +13,7 @@ module Quant
|
|
13
13
|
coef3 = -a1 * a1
|
14
14
|
coef1 = 1.0 - coef2 - coef3
|
15
15
|
|
16
|
-
v0 = (p0.send(source) + p1.send(source))/2.0
|
16
|
+
v0 = (p0.send(source) + p1.send(source)) / 2.0
|
17
17
|
v1 = p2.send(previous)
|
18
18
|
v2 = p3.send(previous)
|
19
19
|
((coef1 * v0) + (coef2 * v1) + (coef3 * v2)).to_f
|
@@ -17,7 +17,7 @@ module Quant
|
|
17
17
|
[4.0 * p0.send(source),
|
18
18
|
3.0 * p1.send(source),
|
19
19
|
2.0 * p2.send(source),
|
20
|
-
|
20
|
+
p3.send(source)].sum / 10.0
|
21
21
|
end
|
22
22
|
alias wma weighted_moving_average
|
23
23
|
|
@@ -37,7 +37,7 @@ module Quant
|
|
37
37
|
4.0 * p3.send(source),
|
38
38
|
3.0 * p(4).send(source),
|
39
39
|
2.0 * p(5).send(source),
|
40
|
-
|
40
|
+
p(6).send(source)].sum / 28.0
|
41
41
|
end
|
42
42
|
alias ewma extended_weighted_moving_average
|
43
43
|
end
|
@@ -176,7 +176,7 @@ module Quant
|
|
176
176
|
# @param n [Integer] the number of elements to compute the Standard Deviation over.
|
177
177
|
# @return [Float]
|
178
178
|
def stddev(reference_value, n: size)
|
179
|
-
variance(reference_value, n:
|
179
|
+
variance(reference_value, n:)**0.5
|
180
180
|
end
|
181
181
|
|
182
182
|
def variance(reference_value, n: size)
|
data/lib/quant/series.rb
CHANGED
@@ -19,7 +19,7 @@ module Quant
|
|
19
19
|
raise "File #{filename} does not exist" unless File.exist?(filename)
|
20
20
|
|
21
21
|
ticks = File.read(filename).split("\n").map{ |line| Oj.load(line) }
|
22
|
-
from_hash symbol
|
22
|
+
from_hash symbol:, interval:, hash: ticks, serializer_class:
|
23
23
|
end
|
24
24
|
|
25
25
|
# Loads a series of ticks when the JSON string represents an array of ticks.
|
@@ -29,8 +29,8 @@ module Quant
|
|
29
29
|
# @param json [String] The JSON string to parse into ticks.
|
30
30
|
# @param serializer_class [Class] {Quant::Ticks::TickSerializer} class to use for the conversion.
|
31
31
|
def self.from_json(symbol:, interval:, json:, serializer_class: nil)
|
32
|
-
|
33
|
-
from_hash symbol
|
32
|
+
hash = Oj.load(json)
|
33
|
+
from_hash symbol:, interval:, hash:, serializer_class:
|
34
34
|
end
|
35
35
|
|
36
36
|
# Loads a series of ticks where the hash must be cast to an array of {Quant::Ticks::Tick} objects.
|
@@ -39,15 +39,15 @@ module Quant
|
|
39
39
|
# @param hash [Array<Hash>] The array of hashes to convert to {Quant::Ticks::Tick} objects.
|
40
40
|
# @param serializer_class [Class] {Quant::Ticks::TickSerializer} class to use for the conversion.
|
41
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:
|
43
|
-
from_ticks symbol
|
42
|
+
ticks = hash.map { |tick_hash| Quant::Ticks::OHLC.from(tick_hash, serializer_class:) }
|
43
|
+
from_ticks symbol:, interval:, ticks:
|
44
44
|
end
|
45
45
|
|
46
46
|
# Loads a series of ticks where the array represents an array of {Quant::Ticks::Tick} objects.
|
47
47
|
def self.from_ticks(symbol:, interval:, ticks:)
|
48
48
|
ticks = ticks.sort_by(&:close_timestamp)
|
49
49
|
|
50
|
-
new(symbol
|
50
|
+
new(symbol:, interval:).tap do |series|
|
51
51
|
ticks.each { |tick| series << tick }
|
52
52
|
end
|
53
53
|
end
|
@@ -64,14 +64,14 @@ module Quant
|
|
64
64
|
selected_ticks = ticks[start_iteration..stop_iteration]
|
65
65
|
return self if selected_ticks.size == ticks.size
|
66
66
|
|
67
|
-
self.class.from_ticks(symbol
|
67
|
+
self.class.from_ticks(symbol:, interval:, ticks: selected_ticks)
|
68
68
|
end
|
69
69
|
|
70
70
|
def limit(period)
|
71
71
|
selected_ticks = ticks.select{ |tick| period.cover?(tick.close_timestamp) }
|
72
72
|
return self if selected_ticks.size == ticks.size
|
73
73
|
|
74
|
-
self.class.from_ticks(symbol
|
74
|
+
self.class.from_ticks(symbol:, interval:, ticks: selected_ticks)
|
75
75
|
end
|
76
76
|
|
77
77
|
def_delegator :@ticks, :[]
|
@@ -94,7 +94,7 @@ module Quant
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def dup
|
97
|
-
self.class.from_ticks(symbol
|
97
|
+
self.class.from_ticks(symbol:, interval:, ticks:)
|
98
98
|
end
|
99
99
|
|
100
100
|
def inspect
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Quant
|
4
4
|
module Ticks
|
5
5
|
module Serializers
|
6
|
-
|
6
|
+
class OHLC < Tick
|
7
7
|
# Returns a +Quant::Ticks::Tick+ from a valid JSON +String+.
|
8
8
|
# @param json [String]
|
9
9
|
# @param tick_class [Quant::Ticks::Tick]
|
@@ -13,7 +13,7 @@ module Quant
|
|
13
13
|
# Quant::Ticks::Serializers::Tick.from_json(json, tick_class: Quant::Ticks::Spot)
|
14
14
|
def self.from_json(json, tick_class:)
|
15
15
|
hash = Oj.load(json)
|
16
|
-
from(hash, tick_class:
|
16
|
+
from(hash, tick_class:)
|
17
17
|
end
|
18
18
|
|
19
19
|
# Instantiates a tick from a +Hash+. The hash keys are expected to be the same as the serialized keys.
|
@@ -13,7 +13,7 @@ module Quant
|
|
13
13
|
# Quant::Ticks::Serializers::Tick.from_json(json, tick_class: Quant::Ticks::Spot)
|
14
14
|
def self.from_json(json, tick_class:)
|
15
15
|
hash = Oj.load(json)
|
16
|
-
from(hash, tick_class:
|
16
|
+
from(hash, tick_class:)
|
17
17
|
end
|
18
18
|
|
19
19
|
# Returns a +Hash+ of the Spot tick's key properties
|
data/lib/quant/ticks/tick.rb
CHANGED
@@ -120,7 +120,7 @@ module Quant
|
|
120
120
|
# tick.to_csv(headers: true)
|
121
121
|
# # => "timestamp,price,volume\n2018-01-01 12:00:00 UTC,100.0,1000\n"
|
122
122
|
def to_csv(serializer_class: default_serializer_class, headers: false)
|
123
|
-
serializer_class.to_csv(self, headers:
|
123
|
+
serializer_class.to_csv(self, headers:)
|
124
124
|
end
|
125
125
|
|
126
126
|
# Reflects the serializer class from the tick's class name.
|
data/lib/quant/time_period.rb
CHANGED
@@ -16,13 +16,21 @@ module Quant
|
|
16
16
|
def as_start_time(value)
|
17
17
|
return value if value.nil? || value.is_a?(Time)
|
18
18
|
|
19
|
-
value.is_a?(Date) ? value
|
19
|
+
value.is_a?(Date) ? beginning_of_day(value) : value.to_time
|
20
20
|
end
|
21
21
|
|
22
22
|
def as_end_time(value)
|
23
23
|
return value if value.nil? || value.is_a?(Time)
|
24
24
|
|
25
|
-
value.is_a?(Date) ? value
|
25
|
+
value.is_a?(Date) ? end_of_day(value) : value.to_time
|
26
|
+
end
|
27
|
+
|
28
|
+
def end_of_day(date)
|
29
|
+
Time.utc(date.year, date.month, date.day, 23, 59, 59)
|
30
|
+
end
|
31
|
+
|
32
|
+
def beginning_of_day(date)
|
33
|
+
Time.utc(date.year, date.month, date.day)
|
26
34
|
end
|
27
35
|
|
28
36
|
def validate_bounds!
|
@@ -72,7 +80,7 @@ module Quant
|
|
72
80
|
end
|
73
81
|
|
74
82
|
def to_h
|
75
|
-
{ start_at
|
83
|
+
{ start_at:, end_at: }
|
76
84
|
end
|
77
85
|
end
|
78
86
|
end
|
data/lib/quant/version.rb
CHANGED
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.9
|
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-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oj
|
@@ -24,8 +24,8 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.10'
|
27
|
-
description: Quantitative and statistical tools written for Ruby 3.
|
28
|
-
finance.
|
27
|
+
description: Quantitative and statistical tools written for Ruby 3.2+ for trading
|
28
|
+
and finance.
|
29
29
|
email:
|
30
30
|
- mwlang@cybrains.net
|
31
31
|
executables: []
|
@@ -57,6 +57,7 @@ files:
|
|
57
57
|
- lib/quant/indicators_proxy.rb
|
58
58
|
- lib/quant/indicators_sources.rb
|
59
59
|
- lib/quant/interval.rb
|
60
|
+
- lib/quant/mixins/butterworth_filters.rb
|
60
61
|
- lib/quant/mixins/direction.rb
|
61
62
|
- lib/quant/mixins/exponential_moving_average.rb
|
62
63
|
- lib/quant/mixins/filters.rb
|
@@ -99,7 +100,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
100
|
requirements:
|
100
101
|
- - ">="
|
101
102
|
- !ruby/object:Gem::Version
|
102
|
-
version: 3.
|
103
|
+
version: '3.2'
|
103
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
105
|
requirements:
|
105
106
|
- - ">="
|
@@ -109,5 +110,6 @@ requirements: []
|
|
109
110
|
rubygems_version: 3.5.6
|
110
111
|
signing_key:
|
111
112
|
specification_version: 4
|
112
|
-
summary: Quantitative and statistical tools written for Ruby 3.
|
113
|
+
summary: Quantitative and statistical tools written for Ruby 3.2+ for trading and
|
114
|
+
finance.
|
113
115
|
test_files: []
|