technical-analysis 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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