technical-analysis 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/lib/technical-analysis.rb +3 -0
  3. data/lib/technical_analysis.rb +41 -0
  4. data/lib/technical_analysis/helpers/array_helper.rb +27 -0
  5. data/lib/technical_analysis/helpers/stock_calculation.rb +25 -0
  6. data/lib/technical_analysis/helpers/validation.rb +33 -0
  7. data/lib/technical_analysis/indicators/adi.rb +101 -0
  8. data/lib/technical_analysis/indicators/adtv.rb +98 -0
  9. data/lib/technical_analysis/indicators/adx.rb +168 -0
  10. data/lib/technical_analysis/indicators/ao.rb +105 -0
  11. data/lib/technical_analysis/indicators/atr.rb +109 -0
  12. data/lib/technical_analysis/indicators/bb.rb +126 -0
  13. data/lib/technical_analysis/indicators/cci.rb +105 -0
  14. data/lib/technical_analysis/indicators/cmf.rb +105 -0
  15. data/lib/technical_analysis/indicators/cr.rb +95 -0
  16. data/lib/technical_analysis/indicators/dc.rb +108 -0
  17. data/lib/technical_analysis/indicators/dlr.rb +97 -0
  18. data/lib/technical_analysis/indicators/dpo.rb +106 -0
  19. data/lib/technical_analysis/indicators/dr.rb +96 -0
  20. data/lib/technical_analysis/indicators/eom.rb +104 -0
  21. data/lib/technical_analysis/indicators/fi.rb +95 -0
  22. data/lib/technical_analysis/indicators/ichimoku.rb +179 -0
  23. data/lib/technical_analysis/indicators/indicator.rb +138 -0
  24. data/lib/technical_analysis/indicators/kc.rb +124 -0
  25. data/lib/technical_analysis/indicators/kst.rb +132 -0
  26. data/lib/technical_analysis/indicators/macd.rb +144 -0
  27. data/lib/technical_analysis/indicators/mfi.rb +119 -0
  28. data/lib/technical_analysis/indicators/mi.rb +121 -0
  29. data/lib/technical_analysis/indicators/nvi.rb +102 -0
  30. data/lib/technical_analysis/indicators/obv.rb +104 -0
  31. data/lib/technical_analysis/indicators/obv_mean.rb +110 -0
  32. data/lib/technical_analysis/indicators/rsi.rb +133 -0
  33. data/lib/technical_analysis/indicators/sma.rb +98 -0
  34. data/lib/technical_analysis/indicators/sr.rb +122 -0
  35. data/lib/technical_analysis/indicators/trix.rb +127 -0
  36. data/lib/technical_analysis/indicators/tsi.rb +139 -0
  37. data/lib/technical_analysis/indicators/uo.rb +130 -0
  38. data/lib/technical_analysis/indicators/vi.rb +117 -0
  39. data/lib/technical_analysis/indicators/vpt.rb +95 -0
  40. data/lib/technical_analysis/indicators/wr.rb +103 -0
  41. data/spec/helpers/array_helper_spec.rb +31 -0
  42. data/spec/helpers/validaton_spec.rb +22 -0
  43. data/spec/spec_helper.rb +26 -0
  44. data/spec/ta_test_data.csv +64 -0
  45. data/spec/technical_analysis/indicators/adi_spec.rb +116 -0
  46. data/spec/technical_analysis/indicators/adtv_spec.rb +98 -0
  47. data/spec/technical_analysis/indicators/adx_spec.rb +92 -0
  48. data/spec/technical_analysis/indicators/ao_spec.rb +86 -0
  49. data/spec/technical_analysis/indicators/atr_spec.rb +105 -0
  50. data/spec/technical_analysis/indicators/bb_spec.rb +100 -0
  51. data/spec/technical_analysis/indicators/cci_spec.rb +100 -0
  52. data/spec/technical_analysis/indicators/cmf_spec.rb +100 -0
  53. data/spec/technical_analysis/indicators/cr_spec.rb +119 -0
  54. data/spec/technical_analysis/indicators/dc_spec.rb +100 -0
  55. data/spec/technical_analysis/indicators/dlr_spec.rb +119 -0
  56. data/spec/technical_analysis/indicators/dpo_spec.rb +90 -0
  57. data/spec/technical_analysis/indicators/dr_spec.rb +119 -0
  58. data/spec/technical_analysis/indicators/eom_spec.rb +105 -0
  59. data/spec/technical_analysis/indicators/fi_spec.rb +118 -0
  60. data/spec/technical_analysis/indicators/ichimoku_spec.rb +95 -0
  61. data/spec/technical_analysis/indicators/indicator_spec.rb +120 -0
  62. data/spec/technical_analysis/indicators/kc_spec.rb +110 -0
  63. data/spec/technical_analysis/indicators/kst_spec.rb +78 -0
  64. data/spec/technical_analysis/indicators/macd_spec.rb +86 -0
  65. data/spec/technical_analysis/indicators/mfi_spec.rb +105 -0
  66. data/spec/technical_analysis/indicators/mi_spec.rb +79 -0
  67. data/spec/technical_analysis/indicators/nvi_spec.rb +119 -0
  68. data/spec/technical_analysis/indicators/obv_mean_spec.rb +109 -0
  69. data/spec/technical_analysis/indicators/obv_spec.rb +119 -0
  70. data/spec/technical_analysis/indicators/rsi_spec.rb +105 -0
  71. data/spec/technical_analysis/indicators/sma_spec.rb +115 -0
  72. data/spec/technical_analysis/indicators/sr_spec.rb +104 -0
  73. data/spec/technical_analysis/indicators/trix_spec.rb +76 -0
  74. data/spec/technical_analysis/indicators/tsi_spec.rb +87 -0
  75. data/spec/technical_analysis/indicators/uo_spec.rb +91 -0
  76. data/spec/technical_analysis/indicators/vi_spec.rb +105 -0
  77. data/spec/technical_analysis/indicators/vpt_spec.rb +118 -0
  78. data/spec/technical_analysis/indicators/wr_spec.rb +106 -0
  79. metadata +177 -0
@@ -0,0 +1,138 @@
1
+ module TechnicalAnalysis
2
+ class Indicator
3
+
4
+ CALCULATIONS = [
5
+ :indicator_name,
6
+ :indicator_symbol,
7
+ :min_data_size,
8
+ :technicals,
9
+ :valid_options,
10
+ :validate_options,
11
+ ].freeze
12
+
13
+ private_constant :CALCULATIONS
14
+
15
+ # Returns an array of TechnicalAnalysis modules
16
+ #
17
+ # @return [Array] A list of TechnicalAnalysis::Class
18
+ def self.roster
19
+ [
20
+ TechnicalAnalysis::Adi,
21
+ TechnicalAnalysis::Adtv,
22
+ TechnicalAnalysis::Adx,
23
+ TechnicalAnalysis::Ao,
24
+ TechnicalAnalysis::Atr,
25
+ TechnicalAnalysis::Bb,
26
+ TechnicalAnalysis::Cci,
27
+ TechnicalAnalysis::Cmf,
28
+ TechnicalAnalysis::Cr,
29
+ TechnicalAnalysis::Dc,
30
+ TechnicalAnalysis::Dlr,
31
+ TechnicalAnalysis::Dpo,
32
+ TechnicalAnalysis::Dr,
33
+ TechnicalAnalysis::Eom,
34
+ TechnicalAnalysis::Fi,
35
+ TechnicalAnalysis::Ichimoku,
36
+ TechnicalAnalysis::Kc,
37
+ TechnicalAnalysis::Kst,
38
+ TechnicalAnalysis::Macd,
39
+ TechnicalAnalysis::Mfi,
40
+ TechnicalAnalysis::Mi,
41
+ TechnicalAnalysis::Nvi,
42
+ TechnicalAnalysis::Obv,
43
+ TechnicalAnalysis::ObvMean,
44
+ TechnicalAnalysis::Rsi,
45
+ TechnicalAnalysis::Sma,
46
+ TechnicalAnalysis::Sr,
47
+ TechnicalAnalysis::Trix,
48
+ TechnicalAnalysis::Tsi,
49
+ TechnicalAnalysis::Uo,
50
+ TechnicalAnalysis::Vi,
51
+ TechnicalAnalysis::Vpt,
52
+ TechnicalAnalysis::Wr,
53
+ ]
54
+ end
55
+
56
+ # Finds the applicable indicator and returns an instance
57
+ #
58
+ # @param indicator_symbol [String] Downcased string of the indicator symbol
59
+ #
60
+ # @return TechnicalAnalysis::ClassName
61
+ def self.find(indicator_symbol)
62
+ roster.each do |indicator|
63
+ return indicator if indicator.indicator_symbol == indicator_symbol
64
+ end
65
+
66
+ nil
67
+ end
68
+
69
+ # Find the applicable indicator and looks up the value
70
+ #
71
+ # @param indicator_symbol [String] Downcased string of the indicator symbol
72
+ # @param data [Array] Array of hashes of price data to perform calcualtion on
73
+ # @param calculation [Symbol] The calculation to be performed on the requested indicator and params
74
+ # @param options [Hash] A hash containing options for the requested calculation
75
+ #
76
+ # @return Returns the requested calculation
77
+ def self.calculate(indicator_symbol, data, calculation, options={})
78
+ return nil unless CALCULATIONS.include? calculation
79
+
80
+ indicator = find(indicator_symbol)
81
+ raise "Indicator not found!" if indicator.nil?
82
+
83
+ case calculation
84
+ when :indicator_name; indicator.indicator_name
85
+ when :indicator_symbol; indicator.indicator_symbol
86
+ when :technicals; indicator.calculate(data, options)
87
+ when :min_data_size; indicator.min_data_size(options)
88
+ when :valid_options; indicator.valid_options
89
+ when :validate_options; indicator.validate_options(options)
90
+ else nil
91
+ end
92
+ end
93
+
94
+ # Calculates the minimum number of observations needed to calculate the technical indicator
95
+ #
96
+ # @param options [Hash] The options for the technical indicator
97
+ #
98
+ # @return [Integer] Returns the minimum number of observations needed to calculate the technical
99
+ # indicator based on the options provided
100
+ def self.min_data_size(indicator_symbol, options)
101
+ raise "#{self.name} did not implement min_data_size"
102
+ nil
103
+ end
104
+
105
+ # Validates the provided options for this technical indicator
106
+ #
107
+ # @param options [Hash] The options for the technical indicator to be validated
108
+ #
109
+ # @return [Boolean] Returns true if options are valid or raises a ValidationError if they're not
110
+ def self.validate_options(options)
111
+ raise "#{self.name} did not implement validate_options"
112
+ false
113
+ end
114
+
115
+ # Returns an array of valid keys for options for this technical indicator
116
+ #
117
+ # @return [Array] An array of keys as symbols for valid options for this technical indicator
118
+ def self.valid_options
119
+ raise "#{self.name} did not implement valid_options"
120
+ []
121
+ end
122
+
123
+ # Returns the symbol of the technical indicator
124
+ #
125
+ # @return [String] A string of the symbol of the technical indicator
126
+ def self.indicator_symbol
127
+ raise "#{self.name} did not implement indicator_symbol"
128
+ end
129
+
130
+ # Returns the name of the technical indicator
131
+ #
132
+ # @return [String] A string of the name of the technical indicator
133
+ def self.indicator_name
134
+ raise "#{self.name} did not implement indicator_name"
135
+ end
136
+
137
+ end
138
+ end
@@ -0,0 +1,124 @@
1
+ module TechnicalAnalysis
2
+ # Keltner Channel
3
+ class Kc < Indicator
4
+
5
+ # Returns the symbol of the technical indicator
6
+ #
7
+ # @return [String] A string of the symbol of the technical indicator
8
+ def self.indicator_symbol
9
+ "kc"
10
+ end
11
+
12
+ # Returns the name of the technical indicator
13
+ #
14
+ # @return [String] A string of the name of the technical indicator
15
+ def self.indicator_name
16
+ "Keltner Channel"
17
+ end
18
+
19
+ # Returns an array of valid keys for options for this technical indicator
20
+ #
21
+ # @return [Array] An array of keys as symbols for valid options for this technical indicator
22
+ def self.valid_options
23
+ %i(period)
24
+ end
25
+
26
+ # Validates the provided options for this technical indicator
27
+ #
28
+ # @param options [Hash] The options for the technical indicator to be validated
29
+ #
30
+ # @return [Boolean] Returns true if options are valid or raises a ValidationError if they're not
31
+ def self.validate_options(options)
32
+ Validation.validate_options(options, valid_options)
33
+ end
34
+
35
+ # Calculates the minimum number of observations needed to calculate the technical indicator
36
+ #
37
+ # @param options [Hash] The options for the technical indicator
38
+ #
39
+ # @return [Integer] Returns the minimum number of observations needed to calculate the technical
40
+ # indicator based on the options provided
41
+ def self.min_data_size(period: 10)
42
+ period.to_i
43
+ end
44
+
45
+ # Calculates the keltner channel (KC) for the data over the given period
46
+ # https://en.wikipedia.org/wiki/Keltner_channel
47
+ #
48
+ # @param data [Array] Array of hashes with keys (:date_time, :high, :low, :close)
49
+ # @param period [Integer] The given period to calculate the KC
50
+ #
51
+ # @return [Array<KcValue>] An array of KcValue instances
52
+ def self.calculate(data, period: 10)
53
+ period = period.to_i
54
+ Validation.validate_numeric_data(data, :high, :low, :close)
55
+ Validation.validate_length(data, min_data_size(period: period))
56
+ Validation.validate_date_time_key(data)
57
+
58
+ data = data.sort_by { |row| row[:date_time] }
59
+
60
+ output = []
61
+ period_values = []
62
+
63
+ data.each do |v|
64
+ tp = StockCalculation.typical_price(v)
65
+ tr = v[:high] - v[:low]
66
+ period_values << { typical_price: tp, trading_range: tr }
67
+
68
+ if period_values.size == period
69
+ mb = ArrayHelper.average(period_values.map { |pv| pv[:typical_price] })
70
+
71
+ trading_range_average = ArrayHelper.average(period_values.map { |pv| pv[:trading_range] })
72
+ ub = mb + trading_range_average
73
+ lb = mb - trading_range_average
74
+
75
+ output << KcValue.new(
76
+ date_time: v[:date_time],
77
+ lower_band: lb,
78
+ middle_band: mb,
79
+ upper_band: ub
80
+ )
81
+
82
+ period_values.shift
83
+ end
84
+ end
85
+
86
+ output.sort_by(&:date_time).reverse
87
+ end
88
+
89
+ end
90
+
91
+ # The value class to be returned by calculations
92
+ class KcValue
93
+
94
+ # @return [String] the date_time of the obversation as it was provided
95
+ attr_accessor :date_time
96
+
97
+ # @return [Float] the lower_band calculation value
98
+ attr_accessor :lower_band
99
+
100
+ # @return [Float] the middle_band calculation value
101
+ attr_accessor :middle_band
102
+
103
+ # @return [Float] the upper_band calculation value
104
+ attr_accessor :upper_band
105
+
106
+ def initialize(date_time: nil, lower_band: nil, middle_band: nil, upper_band: nil)
107
+ @date_time = date_time
108
+ @lower_band = lower_band
109
+ @middle_band = middle_band
110
+ @upper_band = upper_band
111
+ end
112
+
113
+ # @return [Hash] the attributes as a hash
114
+ def to_hash
115
+ {
116
+ date_time: @date_time,
117
+ lower_band: @lower_band,
118
+ middle_band: @middle_band,
119
+ upper_band: @upper_band
120
+ }
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,132 @@
1
+ module TechnicalAnalysis
2
+ # Know Sure Thing
3
+ class Kst < Indicator
4
+
5
+ # Returns the symbol of the technical indicator
6
+ #
7
+ # @return [String] A string of the symbol of the technical indicator
8
+ def self.indicator_symbol
9
+ "kst"
10
+ end
11
+
12
+ # Returns the name of the technical indicator
13
+ #
14
+ # @return [String] A string of the name of the technical indicator
15
+ def self.indicator_name
16
+ "Know Sure Thing"
17
+ end
18
+
19
+ # Returns an array of valid keys for options for this technical indicator
20
+ #
21
+ # @return [Array] An array of keys as symbols for valid options for this technical indicator
22
+ def self.valid_options
23
+ %i(period roc1 roc2 roc3 roc4 sma1 sma2 sma3 sma4 price_key)
24
+ end
25
+
26
+ # Validates the provided options for this technical indicator
27
+ #
28
+ # @param options [Hash] The options for the technical indicator to be validated
29
+ #
30
+ # @return [Boolean] Returns true if options are valid or raises a ValidationError if they're not
31
+ def self.validate_options(options)
32
+ Validation.validate_options(options, valid_options)
33
+ end
34
+
35
+ # Calculates the minimum number of observations needed to calculate the technical indicator
36
+ #
37
+ # @param options [Hash] The options for the technical indicator
38
+ #
39
+ # @return [Integer] Returns the minimum number of observations needed to calculate the technical
40
+ # indicator based on the options provided
41
+ def self.min_data_size(roc4: 30, sma4: 15, **params)
42
+ roc4.to_i + sma4.to_i - 1
43
+ end
44
+
45
+ # Calculates the know sure thing (KST) for the data over the given period
46
+ # https://en.wikipedia.org/wiki/KST_oscillator
47
+ #
48
+ # @param data [Array] Array of hashes with keys (:date_time, :value)
49
+ # @param roc1 [Integer] The given period to calculate the rate-of-change for RCMA1
50
+ # @param roc2 [Integer] The given period to calculate the rate-of-change for RCMA2
51
+ # @param roc3 [Integer] The given period to calculate the rate-of-change for RCMA3
52
+ # @param roc4 [Integer] The given period to calculate the rate-of-change for RCMA4
53
+ # @param sma1 [Integer] The given period to calculate the SMA of the rate-of-change for RCMA1
54
+ # @param sma2 [Integer] The given period to calculate the SMA of the rate-of-change for RCMA2
55
+ # @param sma3 [Integer] The given period to calculate the SMA of the rate-of-change for RCMA3
56
+ # @param sma4 [Integer] The given period to calculate the SMA of the rate-of-change for RCMA4
57
+ # @param price_key [Symbol] The hash key for the price data. Default :value
58
+ #
59
+ # @return [Array<KstValue>] An array of KstValue instances
60
+ def self.calculate(data, roc1: 10, roc2: 15, roc3: 20, roc4: 30, sma1: 10, sma2: 10, sma3: 10, sma4: 15, price_key: :value)
61
+ roc1 = roc1.to_i
62
+ roc2 = roc2.to_i
63
+ roc3 = roc3.to_i
64
+ roc4 = roc4.to_i
65
+ sma1 = sma1.to_i
66
+ sma2 = sma2.to_i
67
+ sma3 = sma3.to_i
68
+ sma4 = sma4.to_i
69
+ price_key = price_key.to_sym
70
+ Validation.validate_numeric_data(data, price_key)
71
+ Validation.validate_length(data, min_data_size(roc4: roc4, sma4: sma4))
72
+ Validation.validate_date_time_key(data)
73
+
74
+ data = data.sort_by { |row| row[:date_time] }
75
+
76
+ index = roc4 + sma4 - 2
77
+ output = []
78
+
79
+ while index < data.size
80
+ date_time = data[index][:date_time]
81
+ rcma1 = calculate_rcma(data, index, price_key, roc1, sma1)
82
+ rcma2 = calculate_rcma(data, index, price_key, roc2, sma2)
83
+ rcma3 = calculate_rcma(data, index, price_key, roc3, sma3)
84
+ rcma4 = calculate_rcma(data, index, price_key, roc4, sma4)
85
+
86
+ kst = (1 * rcma1) + (2 * rcma2) + (3 * rcma3) + (4 * rcma4)
87
+
88
+ output << KstValue.new(date_time: date_time, kst: kst)
89
+
90
+ index += 1
91
+ end
92
+
93
+ output.sort_by(&:date_time).reverse
94
+ end
95
+
96
+ private_class_method def self.calculate_rcma(data, index, price_key, roc, sma)
97
+ roc_data = []
98
+ index_range = (index - sma + 1)..index
99
+
100
+ index_range.each do |i|
101
+ last_price = data[i][price_key]
102
+ starting_price = data[i - roc + 1][price_key]
103
+
104
+ roc_data << (last_price - starting_price) / starting_price * 100
105
+ end
106
+
107
+ ArrayHelper.sum(roc_data) / sma.to_f
108
+ end
109
+
110
+ end
111
+
112
+ # The value class to be returned by calculations
113
+ class KstValue
114
+
115
+ # @return [String] the date_time of the obversation as it was provided
116
+ attr_accessor :date_time
117
+
118
+ # @return [Float] the kst calculation value
119
+ attr_accessor :kst
120
+
121
+ def initialize(date_time: nil, kst: nil)
122
+ @date_time = date_time
123
+ @kst = kst
124
+ end
125
+
126
+ # @return [Hash] the attributes as a hash
127
+ def to_hash
128
+ { date_time: @date_time, kst: @kst }
129
+ end
130
+
131
+ end
132
+ end
@@ -0,0 +1,144 @@
1
+ module TechnicalAnalysis
2
+ # Moving Average Convergence Divergence
3
+ class Macd < Indicator
4
+
5
+ # Returns the symbol of the technical indicator
6
+ #
7
+ # @return [String] A string of the symbol of the technical indicator
8
+ def self.indicator_symbol
9
+ "macd"
10
+ end
11
+
12
+ # Returns the name of the technical indicator
13
+ #
14
+ # @return [String] A string of the name of the technical indicator
15
+ def self.indicator_name
16
+ "Moving Average Convergence Divergence"
17
+ end
18
+
19
+ # Returns an array of valid keys for options for this technical indicator
20
+ #
21
+ # @return [Array] An array of keys as symbols for valid options for this technical indicator
22
+ def self.valid_options
23
+ %i(fast_period slow_period signal_period price_key)
24
+ end
25
+
26
+ # Validates the provided options for this technical indicator
27
+ #
28
+ # @param options [Hash] The options for the technical indicator to be validated
29
+ #
30
+ # @return [Boolean] Returns true if options are valid or raises a ValidationError if they're not
31
+ def self.validate_options(options)
32
+ Validation.validate_options(options, valid_options)
33
+ end
34
+
35
+ # Calculates the minimum number of observations needed to calculate the technical indicator
36
+ #
37
+ # @param options [Hash] The options for the technical indicator
38
+ #
39
+ # @return [Integer] Returns the minimum number of observations needed to calculate the technical
40
+ # indicator based on the options provided
41
+ def self.min_data_size(slow_period: 26, signal_period: 9, **params)
42
+ slow_period.to_i + signal_period.to_i - 1
43
+ end
44
+
45
+ # Calculates the moving average convergence divergence (MACD) for the data over the given period
46
+ # https://en.wikipedia.org/wiki/MACD
47
+ #
48
+ # @param data [Array] Array of hashes with keys (:date_time, :value)
49
+ # @param fast_period [Integer] The given period to calculate the fast moving EMA for MACD
50
+ # @param slow_period [Integer] The given period to calculate the slow moving EMA for MACD
51
+ # @param signal_period [Integer] The given period to calculate the signal line for MACD
52
+ # @param price_key [Symbol] The hash key for the price data. Default :value
53
+ #
54
+ # @return [Array<MacdValue>] An array of MacdValue instances
55
+ def self.calculate(data, fast_period: 12, slow_period: 26, signal_period: 9, price_key: :value)
56
+ fast_period = fast_period.to_i
57
+ slow_period = slow_period.to_i
58
+ signal_period = signal_period.to_i
59
+ price_key = price_key.to_sym
60
+ Validation.validate_numeric_data(data, price_key)
61
+ Validation.validate_length(data, min_data_size(slow_period: slow_period, signal_period: signal_period))
62
+ Validation.validate_date_time_key(data)
63
+
64
+ data = data.sort_by { |row| row[:date_time] }
65
+
66
+ macd_values = []
67
+ output = []
68
+ period_values = []
69
+ prev_fast_ema = nil
70
+ prev_signal = nil
71
+ prev_slow_ema = nil
72
+
73
+ data.each do |v|
74
+ period_values << v[price_key]
75
+
76
+ if period_values.size >= fast_period
77
+ fast_ema = StockCalculation.ema(v[price_key], period_values, fast_period, prev_fast_ema)
78
+ prev_fast_ema = fast_ema
79
+
80
+ if period_values.size == slow_period
81
+ slow_ema = StockCalculation.ema(v[price_key], period_values, slow_period, prev_slow_ema)
82
+ prev_slow_ema = slow_ema
83
+
84
+ macd = fast_ema - slow_ema
85
+ macd_values << macd
86
+
87
+ if macd_values.size == signal_period
88
+ signal = StockCalculation.ema(macd, macd_values, signal_period, prev_signal)
89
+ prev_signal = signal
90
+
91
+ output << MacdValue.new(
92
+ date_time: v[:date_time],
93
+ macd_line: macd,
94
+ signal_line: signal,
95
+ macd_histogram: macd - signal,
96
+ )
97
+
98
+ macd_values.shift
99
+ end
100
+
101
+ period_values.shift
102
+ end
103
+ end
104
+ end
105
+
106
+ output.sort_by(&:date_time).reverse
107
+ end
108
+
109
+ end
110
+
111
+ # The value class to be returned by calculations
112
+ class MacdValue
113
+
114
+ # @return [String] the date_time of the obversation as it was provided
115
+ attr_accessor :date_time
116
+
117
+ # @return [Float] the macd_line calculation value
118
+ attr_accessor :macd_line
119
+
120
+ # @return [Float] the macd_histogram calculation value
121
+ attr_accessor :macd_histogram
122
+
123
+ # @return [Float] the signal_line calculation value
124
+ attr_accessor :signal_line
125
+
126
+ def initialize(date_time: nil, macd_line: nil, macd_histogram: nil, signal_line: nil)
127
+ @date_time = date_time
128
+ @macd_line = macd_line
129
+ @macd_histogram = macd_histogram
130
+ @signal_line = signal_line
131
+ end
132
+
133
+ # @return [Hash] the attributes as a hash
134
+ def to_hash
135
+ {
136
+ date_time: @date_time,
137
+ macd_line: @macd_line,
138
+ macd_histogram: @macd_histogram,
139
+ signal_line: @signal_line
140
+ }
141
+ end
142
+
143
+ end
144
+ end