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