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,119 @@
1
+ module TechnicalAnalysis
2
+ # Monoey Flow Index
3
+ class Mfi < 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
+ "mfi"
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
+ "Money Flow 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
+ %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 money flow index (MFI) for the data over the given period
46
+ # https://en.wikipedia.org/wiki/Money_flow_index
47
+ #
48
+ # @param data [Array] Array of hashes with keys (:date_time, :high, :low, :close, :volume)
49
+ # @param period [Integer] The given period to calculate the MFI
50
+ #
51
+ # @return [Array<MfiValue>] An array of MfiValue instances
52
+ def self.calculate(data, period: 14)
53
+ period = period.to_i
54
+ Validation.validate_numeric_data(data, :high, :low, :close, :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
+ prev_typical_price = StockCalculation.typical_price(data.first)
62
+ raw_money_flows = []
63
+
64
+ data.shift
65
+
66
+ data.each do |v|
67
+ typical_price = StockCalculation.typical_price(v)
68
+
69
+ if typical_price < prev_typical_price
70
+ money_flow = (-1.0 * typical_price * v[:volume])
71
+ elsif typical_price > prev_typical_price
72
+ money_flow = (typical_price * v[:volume])
73
+ else
74
+ money_flow = 0.0
75
+ end
76
+
77
+ raw_money_flows << money_flow
78
+
79
+ if raw_money_flows.size == period
80
+ positive_period_flows = ArrayHelper.sum(raw_money_flows.map { |rmf| rmf.positive? ? rmf : 0 })
81
+ negative_period_flows = ArrayHelper.sum(raw_money_flows.map { |rmf| rmf.negative? ? rmf.abs : 0 })
82
+
83
+ money_flow_ratio = (positive_period_flows / negative_period_flows)
84
+ mfi = (100.00 - (100.00 / (1.0 + money_flow_ratio)))
85
+
86
+ output << MfiValue.new(date_time: v[:date_time], mfi: mfi)
87
+
88
+ raw_money_flows.shift
89
+ end
90
+
91
+ prev_typical_price = typical_price
92
+ end
93
+
94
+ output.sort_by(&:date_time).reverse
95
+ end
96
+
97
+ end
98
+
99
+ # The value class to be returned by calculations
100
+ class MfiValue
101
+
102
+ # @return [String] the date_time of the obversation as it was provided
103
+ attr_accessor :date_time
104
+
105
+ # @return [Float] the mfi calculation value
106
+ attr_accessor :mfi
107
+
108
+ def initialize(date_time: nil, mfi: nil)
109
+ @date_time = date_time
110
+ @mfi = mfi
111
+ end
112
+
113
+ # @return [Hash] the attributes as a hash
114
+ def to_hash
115
+ { date_time: @date_time, mfi: @mfi }
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,121 @@
1
+ module TechnicalAnalysis
2
+ # Mass Index
3
+ class Mi < 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
+ "mi"
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
+ "Mass 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
+ %i(ema_period sum_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(ema_period: 9, sum_period: 25)
42
+ (ema_period.to_i * 2) + sum_period.to_i - 2
43
+ end
44
+
45
+ # Calculates the mass index (MI) for the data over the given period
46
+ # https://en.wikipedia.org/wiki/Mass_index
47
+ #
48
+ # @param data [Array] Array of hashes with keys (:date_time, :high, :low)
49
+ # @param ema_period [Integer] The given period to calculate the EMA and EMA of EMA
50
+ # @param sum_period [Integer] The given period to calculate the sum of EMA ratios
51
+ #
52
+ # @return [Array<MiValue>] An array of MiValue instances
53
+ def self.calculate(data, ema_period: 9, sum_period: 25)
54
+ ema_period = ema_period.to_i
55
+ sum_period = sum_period.to_i
56
+ Validation.validate_numeric_data(data, :high, :low)
57
+ Validation.validate_length(data, min_data_size(ema_period: ema_period, sum_period: sum_period))
58
+ Validation.validate_date_time_key(data)
59
+
60
+ data = data.sort_by { |row| row[:date_time] }
61
+
62
+ double_emas = []
63
+ high_low_diffs = []
64
+ output = []
65
+ ratio_of_emas = []
66
+ single_emas = []
67
+
68
+ data.each do |v|
69
+ high_low_diff = v[:high] - v[:low]
70
+ high_low_diffs << high_low_diff
71
+
72
+ if high_low_diffs.size == ema_period
73
+ single_ema = StockCalculation.ema(high_low_diff, high_low_diffs, ema_period, single_emas.last)
74
+ single_emas << single_ema
75
+
76
+ if single_emas.size == ema_period
77
+ double_ema = StockCalculation.ema(single_emas.last, single_emas, ema_period, double_emas.last)
78
+ double_emas << double_ema
79
+
80
+ ratio_of_emas << (single_ema / double_ema)
81
+
82
+ if ratio_of_emas.size == sum_period
83
+ output << MiValue.new(date_time: v[:date_time], mi: ArrayHelper.sum(ratio_of_emas))
84
+
85
+ double_emas.shift
86
+ ratio_of_emas.shift
87
+ end
88
+
89
+ single_emas.shift
90
+ end
91
+
92
+ high_low_diffs.shift
93
+ end
94
+ end
95
+
96
+ output.sort_by(&:date_time).reverse
97
+ end
98
+
99
+ end
100
+
101
+ # The value class to be returned by calculations
102
+ class MiValue
103
+
104
+ # @return [String] the date_time of the obversation as it was provided
105
+ attr_accessor :date_time
106
+
107
+ # @return [Float] the mi calculation value
108
+ attr_accessor :mi
109
+
110
+ def initialize(date_time: nil, mi: nil)
111
+ @date_time = date_time
112
+ @mi = mi
113
+ end
114
+
115
+ # @return [Hash] the attributes as a hash
116
+ def to_hash
117
+ { date_time: @date_time, mi: @mi }
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,102 @@
1
+ module TechnicalAnalysis
2
+ # Negative Volume Index
3
+ class Nvi < 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
+ "nvi"
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
+ "Negative Volume 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
+ 1
44
+ end
45
+
46
+ # Calculates the negative volume index (NVI) for the data
47
+ # https://en.wikipedia.org/wiki/Negative_volume_index
48
+ #
49
+ # @param data [Array] Array of hashes with keys (:date_time, :close, :volume)
50
+ #
51
+ # @return [Array<NviValue>] An array of NviValue 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
+ nvi_cumulative = 1_000.00
60
+ output = []
61
+ prev_price = data.shift
62
+
63
+ output << NviValue.new(date_time: prev_price[:date_time], nvi: nvi_cumulative) # Start with default of 1_000
64
+
65
+ data.each do |v|
66
+ volume_change = ((v[:volume] - prev_price[:volume]) / prev_price[:volume])
67
+
68
+ if volume_change < 0
69
+ price_change = ((v[:close] - prev_price[:close]) / prev_price[:close]) * 100.00
70
+ nvi_cumulative += price_change
71
+ end
72
+
73
+ output << NviValue.new(date_time: v[:date_time], nvi: nvi_cumulative)
74
+ prev_price = v
75
+ end
76
+
77
+ output.sort_by(&:date_time).reverse
78
+ end
79
+
80
+ end
81
+
82
+ # The value class to be returned by calculations
83
+ class NviValue
84
+
85
+ # @return [String] the date_time of the obversation as it was provided
86
+ attr_accessor :date_time
87
+
88
+ # @return [Float] the nvi calculation value
89
+ attr_accessor :nvi
90
+
91
+ def initialize(date_time: nil, nvi: nil)
92
+ @date_time = date_time
93
+ @nvi = nvi
94
+ end
95
+
96
+ # @return [Hash] the attributes as a hash
97
+ def to_hash
98
+ { date_time: @date_time, nvi: @nvi }
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,104 @@
1
+ module TechnicalAnalysis
2
+ # On-balance Volume
3
+ class Obv < 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
+ "obv"
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
+ "On-balance Volume"
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
+ 1
44
+ end
45
+
46
+ # Calculates the on-balance volume (OBV) for the data over the given period
47
+ # https://en.wikipedia.org/wiki/On-balance_volume
48
+ #
49
+ # @param data [Array] Array of hashes with keys (:date_time, :close, :volume)
50
+ #
51
+ # @return [Array<ObvValue>] An array of ObvValue 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
+ current_obv = 0
60
+ output = []
61
+ prior_close = nil
62
+ prior_volume = nil
63
+
64
+ data.each do |v|
65
+ volume = v[:volume]
66
+ close = v[:close]
67
+
68
+ unless prior_close.nil?
69
+ current_obv += volume if close > prior_close
70
+ current_obv -= volume if close < prior_close
71
+ end
72
+
73
+ output << ObvValue.new(date_time: v[:date_time], obv: current_obv)
74
+
75
+ prior_volume = volume
76
+ prior_close = close
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 ObvValue
86
+
87
+ # @return [String] the date_time of the obversation as it was provided
88
+ attr_accessor :date_time
89
+
90
+ # @return [Float] the obv calculation value
91
+ attr_accessor :obv
92
+
93
+ def initialize(date_time: nil, obv: nil)
94
+ @date_time = date_time
95
+ @obv = obv
96
+ end
97
+
98
+ # @return [Hash] the attributes as a hash
99
+ def to_hash
100
+ { date_time: @date_time, obv: @obv }
101
+ end
102
+
103
+ end
104
+ end