ruby-technical-analysis 0.1.1 → 1.0.4
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 +4 -4
- data/lib/ruby-technical-analysis.rb +1 -0
- data/lib/ruby_technical_analysis/indicator.rb +41 -0
- data/lib/ruby_technical_analysis/indicators/bollinger_bands.rb +50 -0
- data/lib/ruby_technical_analysis/indicators/chaikin_money_flow.rb +47 -0
- data/lib/ruby_technical_analysis/indicators/chande_momentum_oscillator.rb +56 -0
- data/lib/ruby_technical_analysis/indicators/commodity_channel_index.rb +70 -0
- data/lib/ruby_technical_analysis/indicators/envelopes_ema.rb +48 -0
- data/lib/ruby_technical_analysis/indicators/intraday_momentum_index.rb +45 -0
- data/lib/ruby_technical_analysis/indicators/macd.rb +86 -0
- data/lib/ruby_technical_analysis/indicators/mass_index.rb +70 -0
- data/lib/ruby_technical_analysis/indicators/moving_averages.rb +71 -0
- data/lib/ruby_technical_analysis/indicators/pivot_points.rb +75 -0
- data/lib/ruby_technical_analysis/indicators/price_channel.rb +48 -0
- data/lib/ruby_technical_analysis/indicators/qstick.rb +44 -0
- data/lib/ruby_technical_analysis/indicators/rate_of_change.rb +40 -0
- data/lib/ruby_technical_analysis/indicators/relative_momentum_index.rb +79 -0
- data/lib/ruby_technical_analysis/indicators/relative_strength_index.rb +74 -0
- data/lib/ruby_technical_analysis/indicators/statistical_methods.rb +40 -0
- data/lib/ruby_technical_analysis/indicators/stochastic_oscillator.rb +99 -0
- data/lib/ruby_technical_analysis/indicators/volume_oscillator.rb +50 -0
- data/lib/ruby_technical_analysis/indicators/volume_rate_of_change.rb +48 -0
- data/lib/ruby_technical_analysis/indicators/wilders_smoothing.rb +47 -0
- data/lib/ruby_technical_analysis/indicators/williams_percent_r.rb +69 -0
- data/lib/ruby_technical_analysis.rb +24 -7
- data/spec/ruby_technical_analysis/indicator_spec.rb +76 -0
- data/spec/ruby_technical_analysis/indicators/bollinger_bands_spec.rb +67 -0
- data/spec/ruby_technical_analysis/indicators/chaikin_money_flow_spec.rb +63 -0
- data/spec/ruby_technical_analysis/indicators/chande_momentum_oscillator_spec.rb +59 -0
- data/spec/ruby_technical_analysis/indicators/commodity_channel_index_spec.rb +66 -0
- data/spec/ruby_technical_analysis/indicators/envelopes_ema_spec.rb +69 -0
- data/spec/ruby_technical_analysis/indicators/intraday_momentum_spec.rb +63 -0
- data/spec/ruby_technical_analysis/indicators/macd_spec.rb +61 -0
- data/spec/ruby_technical_analysis/indicators/mass_index_spec.rb +67 -0
- data/spec/ruby_technical_analysis/indicators/moving_averages_spec.rb +81 -0
- data/spec/ruby_technical_analysis/indicators/pivot_points_spec.rb +43 -0
- data/spec/ruby_technical_analysis/indicators/price_channel_spec.rb +64 -0
- data/spec/ruby_technical_analysis/indicators/qstick_spec.rb +59 -0
- data/spec/ruby_technical_analysis/indicators/rate_of_change_spec.rb +59 -0
- data/spec/ruby_technical_analysis/indicators/relative_momentum_index_spec.rb +67 -0
- data/spec/ruby_technical_analysis/indicators/relative_strength_index_spec.rb +59 -0
- data/spec/ruby_technical_analysis/indicators/statistical_methods_spec.rb +91 -0
- data/spec/ruby_technical_analysis/indicators/stochastic_oscillator_spec.rb +106 -0
- data/spec/ruby_technical_analysis/indicators/volume_oscillator_spec.rb +98 -0
- data/spec/ruby_technical_analysis/indicators/volume_rate_of_change_spec.rb +67 -0
- data/spec/ruby_technical_analysis/indicators/wiilders_smoothing_spec.rb +67 -0
- data/spec/ruby_technical_analysis/indicators/williams_percent_r_spec.rb +71 -0
- data/spec/spec_helper.rb +1 -0
- metadata +100 -40
- data/.rubocop.yml +0 -34
- data/CHANGELOG.md +0 -5
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -12
- data/LICENSE.txt +0 -21
- data/README.md +0 -36
- data/Rakefile +0 -16
- data/lib/ruby-technical-analysis/indicators/bollinger_bands.rb +0 -25
- data/lib/ruby-technical-analysis/indicators/chaikin_money_flow.rb +0 -70
- data/lib/ruby-technical-analysis/indicators/chande_momentum_oscillator.rb +0 -34
- data/lib/ruby-technical-analysis/indicators/commodity_channel_index.rb +0 -64
- data/lib/ruby-technical-analysis/indicators/envelopes_ema.rb +0 -24
- data/lib/ruby-technical-analysis/indicators/intraday_momentum_index.rb +0 -48
- data/lib/ruby-technical-analysis/indicators/macd.rb +0 -47
- data/lib/ruby-technical-analysis/indicators/mass_index.rb +0 -73
- data/lib/ruby-technical-analysis/indicators/pivot_points.rb +0 -23
- data/lib/ruby-technical-analysis/indicators/price_channel.rb +0 -37
- data/lib/ruby-technical-analysis/indicators/qstick.rb +0 -40
- data/lib/ruby-technical-analysis/indicators/rate_of_change.rb +0 -18
- data/lib/ruby-technical-analysis/indicators/relative_momentum_index.rb +0 -66
- data/lib/ruby-technical-analysis/indicators/relative_strength_index.rb +0 -63
- data/lib/ruby-technical-analysis/indicators/stochastic_oscillator.rb +0 -65
- data/lib/ruby-technical-analysis/indicators/volume_oscillator.rb +0 -38
- data/lib/ruby-technical-analysis/indicators/volume_rate_of_change.rb +0 -26
- data/lib/ruby-technical-analysis/indicators/wilders_smoothing.rb +0 -27
- data/lib/ruby-technical-analysis/indicators/williams_percent_r.rb +0 -52
- data/lib/ruby-technical-analysis/moving_averages.rb +0 -85
- data/lib/ruby-technical-analysis/statistical_methods.rb +0 -24
- data/lib/ruby-technical-analysis/version.rb +0 -5
- data/sig/ruby-technical-analysis.rbs +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbed0351a5e66537192cbd5b27244ee4e4d8f7148ef513a594c55bb543ad3c01
|
4
|
+
data.tar.gz: 07513c6544f36eda98693cd137e365b36c6f532e75b889c1016bc64313f150e1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71d2b64e698c348059d573fbb998042fbe539c0f6d9244d884df651df51ab2e0d0c56384e3e9816d039a9a4cefbeb379edc282e86e2fc87956664034808d9c57
|
7
|
+
data.tar.gz: 95b7207c806a264af926d10c202087547332326a2fdb70485c88b7628c67cf9d64df2a5c8db2265d3c5e67600b5b9e8f24bd2d358b169d7ab8f2918e7ec3d69c
|
@@ -0,0 +1 @@
|
|
1
|
+
require "ruby_technical_analysis"
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module RubyTechnicalAnalysis
|
2
|
+
# Base class for indicators
|
3
|
+
class Indicator
|
4
|
+
attr_reader :series
|
5
|
+
|
6
|
+
def self.call(**kwargs) # standard:disable Style/ArgumentsForwarding
|
7
|
+
new(**kwargs).call # standard:disable Style/ArgumentsForwarding
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param series [Array] An array of prices
|
11
|
+
def initialize(series: [])
|
12
|
+
@series = series
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def extract_series(subset_length: nil)
|
18
|
+
subset_length.nil? ? series : series.last(subset_length)
|
19
|
+
end
|
20
|
+
|
21
|
+
def extract_highs_lows_closes_volumes(subset_length: nil)
|
22
|
+
highs, lows, closes, volumes = extract_series(subset_length: subset_length).transpose
|
23
|
+
|
24
|
+
[highs, lows, closes, volumes]
|
25
|
+
end
|
26
|
+
|
27
|
+
def extract_highs_lows_closes(subset_length: nil)
|
28
|
+
highs, lows, closes = extract_series(subset_length: subset_length).transpose
|
29
|
+
|
30
|
+
[highs, lows, closes]
|
31
|
+
end
|
32
|
+
|
33
|
+
def moving_averages(subset_length: nil, period: nil)
|
34
|
+
RubyTechnicalAnalysis::MovingAverages.new(series: extract_series(subset_length: subset_length), period: period)
|
35
|
+
end
|
36
|
+
|
37
|
+
def statistical_methods(subset_length: nil)
|
38
|
+
RubyTechnicalAnalysis::StatisticalMethods.new(series: extract_series(subset_length: subset_length))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RubyTechnicalAnalysis
|
2
|
+
# Bollinger Bands
|
3
|
+
#
|
4
|
+
# Find more information at: https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/bollinger-bands
|
5
|
+
class BollingerBands < Indicator
|
6
|
+
attr_reader :period, :standard_deviations
|
7
|
+
|
8
|
+
# @param series [Array] An array of prices, typically closing prices
|
9
|
+
# @param period [Integer] The number of periods to use in the calculation
|
10
|
+
# @param standard_deviations [Integer] The number of standard deviations to use in the calculation
|
11
|
+
def initialize(series: [], period: 20, standard_deviations: 2)
|
12
|
+
@period = period
|
13
|
+
@standard_deviations = standard_deviations
|
14
|
+
|
15
|
+
super(series: series)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Array] An array containing the current upper, middle, and lower bands of the series
|
19
|
+
def call
|
20
|
+
calculate_bollinger_bands
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Boolean] Whether or not the object is valid
|
24
|
+
def valid?
|
25
|
+
period <= series.length && standard_deviations > 0
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def _middle_price
|
31
|
+
@_middle_price ||= moving_averages(period: period).sma
|
32
|
+
end
|
33
|
+
|
34
|
+
def _twice_sd
|
35
|
+
@_twice_sd ||= standard_deviations * statistical_methods.standard_deviation
|
36
|
+
end
|
37
|
+
|
38
|
+
def upper_band
|
39
|
+
_middle_price + _twice_sd
|
40
|
+
end
|
41
|
+
|
42
|
+
def lower_band
|
43
|
+
_middle_price - _twice_sd
|
44
|
+
end
|
45
|
+
|
46
|
+
def calculate_bollinger_bands
|
47
|
+
[upper_band, _middle_price, lower_band].map { |band| band.truncate(3) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module RubyTechnicalAnalysis
|
2
|
+
# Chaikin Money Flow
|
3
|
+
#
|
4
|
+
# Find more information at: https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/cmf
|
5
|
+
class ChaikinMoneyFlow < Indicator
|
6
|
+
attr_reader :period
|
7
|
+
|
8
|
+
# @param series [Array] An array of arrays containing high, low, close, and volume information, e.g. [[high, low, close, volume], [high, low, close, volume]]
|
9
|
+
# @param period [Integer] The number of periods to use in the calculation
|
10
|
+
def initialize(series: [], period: 21)
|
11
|
+
@period = period
|
12
|
+
@cmf_sum = 0
|
13
|
+
@vol_sum = 0
|
14
|
+
|
15
|
+
super(series: series)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Float] The current Chaikin Money Flow value
|
19
|
+
def call
|
20
|
+
calculate_cmf
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Boolean] Whether or not the object is valid
|
24
|
+
def valid?
|
25
|
+
period <= series.length
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def calculate_cmf_sum
|
31
|
+
highs, lows, closes, volumes = extract_highs_lows_closes_volumes(subset_length: period)
|
32
|
+
|
33
|
+
period.times do |index|
|
34
|
+
high, low, close, volume = highs.at(index), lows.at(index), closes.at(index), volumes.at(index)
|
35
|
+
|
36
|
+
@vol_sum += volume
|
37
|
+
@cmf_sum += (((close - low) - (high - close)).to_f / (high - low)) * volume
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def calculate_cmf
|
42
|
+
calculate_cmf_sum
|
43
|
+
|
44
|
+
(@vol_sum.zero? ? 0 : @cmf_sum.to_f / @vol_sum).round(5)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module RubyTechnicalAnalysis
|
2
|
+
# Chande Momentum Oscillator
|
3
|
+
#
|
4
|
+
# Find more information at: https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/cmo
|
5
|
+
class ChandeMomentumOscillator < Indicator
|
6
|
+
attr_reader :period
|
7
|
+
|
8
|
+
# @param series [Array] An array of prices, typically closing prices
|
9
|
+
# @param period [Integer] The number of periods to use in the calculation
|
10
|
+
def initialize(series: [], period: 20)
|
11
|
+
@period = period
|
12
|
+
@up_change_sum = 0
|
13
|
+
@down_change_sum = 0
|
14
|
+
|
15
|
+
super(series: series)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Float] The current Chande Momentum Oscillator value
|
19
|
+
def call
|
20
|
+
calculate_cmo
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Boolean] Whether or not the object is valid
|
24
|
+
def valid?
|
25
|
+
period + 1 <= series.length
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def _closes
|
31
|
+
@_closes ||= extract_series(subset_length: period + 1)
|
32
|
+
end
|
33
|
+
|
34
|
+
def calculate_change_sums
|
35
|
+
(1..period).each do |index|
|
36
|
+
price_diff = _closes.at(index) - _closes.at(index - 1)
|
37
|
+
@up_change_sum += price_diff if price_diff.positive?
|
38
|
+
@down_change_sum -= price_diff if price_diff.negative?
|
39
|
+
end
|
40
|
+
|
41
|
+
@up_change_sum + @down_change_sum
|
42
|
+
end
|
43
|
+
|
44
|
+
def _up_sum_plus_down_sum
|
45
|
+
@_up_sum_plus_down_sum ||= calculate_change_sums
|
46
|
+
end
|
47
|
+
|
48
|
+
def calculate_oscillator_value
|
49
|
+
(@up_change_sum - @down_change_sum).to_f / _up_sum_plus_down_sum * 100
|
50
|
+
end
|
51
|
+
|
52
|
+
def calculate_cmo
|
53
|
+
_up_sum_plus_down_sum.zero? ? 0 : calculate_oscillator_value.round(4)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module RubyTechnicalAnalysis
|
2
|
+
# Commodity Channel Index
|
3
|
+
#
|
4
|
+
# Find more information at: https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/cci
|
5
|
+
class CommodityChannelIndex < Indicator
|
6
|
+
attr_reader :period
|
7
|
+
|
8
|
+
# @param series [Array] An array of arrays containing high, low, close information, e.g. [[high, low, close], [high, low, close]]
|
9
|
+
# @param period [Integer] The number of periods to use in the calculation
|
10
|
+
def initialize(series: [], period: 20)
|
11
|
+
@period = period
|
12
|
+
|
13
|
+
super(series: series)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Float] The current Commodity Channel Index value
|
17
|
+
def call
|
18
|
+
calculate_cci
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Boolean] Whether or not the object is valid
|
22
|
+
def valid?
|
23
|
+
min_size <= series.length
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def min_size
|
29
|
+
(period * 2 - 1)
|
30
|
+
end
|
31
|
+
|
32
|
+
def calculate_typical_prices
|
33
|
+
highs, lows, closes = extract_highs_lows_closes(subset_length: min_size)
|
34
|
+
|
35
|
+
highs.zip(lows, closes).map { |high, low, close| (high + low + close) / 3 }
|
36
|
+
end
|
37
|
+
|
38
|
+
def _typical_prices
|
39
|
+
@_typical_prices ||= calculate_typical_prices
|
40
|
+
end
|
41
|
+
|
42
|
+
def _typical_prices_sma
|
43
|
+
@_typical_prices_sma ||=
|
44
|
+
_typical_prices.each_cons(period).map do |tp|
|
45
|
+
RubyTechnicalAnalysis::MovingAverages.new(series: tp, period: period).sma
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def _period_sum
|
50
|
+
@_period_sum ||=
|
51
|
+
_typical_prices.last(period).sum do |tp|
|
52
|
+
(_typical_prices_sma.last - tp).abs
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def _period_sum_next_period
|
57
|
+
@_period_sum_next_period ||=
|
58
|
+
(_period_sum.to_f / period) * 0.015
|
59
|
+
end
|
60
|
+
|
61
|
+
def _typical_prices_sma_minus_typical_prices
|
62
|
+
@_typical_prices_sma_minus_typical_prices ||=
|
63
|
+
_typical_prices.last(period).last - _typical_prices_sma.last
|
64
|
+
end
|
65
|
+
|
66
|
+
def calculate_cci
|
67
|
+
(_typical_prices_sma_minus_typical_prices.to_f / _period_sum_next_period)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RubyTechnicalAnalysis
|
2
|
+
# Envelopes EMA
|
3
|
+
#
|
4
|
+
# Find more information at: https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/mae
|
5
|
+
#
|
6
|
+
# Note that this indicator is similar but not the exact same as the one in the link above. This indicator is based on the EMA, not the SMA.
|
7
|
+
class EnvelopesEma < Indicator
|
8
|
+
attr_reader :period, :percent
|
9
|
+
|
10
|
+
# @param series [Array] An array of prices, typically closing prices
|
11
|
+
# @param period [Integer] The number of periods to use in the calculation
|
12
|
+
# @param percent [Integer] The percent to use in the calculation
|
13
|
+
def initialize(series: [], period: 20, percent: 5)
|
14
|
+
@period = period
|
15
|
+
@percent = percent
|
16
|
+
|
17
|
+
super(series: series)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Array] An array containing the current upper, middle, and lower bands of the series
|
21
|
+
def call
|
22
|
+
caluculate_envelopes_ema
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Boolean] Whether or not the object is valid
|
26
|
+
def valid?
|
27
|
+
period <= series.length && percent <= 100
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def _eema
|
33
|
+
@_eema ||= moving_averages(period: period).ema
|
34
|
+
end
|
35
|
+
|
36
|
+
def eema_up
|
37
|
+
(_eema * (100 + percent)) / 100
|
38
|
+
end
|
39
|
+
|
40
|
+
def eema_down
|
41
|
+
(_eema * (100 - percent)) / 100
|
42
|
+
end
|
43
|
+
|
44
|
+
def caluculate_envelopes_ema
|
45
|
+
[eema_up, _eema, eema_down].map { |val| val.truncate(3) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module RubyTechnicalAnalysis
|
2
|
+
# Intraday Momentum Index
|
3
|
+
#
|
4
|
+
# Find more information at: https://www.investopedia.com/terms/i/intraday-momentum-index-imi.asp
|
5
|
+
class IntradayMomentumIndex < Indicator
|
6
|
+
attr_reader :period
|
7
|
+
|
8
|
+
# @param series [Array] An array of arrays containing open, close prices, e.g. [[open, close], [open, close]]
|
9
|
+
# @param period [Integer] The number of periods to use in the calculation
|
10
|
+
def initialize(series: [], period: 14)
|
11
|
+
@period = period
|
12
|
+
@gsum = 0
|
13
|
+
@lsum = 0
|
14
|
+
|
15
|
+
super(series: series)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Float] The current Intraday Momentum Index value
|
19
|
+
def call
|
20
|
+
calculate_imi
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Boolean] Whether or not the object is valid
|
24
|
+
def valid?
|
25
|
+
period <= series.length
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def calculate_gsum_plus_lsum
|
31
|
+
series.last(period).each do |open, close|
|
32
|
+
cmo = (close - open).abs
|
33
|
+
(close > open) ? @gsum += cmo : @lsum += cmo
|
34
|
+
end
|
35
|
+
|
36
|
+
@gsum + @lsum
|
37
|
+
end
|
38
|
+
|
39
|
+
def calculate_imi
|
40
|
+
gsum_plus_lsum = calculate_gsum_plus_lsum
|
41
|
+
|
42
|
+
(gsum_plus_lsum.zero? ? 0 : (@gsum / gsum_plus_lsum) * 100).round(4)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module RubyTechnicalAnalysis
|
2
|
+
# Moving Average Convergence Divergence (MACD)
|
3
|
+
#
|
4
|
+
# Find more information at: https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/macd
|
5
|
+
class Macd < Indicator
|
6
|
+
attr_reader :fast_period, :slow_period, :signal_period
|
7
|
+
|
8
|
+
# @param series [Array] An array of prices, typically closing prices
|
9
|
+
# @param fast_period [Integer] The number of periods to use in the fast calculation
|
10
|
+
# @param slow_period [Integer] The number of periods to use in the slow calculation
|
11
|
+
# @param signal_period [Integer] The number of periods to use in the signal calculation
|
12
|
+
def initialize(series: [], fast_period: 12, slow_period: 26, signal_period: 9)
|
13
|
+
@fast_period = fast_period
|
14
|
+
@slow_period = slow_period
|
15
|
+
@signal_period = signal_period
|
16
|
+
|
17
|
+
super(series: series)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Array] An array containing the current MACD line, signal line, and histogram values
|
21
|
+
def call
|
22
|
+
calculate_macd
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Boolean] Whether or not the object is valid
|
26
|
+
def valid?
|
27
|
+
series.length >= slow_period + signal_period
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def fast_pct
|
33
|
+
(2.0 / (fast_period + 1)).truncate(6)
|
34
|
+
end
|
35
|
+
|
36
|
+
def slow_pct
|
37
|
+
(2.0 / (slow_period + 1)).truncate(6)
|
38
|
+
end
|
39
|
+
|
40
|
+
def period_array(percent)
|
41
|
+
period_values = Array(series.first)
|
42
|
+
|
43
|
+
series.drop(1).each_with_index do |value, index|
|
44
|
+
period_values << ((value * percent) + (period_values.last * (1 - percent))).round(3)
|
45
|
+
end
|
46
|
+
|
47
|
+
period_values
|
48
|
+
end
|
49
|
+
|
50
|
+
def _fast_period_array
|
51
|
+
@_fast_period_array ||= period_array(fast_pct)
|
52
|
+
end
|
53
|
+
|
54
|
+
def _slow_period_array
|
55
|
+
@_slow_period_array ||= period_array(slow_pct)
|
56
|
+
end
|
57
|
+
|
58
|
+
def _signal_array
|
59
|
+
@_signal_array ||= (0..signal_period - 1).map do |index|
|
60
|
+
calculated_index = slow_period + index - 1
|
61
|
+
|
62
|
+
(_fast_period_array.at(calculated_index) - _slow_period_array.at(calculated_index)).round(3)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def _signal_pct
|
67
|
+
@_signal_pct ||= (2.0 / (signal_period + 1)).truncate(6)
|
68
|
+
end
|
69
|
+
|
70
|
+
def _signal
|
71
|
+
@_signal ||= ((_signal_array.last * _signal_pct) + (_signal_array.at(-2) * (1 - _signal_pct))).round(3)
|
72
|
+
end
|
73
|
+
|
74
|
+
def _macd_line
|
75
|
+
@_macd_line ||= (_fast_period_array.last - _slow_period_array.last).round(4)
|
76
|
+
end
|
77
|
+
|
78
|
+
def histogram
|
79
|
+
(_macd_line - _signal).round(3)
|
80
|
+
end
|
81
|
+
|
82
|
+
def calculate_macd
|
83
|
+
[_macd_line, _signal, histogram]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module RubyTechnicalAnalysis
|
2
|
+
# Mass Index
|
3
|
+
#
|
4
|
+
# Find more information at: https://www.investopedia.com/terms/m/mass-index.asp
|
5
|
+
class MassIndex < Indicator
|
6
|
+
attr_reader :period
|
7
|
+
|
8
|
+
# @param series [Array] An array of arrays containing high and low prices, e.g. [[high, low], [high, low]]
|
9
|
+
# @param period [Integer] The number of periods to use in the calculation
|
10
|
+
def initialize(series: [], period: 9)
|
11
|
+
@period = period
|
12
|
+
|
13
|
+
super(series: series)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Float] The current Mass Index value
|
17
|
+
def call
|
18
|
+
calculate_mass_index
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Boolean] Whether or not the object is valid
|
22
|
+
def valid?
|
23
|
+
series.length >= _full_period
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def _highs
|
29
|
+
@_highs ||= series.map(&:first).last(_full_period)
|
30
|
+
end
|
31
|
+
|
32
|
+
def lows
|
33
|
+
series.map(&:last).last(_full_period)
|
34
|
+
end
|
35
|
+
|
36
|
+
def _full_period
|
37
|
+
@_full_period ||= (2 * period + 1)
|
38
|
+
end
|
39
|
+
|
40
|
+
def _low_multiple
|
41
|
+
@_low_multiple ||= (2.0 / (period + 1)).truncate(4)
|
42
|
+
end
|
43
|
+
|
44
|
+
def high_multiple
|
45
|
+
1 - _low_multiple
|
46
|
+
end
|
47
|
+
|
48
|
+
def high_minus_low_array
|
49
|
+
(0..(_highs.size - 1)).map { |index| _highs.at(index) - lows.at(index) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def _high_minus_low_ema_array
|
53
|
+
@_high_minus_low_ema_array ||= high_minus_low_array.each_with_index.reduce([]) do |arr, (value, index)|
|
54
|
+
arr << (index.zero? ? value.truncate(4) : ((value * _low_multiple) + (arr.at(index - 1) * high_multiple)).truncate(4))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def high_minus_low_ema_ema_array
|
59
|
+
[*0..period + 1].each.reduce([]) do |arr, index|
|
60
|
+
arr << (index.zero? ? _high_minus_low_ema_array.at(period - index - 1) : ((_high_minus_low_ema_array.at(period + index - 1) * 0.2) + (arr.last * 0.8)).round(4))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def calculate_mass_index
|
65
|
+
[*0..2].each.sum do |index|
|
66
|
+
(_high_minus_low_ema_array.at((period * 2) + index - 2) / high_minus_low_ema_ema_array.at(period + index - 1))
|
67
|
+
end.round(4)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module RubyTechnicalAnalysis
|
2
|
+
# Moving Averages
|
3
|
+
#
|
4
|
+
# Find more information at:
|
5
|
+
#
|
6
|
+
# Simple Moving Average (SMA): https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/sma
|
7
|
+
#
|
8
|
+
# Exponential Moving Average (EMA): https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/ema
|
9
|
+
#
|
10
|
+
# Weighted Moving Average (WMA): https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/wma
|
11
|
+
class MovingAverages < Indicator
|
12
|
+
attr_reader :period
|
13
|
+
|
14
|
+
# @param series [Array] An array of prices, typically closing prices
|
15
|
+
# @param period [Integer] The number of periods to use in the calculation
|
16
|
+
def initialize(series: [], period: 20)
|
17
|
+
@period = period
|
18
|
+
|
19
|
+
super(series: series)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Simple Moving Average
|
23
|
+
# @return [Float] The current SMA value
|
24
|
+
def sma
|
25
|
+
series.last(period).sum.to_f / period
|
26
|
+
end
|
27
|
+
|
28
|
+
# Exponential Moving Average
|
29
|
+
# @return [Float] The current EMA value
|
30
|
+
def ema
|
31
|
+
return series.last if period == 1
|
32
|
+
|
33
|
+
series.last(period).each_with_object([]) do |num, result|
|
34
|
+
result << if result.empty?
|
35
|
+
num
|
36
|
+
else
|
37
|
+
(num * _ema_percentages.first) + (result.last * _ema_percentages.last)
|
38
|
+
end
|
39
|
+
end.last
|
40
|
+
end
|
41
|
+
|
42
|
+
# Weighted Moving Average
|
43
|
+
# @return [Float] The current WMA value
|
44
|
+
def wma
|
45
|
+
true_periods = (1..period).sum
|
46
|
+
|
47
|
+
sigma_periods = series.last(period).each_with_index.sum { |num, index| (index + 1) * num }
|
48
|
+
|
49
|
+
sigma_periods.to_f / true_periods
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Boolean] Whether or not the object is valid
|
53
|
+
def valid?
|
54
|
+
period <= series.length
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def _ema_percentages
|
60
|
+
@_ema_percentages ||=
|
61
|
+
case period
|
62
|
+
when 12 then [0.846154, 0.153846]
|
63
|
+
when 26 then [0.925926, 0.074074]
|
64
|
+
else
|
65
|
+
last_obs_pct = 2.0 / (period + 1)
|
66
|
+
|
67
|
+
[last_obs_pct, 1 - last_obs_pct]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module RubyTechnicalAnalysis
|
2
|
+
# Pivot Points
|
3
|
+
#
|
4
|
+
# Find more information at: https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/pivot-points
|
5
|
+
class PivotPoints < Indicator
|
6
|
+
# @param series [Array] An array of high, low, and closing prices for a single period
|
7
|
+
# def initialize(series: [])
|
8
|
+
# super(series: series)
|
9
|
+
# end
|
10
|
+
|
11
|
+
# @return [Array] An array containing the current S3, S2, S1, pivot, R1, R2, and R3 values
|
12
|
+
def call
|
13
|
+
calculate_pivot_points
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Boolean] Whether or not the object is valid
|
17
|
+
def valid?
|
18
|
+
series.length == 3
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def _high
|
24
|
+
@_high ||= series.first
|
25
|
+
end
|
26
|
+
|
27
|
+
def _low
|
28
|
+
@_low ||= series.at(1)
|
29
|
+
end
|
30
|
+
|
31
|
+
def _close
|
32
|
+
@_close ||= series.last
|
33
|
+
end
|
34
|
+
|
35
|
+
def _pivot
|
36
|
+
@_pivot ||= ((_high + _low + _close) / 3.0).round(2)
|
37
|
+
end
|
38
|
+
|
39
|
+
def support_one
|
40
|
+
((2 * _pivot) - _high).round(2)
|
41
|
+
end
|
42
|
+
|
43
|
+
def support_two
|
44
|
+
(_pivot - (_high - _low)).round(2)
|
45
|
+
end
|
46
|
+
|
47
|
+
def support_three
|
48
|
+
(_low - (2 * (_high - _pivot))).round(2)
|
49
|
+
end
|
50
|
+
|
51
|
+
def resistance_one
|
52
|
+
((2 * _pivot) - _low).round(2)
|
53
|
+
end
|
54
|
+
|
55
|
+
def resistance_two
|
56
|
+
(_pivot + (_high - _low)).round(2)
|
57
|
+
end
|
58
|
+
|
59
|
+
def resistance_three
|
60
|
+
(_high + (2 * (_pivot - _low))).round(2)
|
61
|
+
end
|
62
|
+
|
63
|
+
def calculate_pivot_points
|
64
|
+
[
|
65
|
+
support_three,
|
66
|
+
support_two,
|
67
|
+
support_one,
|
68
|
+
_pivot,
|
69
|
+
resistance_one,
|
70
|
+
resistance_two,
|
71
|
+
resistance_three
|
72
|
+
]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|