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,96 @@
1
+ module TechnicalAnalysis
2
+ # Daily Return
3
+ class Dr < 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
+ "dr"
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
+ "Daily Return"
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(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(**params)
42
+ 1
43
+ end
44
+
45
+ # Calculates the daily return (percent expressed as a decimal) for the data over the given period
46
+ # https://en.wikipedia.org/wiki/Rate_of_return
47
+ #
48
+ # @param data [Array] Array of hashes with keys (:date_time, :value)
49
+ # @param price_key [Symbol] The hash key for the price data. Default :value
50
+ #
51
+ # @return [Array<DrValue>] An array of DrValue instances
52
+ def self.calculate(data, price_key: :value)
53
+ price_key = price_key.to_sym
54
+ Validation.validate_numeric_data(data, price_key)
55
+ Validation.validate_length(data, min_data_size({}))
56
+ Validation.validate_date_time_key(data)
57
+
58
+ data = data.sort_by { |row| row[:date_time] }
59
+
60
+ output = []
61
+ prev_price = data.first[price_key].to_f
62
+
63
+ data.each do |v|
64
+ current_price = v[:close].to_f
65
+
66
+ output << DrValue.new(date_time: v[:date_time], dr: ((current_price / prev_price) - 1))
67
+
68
+ prev_price = current_price
69
+ end
70
+
71
+ output.sort_by(&:date_time).reverse
72
+ end
73
+
74
+ end
75
+
76
+ # The value class to be returned by calculations
77
+ class DrValue
78
+
79
+ # @return [String] the date_time of the obversation as it was provided
80
+ attr_accessor :date_time
81
+
82
+ # @return [Float] the dr calculation value
83
+ attr_accessor :dr
84
+
85
+ def initialize(date_time: nil, dr: nil)
86
+ @date_time = date_time
87
+ @dr = dr
88
+ end
89
+
90
+ # @return [Hash] the attributes as a hash
91
+ def to_hash
92
+ { date_time: @date_time, dr: @dr }
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,104 @@
1
+ module TechnicalAnalysis
2
+ # Ease of Movement
3
+ class Eom < 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
+ "eom"
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
+ "Ease of Movement"
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: 14)
42
+ period.to_i + 1
43
+ end
44
+
45
+ # Calculates the ease of movement (EoM) for the data over the given period
46
+ # https://en.wikipedia.org/wiki/Ease_of_movement
47
+ #
48
+ # @param data [Array] Array of hashes with keys (:date_time, :high, :low, :volume)
49
+ # @param period [Integer] The given period to calculate the EoM
50
+ #
51
+ # @return [Array<Hash>] An array of EomValue instances
52
+ def self.calculate(data, period: 14)
53
+ period = period.to_i
54
+ Validation.validate_numeric_data(data, :high, :low, :volume)
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
+ prev_price = data.shift
63
+
64
+ data.each do |v|
65
+ distance_moved = ((v[:high] + v[:low]) / 2) - ((prev_price[:high] + prev_price[:low]) / 2)
66
+ box_ratio = (v[:volume] / 100_000_000.00) / (v[:high] - v[:low])
67
+ emv = distance_moved / box_ratio
68
+
69
+ period_values << emv
70
+
71
+ if period_values.size == period
72
+ output << EomValue.new(date_time: v[:date_time], eom: ArrayHelper.average(period_values))
73
+ period_values.shift
74
+ end
75
+
76
+ prev_price = v
77
+ end
78
+
79
+ output.sort_by(&:date_time).reverse
80
+ end
81
+
82
+ end
83
+
84
+ # The value class to be returned by calculations
85
+ class EomValue
86
+
87
+ # @return [String] the date_time of the obversation as it was provided
88
+ attr_accessor :date_time
89
+
90
+ # @return [Float] the eom calculation value
91
+ attr_accessor :eom
92
+
93
+ def initialize(date_time: nil, eom: nil)
94
+ @date_time = date_time
95
+ @eom = eom
96
+ end
97
+
98
+ # @return [Hash] the attributes as a hash
99
+ def to_hash
100
+ { date_time: @date_time, eom: @eom }
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,95 @@
1
+ module TechnicalAnalysis
2
+ # Force Index
3
+ class Fi < 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
+ "fi"
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
+ "Force Index"
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
+ []
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
+ return true if options == {}
33
+ raise Validation::ValidationError.new "This indicator doesn't accept any options."
34
+ end
35
+
36
+ # Calculates the minimum number of observations needed to calculate the technical indicator
37
+ #
38
+ # @param options [Hash] The options for the technical indicator
39
+ #
40
+ # @return [Integer] Returns the minimum number of observations needed to calculate the technical
41
+ # indicator based on the options provided
42
+ def self.min_data_size(**params)
43
+ 2
44
+ end
45
+
46
+ # Calculates the force index (FI) for the data
47
+ # https://en.wikipedia.org/wiki/Force_index
48
+ #
49
+ # @param data [Array] Array of hashes with keys (:date_time, :close, :volume)
50
+ #
51
+ # @return [Array<FiValue>] An array of FiValue instances
52
+ def self.calculate(data)
53
+ Validation.validate_numeric_data(data, :close, :volume)
54
+ Validation.validate_length(data, min_data_size({}))
55
+ Validation.validate_date_time_key(data)
56
+
57
+ data = data.sort_by { |row| row[:date_time] }
58
+
59
+ output = []
60
+ prev_price = data.shift
61
+
62
+ data.each do |v|
63
+ fi = ((v[:close] - prev_price[:close]) * v[:volume])
64
+
65
+ output << FiValue.new(date_time: v[:date_time], fi: fi)
66
+
67
+ prev_price = v
68
+ end
69
+
70
+ output.sort_by(&:date_time).reverse
71
+ end
72
+
73
+ end
74
+
75
+ # The value class to be returned by calculations
76
+ class FiValue
77
+
78
+ # @return [String] the date_time of the obversation as it was provided
79
+ attr_accessor :date_time
80
+
81
+ # @return [Float] the fi calculation value
82
+ attr_accessor :fi
83
+
84
+ def initialize(date_time: nil, fi: nil)
85
+ @date_time = date_time
86
+ @fi = fi
87
+ end
88
+
89
+ # @return [Hash] the attributes as a hash
90
+ def to_hash
91
+ { date_time: @date_time, fi: @fi }
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,179 @@
1
+ module TechnicalAnalysis
2
+ # Ichimoku Kinko Hyo
3
+ class Ichimoku < 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
+ "ichimoku"
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
+ "Ichimoku Kinko Hyo"
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(low_period medium_period high_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(medium_period: 26, high_period: 52, **params)
42
+ high_period.to_i + medium_period.to_i - 1
43
+ end
44
+
45
+ # Calculates the 5 points of Ichimoku Kinko Hyo (Ichimoku) for the data over the given period
46
+ # 1. tenkan_sen (Conversion Line)
47
+ # 2. kijun_sen (Base Line)
48
+ # 3. senkou_span_a (Leading Span A)
49
+ # 4. senkou_span_b (Leading Span B)
50
+ # 5. chickou_span (Lagging Span)
51
+ # https://en.wikipedia.org/wiki/Ichimoku_Kink%C5%8D_Hy%C5%8D
52
+ #
53
+ # @param data [Array] Array of hashes with keys (:date_time, :high, :low, :close)
54
+ # @param low_period [Integer] The given period to calculate tenkan_sen (Conversion Line)
55
+ # @param medium_period [Integer] The given period to calculate kijun_sen (Base Line), senkou_span_a (Leading Span A), and chikou_span (Lagging Span)
56
+ # @param high_period [Integer] The given period to calculate senkou_span_b (Leading Span B)
57
+ #
58
+ # @return [Array<IchimokuValue>] An array of IchimokuValue instances
59
+ def self.calculate(data, low_period: 9, medium_period: 26, high_period: 52)
60
+ low_period = low_period.to_i
61
+ medium_period = medium_period.to_i
62
+ high_period = high_period.to_i
63
+ Validation.validate_numeric_data(data, :high, :low, :close)
64
+ Validation.validate_length(data, min_data_size(high_period: high_period, medium_period: medium_period))
65
+ Validation.validate_date_time_key(data)
66
+
67
+ data = data.sort_by { |row| row[:date_time] }
68
+
69
+ index = high_period + medium_period - 2
70
+ output = []
71
+
72
+ while index < data.size
73
+ date_time = data[index][:date_time]
74
+
75
+ tenkan_sen = calculate_midpoint(index, low_period, data)
76
+ kinjun_sen = calculate_midpoint(index, medium_period, data)
77
+ senkou_span_a = calculate_senkou_span_a(index, low_period, medium_period, data)
78
+ senkou_span_b = calculate_senkou_span_b(index, medium_period, high_period, data)
79
+ chikou_span = calculate_chikou_span(index, medium_period, data)
80
+
81
+ output << IchimokuValue.new(
82
+ date_time: date_time,
83
+ tenkan_sen: tenkan_sen,
84
+ kijun_sen: kinjun_sen,
85
+ senkou_span_a: senkou_span_a,
86
+ senkou_span_b: senkou_span_b,
87
+ chikou_span: chikou_span
88
+ )
89
+
90
+ index += 1
91
+ end
92
+
93
+ output.sort_by(&:date_time).reverse
94
+ end
95
+
96
+ private_class_method def self.lowest_low(prices)
97
+ prices.map { |price| price[:low] }.min
98
+ end
99
+
100
+ private_class_method def self.highest_high(prices)
101
+ prices.map { |price| price[:high] }.max
102
+ end
103
+
104
+ private_class_method def self.calculate_midpoint(index, period, data)
105
+ period_range = ((index - (period - 1))..index)
106
+ period_data = data[period_range]
107
+ lowest_low = lowest_low(period_data)
108
+ highest_high = highest_high(period_data)
109
+
110
+ ((highest_high + lowest_low) / 2.0)
111
+ end
112
+
113
+ private_class_method def self.calculate_senkou_span_a(index, low_period, medium_period, data)
114
+ mp_ago_index = (index - (medium_period - 1))
115
+
116
+ tenkan_sen_mp_ago = calculate_midpoint(mp_ago_index, low_period, data)
117
+ kinjun_sen_mp_ago = calculate_midpoint(mp_ago_index, medium_period, data)
118
+
119
+ ((tenkan_sen_mp_ago + kinjun_sen_mp_ago) / 2.0)
120
+ end
121
+
122
+ private_class_method def self.calculate_senkou_span_b(index, medium_period, high_period, data)
123
+ mp_ago_index = (index - (medium_period - 1))
124
+
125
+ calculate_midpoint(mp_ago_index, high_period, data)
126
+ end
127
+
128
+ private_class_method def self.calculate_chikou_span(index, medium_period, data)
129
+ mp_ago_index = (index - (medium_period - 1))
130
+
131
+ data[mp_ago_index][:close]
132
+ end
133
+
134
+ end
135
+
136
+ # The value class to be returned by calculations
137
+ class IchimokuValue
138
+
139
+ # @return [String] the date_time of the obversation as it was provided
140
+ attr_accessor :date_time
141
+
142
+ # @return [Float] the tenkan_sen calculation value
143
+ attr_accessor :tenkan_sen
144
+
145
+ # @return [Float] the kijun_sen calculation value
146
+ attr_accessor :kijun_sen
147
+
148
+ # @return [Float] the senkou_span_a calculation value
149
+ attr_accessor :senkou_span_a
150
+
151
+ # @return [Float] the senkou_span_b calculation value
152
+ attr_accessor :senkou_span_b
153
+
154
+ # @return [Float] the chikou_span calculation value
155
+ attr_accessor :chikou_span
156
+
157
+ def initialize(date_time: nil, tenkan_sen: nil, kijun_sen: nil, senkou_span_a: nil, senkou_span_b: nil, chikou_span: nil)
158
+ @date_time = date_time
159
+ @tenkan_sen = tenkan_sen
160
+ @kijun_sen = kijun_sen
161
+ @senkou_span_a = senkou_span_a
162
+ @senkou_span_b = senkou_span_b
163
+ @chikou_span = chikou_span
164
+ end
165
+
166
+ # @return [Hash] the attributes as a hash
167
+ def to_hash
168
+ {
169
+ date_time: @date_time,
170
+ tenkan_sen: @tenkan_sen,
171
+ kijun_sen: @kijun_sen,
172
+ senkou_span_a: @senkou_span_a,
173
+ senkou_span_b: @senkou_span_b,
174
+ chikou_span: @chikou_span
175
+ }
176
+ end
177
+
178
+ end
179
+ end