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,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