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,127 @@
1
+ module TechnicalAnalysis
2
+ # Triple Exponential Average
3
+ class Trix < 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
+ "trix"
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
+ "Triple Exponential Average"
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 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(period: 15, **params)
42
+ (period.to_i * 3) - 1
43
+ end
44
+
45
+ # Calculates the triple exponential average (Trix) for the data over the given period
46
+ # https://en.wikipedia.org/wiki/Trix_(technical_analysis)
47
+ #
48
+ # @param data [Array] Array of hashes with keys (:date_time, :value)
49
+ # @param period [Integer] The given period to calculate the EMA for Trix
50
+ # @param price_key [Symbol] The hash key for the price data. Default :value
51
+ #
52
+ # @return [Array<TrixValue>] An array of TrixValue instances
53
+ def self.calculate(data, period: 15, price_key: :value)
54
+ period = period.to_i
55
+ price_key = price_key.to_sym
56
+ Validation.validate_numeric_data(data, price_key)
57
+ Validation.validate_length(data, min_data_size(period: period))
58
+ Validation.validate_date_time_key(data)
59
+
60
+ data = data.sort_by { |row| row[:date_time] }
61
+
62
+ ema1 = []
63
+ ema2 = []
64
+ ema3 = []
65
+ output = []
66
+ period_values = []
67
+
68
+ data.each do |v|
69
+ price = v[price_key]
70
+ period_values << price
71
+
72
+ if period_values.size == period
73
+ ema1_value = StockCalculation.ema(price, period_values, period, ema1.last)
74
+ ema1 << ema1_value
75
+
76
+ if ema1.size == period
77
+ ema2_value = StockCalculation.ema(ema1_value, ema1, period, ema2.last)
78
+ ema2 << ema2_value
79
+
80
+ if ema2.size == period
81
+ ema3_value = StockCalculation.ema(ema2_value, ema2, period, ema3.last)
82
+ ema3 << ema3_value
83
+
84
+ if ema3.size == 2
85
+ prev_ema3, current_ema3 = ema3
86
+ trix = ((current_ema3 - prev_ema3) / prev_ema3)
87
+ output << TrixValue.new(date_time: v[:date_time], trix: trix)
88
+
89
+ ema3.shift
90
+ end
91
+
92
+ ema2.shift
93
+ end
94
+
95
+ ema1.shift
96
+ end
97
+
98
+ period_values.shift
99
+ end
100
+ end
101
+
102
+ output.sort_by(&:date_time).reverse
103
+ end
104
+
105
+ end
106
+
107
+ # The value class to be returned by calculations
108
+ class TrixValue
109
+
110
+ # @return [String] the date_time of the obversation as it was provided
111
+ attr_accessor :date_time
112
+
113
+ # @return [Float] the trix calculation value
114
+ attr_accessor :trix
115
+
116
+ def initialize(date_time: nil, trix: nil)
117
+ @date_time = date_time
118
+ @trix = trix
119
+ end
120
+
121
+ # @return [Hash] the attributes as a hash
122
+ def to_hash
123
+ { date_time: @date_time, trix: @trix }
124
+ end
125
+
126
+ end
127
+ end
@@ -0,0 +1,139 @@
1
+ module TechnicalAnalysis
2
+ # True Strength Index
3
+ class Tsi < 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
+ "tsi"
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
+ "True Strength 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(low_period high_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(low_period: 13, high_period: 25, **params)
42
+ low_period.to_i + high_period.to_i
43
+ end
44
+
45
+ # Calculates the true strength index (TSI) for the data over the given period
46
+ # https://en.wikipedia.org/wiki/True_strength_index
47
+ #
48
+ # @param data [Array] Array of hashes with keys (:date_time, :value)
49
+ # @param high_period [Integer] The given high period to calculate the EMA
50
+ # @param low_period [Integer] The given low period to calculate the EMA
51
+ # @param price_key [Symbol] The hash key for the price data. Default :value
52
+ #
53
+ # @return [Array<TsiValue>] An array of TsiValue instances
54
+ def self.calculate(data, low_period: 13, high_period: 25, price_key: :value)
55
+ low_period = low_period.to_i
56
+ high_period = high_period.to_i
57
+ price_key = price_key.to_sym
58
+ Validation.validate_numeric_data(data, price_key)
59
+ Validation.validate_length(data, min_data_size(low_period: low_period, high_period: high_period))
60
+ Validation.validate_date_time_key(data)
61
+
62
+ data = data.sort_by { |row| row[:date_time] }
63
+
64
+ high_emas = []
65
+ high_multiplier = (2.0 / (high_period + 1.0))
66
+ low_emas = []
67
+ low_multiplier = (2.0 / (low_period + 1.0))
68
+ momentum_values = []
69
+ output = []
70
+ prev_price = data.shift[price_key]
71
+
72
+ data.each do |v|
73
+ current_price = v[price_key]
74
+ momentum = current_price - prev_price
75
+ momentum_hash = { value: momentum, abs_value: momentum.abs }
76
+
77
+ momentum_values << momentum_hash
78
+
79
+ if momentum_values.size == high_period
80
+ high_emas << process_ema(momentum_hash, momentum_values, high_multiplier, high_period, high_emas)
81
+
82
+ if high_emas.size == low_period
83
+ low_ema = process_ema(high_emas.last, high_emas, low_multiplier, low_period, low_emas)
84
+ low_emas << low_ema
85
+
86
+ output << TsiValue.new(
87
+ date_time: v[:date_time],
88
+ tsi: ((100 * (low_ema[:value] / low_ema[:abs_value])))
89
+ )
90
+
91
+ low_emas.shift if low_emas.size > 1 # Only need to retain the last low_ema
92
+ high_emas.shift
93
+ end
94
+
95
+ momentum_values.shift
96
+ end
97
+
98
+ prev_price = current_price
99
+ end
100
+
101
+ output.sort_by(&:date_time).reverse
102
+ end
103
+
104
+ private_class_method def self.process_ema(current_value, data, multiplier, period, store)
105
+ if store.empty?
106
+ value = ArrayHelper.average(data.map { |d| d[:value] })
107
+ abs_value = ArrayHelper.average(data.map { |d| d[:abs_value] })
108
+ else
109
+ prev_value = store.last
110
+ value = ((multiplier * (current_value[:value] - prev_value[:value])) + prev_value[:value])
111
+ abs_value = ((multiplier * (current_value[:abs_value] - prev_value[:abs_value])) + prev_value[:abs_value])
112
+ end
113
+
114
+ { value: value, abs_value: abs_value }
115
+ end
116
+
117
+ end
118
+
119
+ # The value class to be returned by calculations
120
+ class TsiValue
121
+
122
+ # @return [String] the date_time of the obversation as it was provided
123
+ attr_accessor :date_time
124
+
125
+ # @return [Float] the tsi calculation value
126
+ attr_accessor :tsi
127
+
128
+ def initialize(date_time: nil, tsi: nil)
129
+ @date_time = date_time
130
+ @tsi = tsi
131
+ end
132
+
133
+ # @return [Hash] the attributes as a hash
134
+ def to_hash
135
+ { date_time: @date_time, tsi: @tsi }
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,130 @@
1
+ module TechnicalAnalysis
2
+ # Ultimate Oscillator
3
+ class Uo < 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
+ "uo"
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
+ "Ultimate Oscillator"
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(short_period medium_period long_period short_weight medium_weight long_weight)
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(long_period: 28, **params)
42
+ long_period.to_i + 1
43
+ end
44
+
45
+ # Calculates the ultimate oscillator (UO) for the data over the given period
46
+ # https://en.wikipedia.org/wiki/Ultimate_oscillator
47
+ #
48
+ # @param data [Array] Array of hashes with keys (:date_time, :high, :low, :close)
49
+ # @param short_period [Integer] The given short period
50
+ # @param medium_period [Integer] The given medium period
51
+ # @param long_period [Integer] The given long period
52
+ # @param short_weight [Float] Weight of short Buying Pressure average for UO
53
+ # @param medium_weight [Float] Weight of medium Buying Pressure average for UO
54
+ # @param long_weight [Float] Weight of long Buying Pressure average for UO
55
+ #
56
+ # @return [Array<UoValue>] An array of UoValue instances
57
+ def self.calculate(data, short_period: 7, medium_period: 14, long_period: 28, short_weight: 4, medium_weight: 2, long_weight: 1)
58
+ short_period = short_period.to_i
59
+ medium_period = medium_period.to_i
60
+ long_period = long_period.to_i
61
+ short_weight = short_weight.to_f
62
+ medium_weight = medium_weight.to_f
63
+ long_weight = long_weight.to_f
64
+ Validation.validate_numeric_data(data, :high, :low, :close)
65
+ Validation.validate_length(data, min_data_size(long_period: long_period))
66
+ Validation.validate_date_time_key(data)
67
+
68
+ data = data.sort_by { |row| row[:date_time] }
69
+
70
+ output = []
71
+ period_values = []
72
+ prior_close = data.shift[:close]
73
+ sum_of_weights = ArrayHelper.sum([short_weight, medium_weight, long_weight])
74
+
75
+ data.each do |v|
76
+ min_low_p_close = [v[:low], prior_close].min
77
+ max_high_p_close = [v[:high], prior_close].max
78
+
79
+ buying_pressure = v[:close] - min_low_p_close
80
+ true_range = max_high_p_close - min_low_p_close
81
+
82
+ period_values << { buying_pressure: buying_pressure, true_range: true_range }
83
+
84
+ if period_values.size == long_period
85
+ short_average = calculate_average(short_period, period_values)
86
+ medium_average = calculate_average(medium_period, period_values)
87
+ long_average = calculate_average(long_period, period_values)
88
+ uo = 100 * (((short_weight * short_average) + (medium_weight * medium_average) + (long_weight * long_average)) / (sum_of_weights))
89
+
90
+ output << UoValue.new(date_time: v[:date_time], uo: uo)
91
+
92
+ period_values.shift
93
+ end
94
+
95
+ prior_close = v[:close]
96
+ end
97
+
98
+ output.sort_by(&:date_time).reverse
99
+ end
100
+
101
+ private_class_method def self.calculate_average(period, data)
102
+ buying_pressures_sum = ArrayHelper.sum(data.last(period).map { |d| d[:buying_pressure] })
103
+ true_ranges_sum = ArrayHelper.sum(data.last(period).map { |d| d[:true_range] })
104
+
105
+ buying_pressures_sum / true_ranges_sum
106
+ end
107
+
108
+ end
109
+
110
+ # The value class to be returned by calculations
111
+ class UoValue
112
+
113
+ # @return [String] the date_time of the obversation as it was provided
114
+ attr_accessor :date_time
115
+
116
+ # @return [Float] the uo calculation value
117
+ attr_accessor :uo
118
+
119
+ def initialize(date_time: nil, uo: nil)
120
+ @date_time = date_time
121
+ @uo = uo
122
+ end
123
+
124
+ # @return [Hash] the attributes as a hash
125
+ def to_hash
126
+ { date_time: @date_time, uo: @uo }
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,117 @@
1
+ module TechnicalAnalysis
2
+ # Vortex Indicator
3
+ class Vi < 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
+ "vi"
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
+ "Vortex Indicator"
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 vortex indicator (VI) for the data over the given period
46
+ # https://en.wikipedia.org/wiki/Vortex_indicator
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 VI
50
+ #
51
+ # @return [Array<Hash>] An array of ViValue instances
52
+ def self.calculate(data, period: 14)
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
+ prev_price = data.shift
63
+
64
+ data.each do |v|
65
+ positive_vm = (v[:high] - prev_price[:low]).abs
66
+ negative_vm = (v[:low] - prev_price[:high]).abs
67
+ tr = [(v[:high] - v[:low]), (v[:high] - prev_price[:close]).abs, (v[:low] - prev_price[:close]).abs].max
68
+
69
+ period_values << { pos_vm: positive_vm, neg_vm: negative_vm, tr: tr }
70
+
71
+ if period_values.size == period
72
+ pos_vm_period = ArrayHelper.sum(period_values.map { |pv| pv[:pos_vm] })
73
+ neg_vm_period = ArrayHelper.sum(period_values.map { |pv| pv[:neg_vm] })
74
+ tr_period = ArrayHelper.sum(period_values.map { |pv| pv[:tr] })
75
+
76
+ output << ViValue.new(
77
+ date_time: v[:date_time],
78
+ positive_vi: (pos_vm_period / tr_period),
79
+ negative_vi: (neg_vm_period / tr_period),
80
+ )
81
+
82
+ period_values.shift
83
+ end
84
+
85
+ prev_price = v
86
+ end
87
+
88
+ output.sort_by(&:date_time).reverse
89
+ end
90
+
91
+ end
92
+
93
+ # The value class to be returned by calculations
94
+ class ViValue
95
+
96
+ # @return [String] the date_time of the obversation as it was provided
97
+ attr_accessor :date_time
98
+
99
+ # @return [Float] the positive Vortex Indicator value
100
+ attr_accessor :positive_vi
101
+
102
+ # @return [Float] the negative Vortex Indicator value
103
+ attr_accessor :negative_vi
104
+
105
+ def initialize(date_time: nil, positive_vi: nil, negative_vi: nil)
106
+ @date_time = date_time
107
+ @positive_vi = positive_vi
108
+ @negative_vi = negative_vi
109
+ end
110
+
111
+ # @return [Hash] the attributes as a hash
112
+ def to_hash
113
+ { date_time: @date_time, positive_vi: @positive_vi, negative_vi: @negative_vi }
114
+ end
115
+
116
+ end
117
+ end