quantitative 0.1.6 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72d65b65fdcbb81650fcce3233f3fbde9c99c4b9b9ace1ce3bbc3b8e565152f9
4
- data.tar.gz: f687ab3c32e88ffc579a9f92ff428846f91e60ff166d3b4379963deb15a88425
3
+ metadata.gz: 7491465248d383f459899467e14c9bafa16376f8fe7dc2cac657fca8a9170200
4
+ data.tar.gz: 956074c17983ee22d6480a176c48b0cc15257aaca2649086eacc71a52688cb12
5
5
  SHA512:
6
- metadata.gz: 594d57a819ce8d561c064f685353d27ef4dfd1cb2e76e5cf07758bcedb1c501d2bc06859aff89cf6f4bc744e327ede1290e31b4d4ffb1daed99d614735eb0a76
7
- data.tar.gz: 5fbfb29d1501a3562788abf83ef4fcb78bcb17fcfc030e7543b42f3c29c2a7a5c3c8fba187494d4655691e117e383af59bca428764f49fd7e41fcef911ef6d40
6
+ metadata.gz: 0f932bc68ca6f3e47261aaf2d19b34465b0f242bc13b5935ab55c0eea0ee736b0ea183b4bacc6a99d9beecd9a2457dd07957a639bd2d85e35f0a5584b35c2f2a
7
+ data.tar.gz: c0baa0af0133cbdd46439fc0ce8d068a583f05688371025275afe4297d0bd01a7aad90c4f3ebae98afaa1a4b28b56e43fa2aa68aaafc018bd34e22ec037d1b07
data/.rubocop.yml CHANGED
@@ -2,7 +2,7 @@ inherit_gem:
2
2
  relaxed-rubocop: .rubocop.yml
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 3.0
5
+ TargetRubyVersion: 3.2
6
6
  Exclude:
7
7
  - 'spec/performance/*.rb'
8
8
 
data/Gemfile CHANGED
@@ -18,3 +18,7 @@ gem "guard-rspec", "~> 4.7"
18
18
  gem "yard", "~> 0.9"
19
19
  gem "benchmark-ips", "~> 2.9"
20
20
 
21
+ gem 'rspec-github'
22
+ gem 'simplecov'
23
+ gem 'simplecov-cobertura'
24
+
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- quantitative (0.1.6)
4
+ quantitative (0.1.8)
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.x 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.
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/lib/quant/asset.rb CHANGED
@@ -82,7 +82,7 @@ module Quant
82
82
  end
83
83
 
84
84
  def to_json(*args, full: false)
85
- Oj.dump(to_h(full: full), *args)
85
+ Oj.dump(to_h(full:), *args)
86
86
  end
87
87
  end
88
88
  end
@@ -93,7 +93,7 @@ module Quant
93
93
  end
94
94
 
95
95
  registry[klass] ||= {}
96
- registry[klass][name] = { key: key, default: default }
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&.respond_to?(entry[:default])
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)
@@ -4,10 +4,9 @@ module Quant
4
4
  class Indicators
5
5
  class Indicator
6
6
  include Enumerable
7
-
8
- # # include Mixins::TrendMethods
9
- # include Mixins::Trig
10
- # include Mixins::WeightedAverage
7
+ include Mixins::Functions
8
+ include Mixins::Filters
9
+ include Mixins::MovingAverages
11
10
  # include Mixins::HilbertTransform
12
11
  # include Mixins::SuperSmoother
13
12
  # include Mixins::Stochastic
@@ -46,7 +45,7 @@ module Quant
46
45
 
47
46
  def <<(tick)
48
47
  @t0 = tick
49
- @p0 = points_class.new(tick: tick, source: source)
48
+ @p0 = points_class.new(tick:, source:)
50
49
  @points[tick] = @p0
51
50
 
52
51
  @p1 = values[-2] || @p0
@@ -6,6 +6,7 @@ module Quant
6
6
  include Quant::Attributes
7
7
 
8
8
  attr_reader :tick
9
+
9
10
  attribute :source, key: "src"
10
11
  attribute :input, key: "in"
11
12
 
@@ -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: series, source: source)
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
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ module Mixins
5
+ module ExponentialMovingAverage
6
+ # Computes the Exponential Moving Average (EMA) of the given period.
7
+ #
8
+ # The EMA computation is optimized to compute using just the last two
9
+ # indicator data points and is expected to be called in each indicator's
10
+ # `#compute` method for each iteration on the series.
11
+ #
12
+ # @param source [Symbol] the source of the data points to be used in the calculation.
13
+ # @param previous [Symbol] the previous EMA value.
14
+ # @param period [Integer] the number of elements to compute the EMA over.
15
+ # @return [Float] the exponential moving average of the period.
16
+ # @raise [ArgumentError] if the source is not a Symbol.
17
+ # @example
18
+ # def compute
19
+ # p0.ema = exponential_moving_average(:close_price, period: 3)
20
+ # end
21
+ #
22
+ # def compute
23
+ # p0.ema = exponential_moving_average(:close_price, previous: :ema, period: 3)
24
+ # end
25
+ def exponential_moving_average(source, period:, previous: :ema)
26
+ raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
27
+ raise ArgumentError, "previous must be a Symbol" unless previous.is_a?(Symbol)
28
+
29
+ alpha = bars_to_alpha(period)
30
+ (p0.send(source) * alpha) + (p1.send(previous) * (1.0 - alpha))
31
+ end
32
+ alias ema exponential_moving_average
33
+ end
34
+ end
35
+ end
@@ -1,114 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "trig"
4
-
5
3
  module Quant
6
4
  module Mixins
7
- # 1. All the common filters useful for traders have a transfer response that can be written
8
- # as a ratio of two polynomials.
9
- # 2. Lag is very important to traders. More complex filters can be created using more input data,
10
- # but more input data increases lag. Sophisticated filters are not very useful for trading
11
- # because they incur too much lag.
12
- # 3. Filter transfer response can be viewed in the time domain and the frequency domain with equal validity.
13
- # 4. Nonrecursive filters can have zeros in the transfer response, enabling the complete cancellation of
14
- # some selected frequency components.
15
- # 5. Nonrecursive filters having coefficients symmetrical about the center of the filter will have a delay
16
- # of half the degree of the transfer response polynomial at all frequencies.
17
- # 6. Low-pass filters are smoothers because they attenuate the high-frequency components of the input data.
18
- # 7. High-pass filters are detrenders because they attenuate the low-frequency components of trends.
19
- # 8. Band-pass filters are both detrenders and smoothers because they attenuate all but the desired frequency components.
20
- # 9. Filters provide an output only through their transfer response. The transfer response is strictly a
21
- # mathematical function, and interpretations such as overbought, oversold, convergence, divergence,
22
- # and so on are not implied. The validity of such interpretations must be made on the basis of
23
- # statistics apart from the filter.
24
- # 10. The critical period of a filter output is the frequency at which the output power of the filter
25
- # is half the power of the input wave at that frequency.
5
+ # 1. All the common filters useful for traders have a transfer response
6
+ # that can be written as a ratio of two polynomials.
7
+ # 2. Lag is very important to traders. More complex filters can be
8
+ # created using more input data, but more input data increases lag.
9
+ # Sophisticated filters are not very useful for trading because they
10
+ # incur too much lag.
11
+ # 3. Filter transfer response can be viewed in the time domain and
12
+ # the frequency domain with equal validity.
13
+ # 4. Nonrecursive filters can have zeros in the transfer response, enabling
14
+ # the complete cancellation of some selected frequency components.
15
+ # 5. Nonrecursive filters having coefficients symmetrical about the
16
+ # center of the filter will have a delay of half the degree of the
17
+ # transfer response polynomial at all frequencies.
18
+ # 6. Low-pass filters are smoothers because they attenuate the high-frequency
19
+ # components of the input data.
20
+ # 7. High-pass filters are detrenders because they attenuate the
21
+ # low-frequency components of trends.
22
+ # 8. Band-pass filters are both detrenders and smoothers because they
23
+ # attenuate all but the desired frequency components.
24
+ # 9. Filters provide an output only through their transfer response.
25
+ # The transfer response is strictly a mathematical function, and
26
+ # interpretations such as overbought, oversold, convergence, divergence,
27
+ # and so on are not implied. The validity of such interpretations
28
+ # must be made on the basis of statistics apart from the filter.
29
+ # 10. The critical period of a filter output is the frequency at which
30
+ # the output power of the filter is half the power of the input
31
+ # wave at that frequency.
26
32
  # 11. A WMA has little or no redeeming virtue.
27
- # 12. A median filter is best used when the data contain impulsive noise or when there are wild
28
- # variations in the data. Smoothing volume data is one example of a good application for a
29
- # median filter.
33
+ # 12. A median filter is best used when the data contain impulsive noise
34
+ # or when there are wild variations in the data. Smoothing volume
35
+ # data is one example of a good application for a median filter.
36
+ #
37
+ # == Filter Coefficients forVariousTypes of Filters
38
+ #
39
+ # Filter Type b0 b1 b2 a0 a1 a2
40
+ # EMA α 0 0 1 −(1−α) 0
41
+ # Two-pole low-pass α**2 0 0 1 −2*(1-α) (1-α)**2
42
+ # High-pass (1-α/2) -(1-α/2) 0 1 −(1−α) 0
43
+ # Two-pole high-pass (1-α/2)**2 -2*(1-α/2)**2 (1-α/2)**2 1 -2*(1-α) (1-α)**2
44
+ # Band-pass (1-σ)/2 0 -(1-σ)/2 1 -λ*(1+σ) σ
45
+ # Band-stop (1+σ)/2 -2λ*(1+σ)/2 (1+σ)/2 1 -λ*(1+σ) σ
30
46
  #
31
- # Filter Coefficients forVariousTypes of Filters
32
- # Filter Type b0 b1 b2 a0 a1 a2
33
- # EMA α 0 0 1 −(1−α) 0
34
- # Two-pole low-pass α**2 0 0 1 −2*(1-α) (1-α)**2
35
- # High-pass (1-α/2) -(1-α/2) 0 1 −(1−α) 0
36
- # Two-pole high-pass (1-α/2)**2 -2*(1-α/2)**2 (1-α/2)**2 1 -2*(1-α) (1-α)**2
37
- # Band-pass (1-σ)/2 0 -(1-σ)/2 1 -λ*(1+σ) σ
38
- # Band-stop (1+σ)/2 -2λ*(1+σ)/2 (1+σ)/2 1 -λ*(1+σ) σ
39
47
  module Filters
40
- include Mixins::Trig
41
-
42
- # α = Cos(K*360/Period)+Sin(K*360/Period)−1 / Cos(K*360/Period)
43
- # k = 1.0 for single-pole filters
44
- # k = 0.707 for two-pole high-pass filters
45
- # k = 1.414 for two-pole low-pass filters
46
- def period_to_alpha(period, k: 1.0)
47
- radians = deg2rad(k * 360 / period)
48
- cos = Math.cos(radians)
49
- sin = Math.sin(radians)
50
- (cos + sin - 1) / cos
51
- end
52
-
53
- # 3 bars = 0.5
54
- # 4 bars = 0.4
55
- # 5 bars = 0.333
56
- # 6 bars = 0.285
57
- # 10 bars = 0.182
58
- # 20 bars = 0.0952
59
- # 40 bars = 0.0488
60
- # 50 bars = 0.0392
61
- def bars_to_alpha(bars)
62
- 2.0 / (bars + 1)
63
- end
64
-
65
- def ema(source, prev_source, period)
66
- alpha = bars_to_alpha(period)
67
- v0 = source.is_a?(Symbol) ? p0.send(source) : source
68
- v1 = p1.send(prev_source)
69
- (v0 * alpha) + (v1 * (1 - alpha))
70
- end
71
-
72
- def band_pass(source, prev_source, period, bandwidth); end
73
-
74
- def two_pole_butterworth(source, prev_source, period)
75
- v0 = source.is_a?(Symbol) ? p0.send(source) : source
76
-
77
- v1 = 0.5 * (v0 + p1.send(source))
78
- v2 = p1.send(prev_source)
79
- v3 = p2.send(prev_source)
80
-
81
- radians = Math.sqrt(2) * Math::PI / period
82
- a = Math.exp(-radians)
83
- b = 2 * a * Math.cos(radians)
84
-
85
- c2 = b
86
- c3 = -a**2
87
- c1 = 1.0 - c2 - c3
88
-
89
- (c1 * v1) + (c2 * v2) + (c3 * v3)
90
- end
91
-
92
- def three_pole_butterworth(source, prev_source, period)
93
- v0 = source.is_a?(Symbol) ? p0.send(source) : source
94
- return v0 if p2 == p3
95
-
96
- v1 = p1.send(prev_source)
97
- v2 = p2.send(prev_source)
98
- v3 = p3.send(prev_source)
99
-
100
- radians = Math.sqrt(3) * Math::PI / period
101
- a = Math.exp(-radians)
102
- b = 2 * a * Math.cos(radians)
103
- c = a**2
104
-
105
- d4 = c**2
106
- d3 = -(c + (b * c))
107
- d2 = b + c
108
- d1 = 1.0 - d2 - d3 - d4
109
-
110
- (d1 * v0) + (d2 * v1) + (d3 * v2) + (d4 * v3)
111
- end
48
+ include Mixins::ButterworthFilters
112
49
  end
113
50
  end
114
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 technical analysis formulas
7
- # falsely assume that it is. Bell Curve tails are missing.
8
- # If $10 stock were Gaussian, it could go up or down $20 – Standard deviation based indicators like Bollinger Bands
9
- # and zScore make the Gaussian assumption error
10
- # TheFisher Transform converts almost any probability distribution in a Gaussian-like one
11
- # Expands the distribution and creates tails
12
- # • The Inverse Fisher Transform converts almost any probability distribution into a square wave
13
- # Compresses, removes low amplitude variations
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 ift(value, scale_factor = 1.0)
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
- # def fisher_transform(value, max_value)
23
- # return 0.0 if max_value.zero?
24
- # x = (value / max_value).abs
25
- # r = 0.5 * Math.log((1 + x) / (1 - x))
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
- r = 0.5 * Math.log((1.0 + value) / (1.0 - value))
32
- r.nan? ? 0.0 : r
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 "value #{value}: #{(1 + value) / (1 - value)}, e: #{e}"
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
@@ -2,7 +2,30 @@
2
2
 
3
3
  module Quant
4
4
  module Mixins
5
- module Trig
5
+ module Functions
6
+ # α = Cos(K*360/Period)+Sin(K*360/Period)−1 / Cos(K*360/Period)
7
+ # k = 1.0 for single-pole filters
8
+ # k = 0.707 for two-pole high-pass filters
9
+ # k = 1.414 for two-pole low-pass filters
10
+ def period_to_alpha(period, k: 1.0)
11
+ radians = deg2rad(k * 360 / period)
12
+ cos = Math.cos(radians)
13
+ sin = Math.sin(radians)
14
+ (cos + sin - 1) / cos
15
+ end
16
+
17
+ # 3 bars = 0.5
18
+ # 4 bars = 0.4
19
+ # 5 bars = 0.333
20
+ # 6 bars = 0.285
21
+ # 10 bars = 0.182
22
+ # 20 bars = 0.0952
23
+ # 40 bars = 0.0488
24
+ # 50 bars = 0.0392
25
+ def bars_to_alpha(bars)
26
+ 2.0 / (bars + 1)
27
+ end
28
+
6
29
  def deg2rad(degrees)
7
30
  degrees * Math::PI / 180.0
8
31
  end
@@ -3,11 +3,11 @@
3
3
  module Quant
4
4
  module Mixins
5
5
  module HilbertTransform
6
- def hilbert_transform(source, period = p1.period)
6
+ def hilbert_transform(source, period:)
7
7
  [0.0962 * p0.send(source),
8
8
  0.5769 * p2.send(source),
9
- -0.5769 * prev(4).send(source),
10
- -0.0962 * prev(6).send(source),].sum * ((0.075 * period) + 0.54)
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
@@ -1,86 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "weighted_moving_average"
4
+ require_relative "simple_moving_average"
5
+ require_relative "exponential_moving_average"
3
6
  module Quant
4
7
  module Mixins
5
8
  module MovingAverages
6
- using Quant
7
-
8
- # Computes the Weighted Moving Average (WMA) of the series, using the four most recent data points.
9
- #
10
- # @param source [Symbol] the source of the data points to be used in the calculation.
11
- # @return [Float] the weighted average of the series.
12
- # @raise [ArgumentError] if the source is not a Symbol.
13
- # @example
14
- # p0.wma = weighted_average(:close_price)
15
- def weighted_moving_average(source)
16
- raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
17
-
18
- [4.0 * p0.send(source),
19
- 3.0 * p1.send(source),
20
- 2.0 * p2.send(source),
21
- p3.send(source)].sum / 10.0
22
- end
23
- alias wma weighted_moving_average
24
-
25
- # Computes the Weighted Moving Average (WMA) of the series, using the seven most recent data points.
26
- #
27
- # @param source [Symbol] the source of the data points to be used in the calculation.
28
- # @return [Float] the weighted average of the series.
29
- # @raise [ArgumentError] if the source is not a Symbol.
30
- # @example
31
- # p0.wma = weighted_average(:close_price)
32
- def extended_weighted_moving_average(source)
33
- raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
34
-
35
- [7.0 * p0.send(source),
36
- 6.0 * p1.send(source),
37
- 5.0 * p2.send(source),
38
- 4.0 * p3.send(source),
39
- 3.0 * p(4).send(source),
40
- 2.0 * p(5).send(source),
41
- p(6).send(source)].sum / 28.0
42
- end
43
- alias ewma extended_weighted_moving_average
44
-
45
- # Computes the Simple Moving Average (SMA) of the given period.
46
- #
47
- # @param source [Symbol] the source of the data points to be used in the calculation.
48
- # @param period [Integer] the number of elements to compute the SMA over.
49
- # @return [Float] the simple moving average of the period.
50
- def simple_moving_average(source, period:)
51
- raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
52
-
53
- values.last(period).map { |value| value.send(source) }.mean
54
- end
55
- alias sma simple_moving_average
56
-
57
- # Computes the Exponential Moving Average (EMA) of the given period.
58
- #
59
- # The EMA computation is optimized to compute using just the last two
60
- # indicator data points and is expected to be called in each indicator's
61
- # `#compute` method for each iteration on the series.
62
- #
63
- # @param source [Symbol] the source of the data points to be used in the calculation.
64
- # @param previous [Symbol] the previous EMA value.
65
- # @param period [Integer] the number of elements to compute the EMA over.
66
- # @return [Float] the exponential moving average of the period.
67
- # @raise [ArgumentError] if the source is not a Symbol.
68
- # @example
69
- # def compute
70
- # p0.ema = exponential_moving_average(:close_price, period: 3)
71
- # end
72
- #
73
- # def compute
74
- # p0.ema = exponential_moving_average(:close_price, previous: :ema, period: 3)
75
- # end
76
- def exponential_moving_average(source, previous: :ema, period:)
77
- raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
78
- raise ArgumentError, "previous must be a Symbol" unless previous.is_a?(Symbol)
79
-
80
- alpha = 2.0 / (period + 1)
81
- p0.send(source) * alpha + p1.send(previous) * (1.0 - alpha)
82
- end
83
- alias ema exponential_moving_average
9
+ include WeightedMovingAverage
10
+ include SimpleMovingAverage
11
+ include ExponentialMovingAverage
84
12
  end
85
13
  end
86
14
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ module Mixins
5
+ module SimpleMovingAverage
6
+ using Quant
7
+
8
+ # Computes the Simple Moving Average (SMA) of the given period.
9
+ #
10
+ # @param source [Symbol] the source of the data points to be used in the calculation.
11
+ # @param period [Integer] the number of elements to compute the SMA over.
12
+ # @return [Float] the simple moving average of the period.
13
+ def simple_moving_average(source, period:)
14
+ raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
15
+
16
+ values.last(period).map { |value| value.send(source) }.mean
17
+ end
18
+ alias sma simple_moving_average
19
+ end
20
+ end
21
+ end
@@ -3,73 +3,29 @@
3
3
  module Quant
4
4
  module Mixins
5
5
  module SuperSmoother
6
- def super_smoother(source, prev_source, period)
7
- v0 = (source.is_a?(Symbol) ? p0.send(source) : source).to_d
8
- return v0.to_f if points.size < 4
6
+ def two_pole_super_smooth(source, period:, previous: :ss)
7
+ raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
9
8
 
10
- k = Math.exp(-Math.sqrt(2) * Math::PI / period)
11
- coef3 = -k**2
12
- coef2 = 2.0 * k * Math.cos(Math.sqrt(2) * (Math::PI / 2) / period)
13
- coef1 = 1.0 - coef2 - coef3
14
-
15
- v1 = p1.send(prev_source).to_d
16
- v2 = p2.send(prev_source).to_d
17
- p3.send(prev_source).to_d
18
- ((coef1 * (v0 + v1)) / 2.0 + (coef2 * v1) + (coef3 * v2)).to_f
19
- end
20
-
21
- def two_pole_super_smooth(source, prev_source, ssperiod)
22
- return p1.send(source) if [p1 == p3]
23
-
24
- radians = Math::PI * Math.sqrt(2) / ssperiod
9
+ radians = Math::PI * Math.sqrt(2) / period
25
10
  a1 = Math.exp(-radians)
26
11
 
27
- coef2 = 2.0 * a1 * Math.cos(radians)
12
+ coef2 = 2.0r * a1 * Math.cos(radians)
28
13
  coef3 = -a1 * a1
29
14
  coef1 = 1.0 - coef2 - coef3
30
15
 
31
- v0 = (p1.send(source) + p2.send(source)) / 2.0
32
- v1 = p2.send(prev_source)
33
- v2 = p3.send(prev_source)
34
- (coef1 * v0) + (coef2 * v1) + (coef3 * v2)
16
+ v0 = (p0.send(source) + p1.send(source)) / 2.0
17
+ v1 = p2.send(previous)
18
+ v2 = p3.send(previous)
19
+ ((coef1 * v0) + (coef2 * v1) + (coef3 * v2)).to_f
35
20
  end
21
+ alias super_smoother two_pole_super_smooth
22
+ alias ss2p two_pole_super_smooth
36
23
 
37
- def three_pole_super_smooth(source, prev_source, ssperiod)
38
- a1 = Math.exp(-Math::PI / ssperiod)
39
- b1 = 2 * a1 * Math.cos(Math::PI * Math.sqrt(3) / ssperiod)
40
- c1 = a1**2
24
+ def three_pole_super_smooth(source, period:, previous: :ss)
25
+ raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
41
26
 
42
- coef2 = b1 + c1
43
- coef3 = -(c1 + b1 * c1)
44
- coef4 = c1**2
45
- coef1 = 1 - coef2 - coef3 - coef4
46
-
47
- p0 = prev(0)
48
- p1 = prev(1)
49
- p2 = prev(2)
50
- p3 = prev(3)
51
-
52
- v0 = source.is_a?(Symbol) ? p0.send(source) : source
53
- return v0 if [p0, p1, p2].include?(p3)
54
-
55
- v1 = p1.send(prev_source)
56
- v2 = p2.send(prev_source)
57
- v3 = p3.send(prev_source)
58
- (coef1 * v0) + (coef2 * v1) + (coef3 * v2) + (coef4 * v3)
59
- end
60
-
61
- # super smoother 3 pole
62
- def ss3p(source, prev_source, ssperiod)
63
- p0 = points[-1]
64
- p1 = points[-2] || p0
65
- p2 = points[-3] || p1
66
- p3 = points[-4] || p2
67
-
68
- v0 = source.is_a?(Symbol) ? p0.send(source) : source
69
- return v0 if p0 == p3
70
-
71
- a1 = Math.exp(-Math::PI / ssperiod)
72
- b1 = 2 * a1 * Math.cos(Math::PI * Math.sqrt(3) / ssperiod)
27
+ a1 = Math.exp(-Math::PI / period)
28
+ b1 = 2 * a1 * Math.cos(Math::PI * Math.sqrt(3) / period)
73
29
  c1 = a1**2
74
30
 
75
31
  coef2 = b1 + c1
@@ -77,56 +33,14 @@ module Quant
77
33
  coef4 = c1**2
78
34
  coef1 = 1 - coef2 - coef3 - coef4
79
35
 
80
- v1 = p1.send(prev_source)
81
- v2 = p2.send(prev_source)
82
- v3 = p3.send(prev_source)
36
+ v0 = p0.send(source)
37
+ v1 = p1.send(previous)
38
+ v2 = p2.send(previous)
39
+ v3 = p3.send(previous)
40
+
83
41
  (coef1 * v0) + (coef2 * v1) + (coef3 * v2) + (coef4 * v3)
84
42
  end
85
-
86
- # attr_reader :hpfs, :value1s, :hpf_psns
87
-
88
- # def hpf
89
- # @hpfs[-1]
90
- # end
91
-
92
- # def hpf_psn
93
- # @hpf_psns[-1]
94
- # end
95
-
96
- # def prev offset, source
97
- # idx = offset + 1
98
- # source[[-idx, -source.size].max]
99
- # end
100
-
101
- # def weighted_average source
102
- # [ 4.0 * prev(0, source),
103
- # 3.0 * prev(1, source),
104
- # 2.0 * prev(2, source),
105
- # prev(3, source),
106
- # ].sum / 10.0
107
- # end
108
-
109
- # def compute_hpf
110
- # @hpfs ||= []
111
- # @value1s ||= []
112
- # @hpf_psns ||= []
113
- # max_cycle = period * 10
114
-
115
- # r = (360.0 / max_cycle) * (Math::PI / 180)
116
- # alpha = (1 - Math::sin(r)) / Math::cos(r)
117
- # hpf = @hpfs.empty? ? 0.0 : (0.5 * (1.0 + alpha) * (current_value - prev_value(1))) + (alpha * (@hpfs[-1]))
118
-
119
- # @hpfs << hpf
120
- # @hpfs.shift if @hpfs.size > max_cycle
121
-
122
- # hh = @hpfs.max
123
- # ll = @hpfs.min
124
- # @value1s << value1 = (hh == ll ? 0.0 : 100 * (hpf - ll) / (hh - ll))
125
- # @value1s.shift if @value1s.size > max_cycle
126
-
127
- # @hpf_psns << weighted_average(@value1s)
128
- # @hpf_psns.shift if @hpf_psns.size > max_cycle
129
- # end
43
+ alias ss3p three_pole_super_smooth
130
44
  end
131
45
  end
132
46
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quant
4
+ module Mixins
5
+ module WeightedMovingAverage
6
+ using Quant
7
+ # Computes the Weighted Moving Average (WMA) of the series, using the four most recent data points.
8
+ #
9
+ # @param source [Symbol] the source of the data points to be used in the calculation.
10
+ # @return [Float] the weighted average of the series.
11
+ # @raise [ArgumentError] if the source is not a Symbol.
12
+ # @example
13
+ # p0.wma = weighted_average(:close_price)
14
+ def weighted_moving_average(source)
15
+ raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
16
+
17
+ [4.0 * p0.send(source),
18
+ 3.0 * p1.send(source),
19
+ 2.0 * p2.send(source),
20
+ p3.send(source)].sum / 10.0
21
+ end
22
+ alias wma weighted_moving_average
23
+
24
+ # Computes the Weighted Moving Average (WMA) of the series, using the seven most recent data points.
25
+ #
26
+ # @param source [Symbol] the source of the data points to be used in the calculation.
27
+ # @return [Float] the weighted average of the series.
28
+ # @raise [ArgumentError] if the source is not a Symbol.
29
+ # @example
30
+ # p0.wma = weighted_average(:close_price)
31
+ def extended_weighted_moving_average(source)
32
+ raise ArgumentError, "source must be a Symbol" unless source.is_a?(Symbol)
33
+
34
+ [7.0 * p0.send(source),
35
+ 6.0 * p1.send(source),
36
+ 5.0 * p2.send(source),
37
+ 4.0 * p3.send(source),
38
+ 3.0 * p(4).send(source),
39
+ 2.0 * p(5).send(source),
40
+ p(6).send(source)].sum / 28.0
41
+ end
42
+ alias ewma extended_weighted_moving_average
43
+ end
44
+ end
45
+ 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: n)**0.5
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: symbol, interval: interval, hash: ticks, serializer_class: serializer_class
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
- ticks = Oj.load(json)
33
- from_hash symbol: symbol, interval: interval, hash: ticks, serializer_class: serializer_class
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: serializer_class) }
43
- from_ticks symbol: symbol, interval: interval, ticks: ticks
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: symbol, interval: interval).tap do |series|
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: symbol, interval: interval, ticks: selected_ticks)
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: symbol, interval: interval, ticks: selected_ticks)
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: symbol, interval: interval, ticks: ticks)
97
+ self.class.from_ticks(symbol:, interval:, ticks:)
98
98
  end
99
99
 
100
100
  def inspect
@@ -4,11 +4,15 @@ require_relative "tick"
4
4
 
5
5
  module Quant
6
6
  module Ticks
7
- # An {Quant::Ticks::OHLC} is a bar or candle for a point in time that has an open, high, low, and close price.
8
- # It is the most common form of a {Quant::Ticks::Tick} and is usually used to representa time period such as a
9
- # minute, hour, day, week, or month. The {Quant::Ticks::OHLC} is used to represent the price action of an asset
10
- # The interval of the {Quant::Ticks::OHLC} is the time period that the {Quant::Ticks::OHLC} represents,
11
- # such has hourly, daily, weekly, etc.
7
+ # A {Quant::Ticks::OHLC} is a bar or candle for a point in time that
8
+ # has an open, high, low, and close price. It is the most common form
9
+ # of a {Quant::Ticks::Tick} and is usually used to representa time
10
+ # period such as a minute, hour, day, week, or month.
11
+ #
12
+ # The {Quant::Ticks::OHLC} is used to represent the price action of
13
+ # an asset The interval of the {Quant::Ticks::OHLC} is the time period
14
+ # that the {Quant::Ticks::OHLC} represents, such has hourly, daily,
15
+ # weekly, etc.
12
16
  class OHLC < Tick
13
17
  include TimeMethods
14
18
 
@@ -3,7 +3,7 @@
3
3
  module Quant
4
4
  module Ticks
5
5
  module Serializers
6
- module OHLC
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: 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: tick_class)
16
+ from(hash, tick_class:)
17
17
  end
18
18
 
19
19
  # Returns a +Hash+ of the Spot tick's key properties
@@ -69,7 +69,7 @@ module Quant
69
69
  # Quant::Ticks::Serializers::Tick.from_json(json, tick_class: Quant::Ticks::Spot)
70
70
  def self.from_json(json, tick_class:)
71
71
  hash = Oj.load(json)
72
- from(hash, tick_class: tick_class)
72
+ from(hash, tick_class:)
73
73
  end
74
74
  end
75
75
  end
@@ -2,22 +2,29 @@
2
2
 
3
3
  module Quant
4
4
  module Ticks
5
- # {Quant::Ticks::Tick} is the abstract ancestor for all Ticks and holds the logic for interacting with series and indicators.
6
- # The public interface is devoid of properties around price, volume, and timestamp, etc. Descendant classes
7
- # are responsible for defining the properties and how they are represented.
8
- #
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
- # indicators that depend on the ticks within the series.
12
- #
13
- # When a tick is added to a series, it is locked into the series and ownership cannot be changed. This is important
14
- # for the integrity of the series and indicators that depend on the ticks within the series. This is a key design
15
- # to being able to being able to not only compute indicators on the ticks just once, but also avoid recomputing
16
- # indicators when series are limited/sliced/filtered into subsets of the original series.
17
- #
5
+ # {Quant::Ticks::Tick} is the abstract ancestor for all Ticks and holds
6
+ # the logic for interacting with series and indicators. The public
7
+ # interface is devoid of properties around price, volume, and timestamp, etc.
8
+ # Descendant classes are responsible for defining the properties and
9
+ # how they are represented.
10
+
11
+ # The {Quant::Ticks::Tick} class is designed to be immutable and is
12
+ # intended to be used as a value object. This means that once a
13
+ # {Quant::Ticks::Tick} is created, it cannot be changed. This is important
14
+ # for the integrity of the series and indicators that depend on the
15
+ # ticks within the series.
16
+
17
+ # When a tick is added to a series, it is locked into the series and
18
+ # ownership cannot be changed. This is important for the integrity
19
+ # of the series and indicators that depend on the ticks within the series.
20
+ # This is a key design to being able to being able to not only compute
21
+ # indicators on the ticks just once, but also avoid recomputing indicators
22
+ # when series are limited/sliced/filtered into subsets of the original series.
23
+
18
24
  # Ticks can be serialized to and from Ruby Hash, JSON strings, and CSV strings.
19
25
  class Tick
20
- # Returns a {Quant::Ticks::Tick} from a Ruby +Hash+. The default serializer is used to generate the {Quant::Ticks::Tick}.
26
+ # Returns a {Quant::Ticks::Tick} from a Ruby +Hash+. The default
27
+ # serializer is used to generate the {Quant::Ticks::Tick}.
21
28
  # @param hash [Hash]
22
29
  # @param serializer_class [Class] The serializer class to use for the conversion.
23
30
  # @return [Quant::Ticks::Tick]
@@ -30,7 +37,8 @@ module Quant
30
37
  serializer_class.from(hash, tick_class: self)
31
38
  end
32
39
 
33
- # Returns a {Quant::Ticks::Tick} from a JSON string. The default serializer is used to generate the {Quant::Ticks::Tick}.
40
+ # Returns a {Quant::Ticks::Tick} from a JSON string. The default
41
+ # serializer is used to generate the {Quant::Ticks::Tick}.
34
42
  # @param json [String]
35
43
  # @param serializer_class [Class] The serializer class to use for the conversion.
36
44
  # @return [Quant::Ticks::Tick]
@@ -80,7 +88,8 @@ module Quant
80
88
  self
81
89
  end
82
90
 
83
- # Returns a Ruby hash for the Tick. The default serializer is used to generate the hash.
91
+ # Returns a Ruby hash for the Tick. The default serializer is used
92
+ # to generate the hash.
84
93
  #
85
94
  # @param serializer_class [Class] the serializer class to use for the conversion.
86
95
  # @example
@@ -90,7 +99,8 @@ module Quant
90
99
  serializer_class.to_h(self)
91
100
  end
92
101
 
93
- # Returns a JSON string for the Tick. The default serializer is used to generate the JSON string.
102
+ # Returns a JSON string for the Tick. The default serializer is used
103
+ # to generate the JSON string.
94
104
  #
95
105
  # @param serializer_class [Class] the serializer class to use for the conversion.
96
106
  # @example
@@ -100,8 +110,9 @@ module Quant
100
110
  serializer_class.to_json(self)
101
111
  end
102
112
 
103
- # Returns a CSV row as a String for the Tick. The default serializer is used to generate the CSV string.
104
- # If headers is true, two lines returned separated by newline.
113
+ # Returns a CSV row as a String for the Tick. The default serializer
114
+ # is used to generate the CSV string. If headers is true, two lines
115
+ # returned separated by newline.
105
116
  # The first line is the header row and the second line is the data row.
106
117
  #
107
118
  # @param serializer_class [Class] the serializer class to use for the conversion.
@@ -109,7 +120,7 @@ module Quant
109
120
  # tick.to_csv(headers: true)
110
121
  # # => "timestamp,price,volume\n2018-01-01 12:00:00 UTC,100.0,1000\n"
111
122
  def to_csv(serializer_class: default_serializer_class, headers: false)
112
- serializer_class.to_csv(self, headers: headers)
123
+ serializer_class.to_csv(self, headers:)
113
124
  end
114
125
 
115
126
  # Reflects the serializer class from the tick's class name.
@@ -72,7 +72,7 @@ module Quant
72
72
  end
73
73
 
74
74
  def to_h
75
- { start_at: start_at, end_at: end_at }
75
+ { start_at:, end_at: }
76
76
  end
77
77
  end
78
78
  end
data/lib/quant/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quant
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.8"
5
5
  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.6
4
+ version: 0.1.8
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-25 00:00:00.000000000 Z
11
+ date: 2024-02-27 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.x for trading and
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,15 +57,19 @@ 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
62
+ - lib/quant/mixins/exponential_moving_average.rb
61
63
  - lib/quant/mixins/filters.rb
62
64
  - lib/quant/mixins/fisher_transform.rb
65
+ - lib/quant/mixins/functions.rb
63
66
  - lib/quant/mixins/high_pass_filter.rb
64
67
  - lib/quant/mixins/hilbert_transform.rb
65
68
  - lib/quant/mixins/moving_averages.rb
69
+ - lib/quant/mixins/simple_moving_average.rb
66
70
  - lib/quant/mixins/stochastic.rb
67
71
  - lib/quant/mixins/super_smoother.rb
68
- - lib/quant/mixins/trig.rb
72
+ - lib/quant/mixins/weighted_moving_average.rb
69
73
  - lib/quant/refinements/array.rb
70
74
  - lib/quant/series.rb
71
75
  - lib/quant/settings.rb
@@ -96,7 +100,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
96
100
  requirements:
97
101
  - - ">="
98
102
  - !ruby/object:Gem::Version
99
- version: 3.0.0
103
+ version: '3.2'
100
104
  required_rubygems_version: !ruby/object:Gem::Requirement
101
105
  requirements:
102
106
  - - ">="
@@ -106,5 +110,6 @@ requirements: []
106
110
  rubygems_version: 3.5.6
107
111
  signing_key:
108
112
  specification_version: 4
109
- summary: Quantitative and statistical tools written for Ruby 3.x for trading and finance.
113
+ summary: Quantitative and statistical tools written for Ruby 3.2+ for trading and
114
+ finance.
110
115
  test_files: []