technical-analysis 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/technical-analysis.rb +3 -0
- data/lib/technical_analysis.rb +41 -0
- data/lib/technical_analysis/helpers/array_helper.rb +27 -0
- data/lib/technical_analysis/helpers/stock_calculation.rb +25 -0
- data/lib/technical_analysis/helpers/validation.rb +33 -0
- data/lib/technical_analysis/indicators/adi.rb +101 -0
- data/lib/technical_analysis/indicators/adtv.rb +98 -0
- data/lib/technical_analysis/indicators/adx.rb +168 -0
- data/lib/technical_analysis/indicators/ao.rb +105 -0
- data/lib/technical_analysis/indicators/atr.rb +109 -0
- data/lib/technical_analysis/indicators/bb.rb +126 -0
- data/lib/technical_analysis/indicators/cci.rb +105 -0
- data/lib/technical_analysis/indicators/cmf.rb +105 -0
- data/lib/technical_analysis/indicators/cr.rb +95 -0
- data/lib/technical_analysis/indicators/dc.rb +108 -0
- data/lib/technical_analysis/indicators/dlr.rb +97 -0
- data/lib/technical_analysis/indicators/dpo.rb +106 -0
- data/lib/technical_analysis/indicators/dr.rb +96 -0
- data/lib/technical_analysis/indicators/eom.rb +104 -0
- data/lib/technical_analysis/indicators/fi.rb +95 -0
- data/lib/technical_analysis/indicators/ichimoku.rb +179 -0
- data/lib/technical_analysis/indicators/indicator.rb +138 -0
- data/lib/technical_analysis/indicators/kc.rb +124 -0
- data/lib/technical_analysis/indicators/kst.rb +132 -0
- data/lib/technical_analysis/indicators/macd.rb +144 -0
- data/lib/technical_analysis/indicators/mfi.rb +119 -0
- data/lib/technical_analysis/indicators/mi.rb +121 -0
- data/lib/technical_analysis/indicators/nvi.rb +102 -0
- data/lib/technical_analysis/indicators/obv.rb +104 -0
- data/lib/technical_analysis/indicators/obv_mean.rb +110 -0
- data/lib/technical_analysis/indicators/rsi.rb +133 -0
- data/lib/technical_analysis/indicators/sma.rb +98 -0
- data/lib/technical_analysis/indicators/sr.rb +122 -0
- data/lib/technical_analysis/indicators/trix.rb +127 -0
- data/lib/technical_analysis/indicators/tsi.rb +139 -0
- data/lib/technical_analysis/indicators/uo.rb +130 -0
- data/lib/technical_analysis/indicators/vi.rb +117 -0
- data/lib/technical_analysis/indicators/vpt.rb +95 -0
- data/lib/technical_analysis/indicators/wr.rb +103 -0
- data/spec/helpers/array_helper_spec.rb +31 -0
- data/spec/helpers/validaton_spec.rb +22 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/ta_test_data.csv +64 -0
- data/spec/technical_analysis/indicators/adi_spec.rb +116 -0
- data/spec/technical_analysis/indicators/adtv_spec.rb +98 -0
- data/spec/technical_analysis/indicators/adx_spec.rb +92 -0
- data/spec/technical_analysis/indicators/ao_spec.rb +86 -0
- data/spec/technical_analysis/indicators/atr_spec.rb +105 -0
- data/spec/technical_analysis/indicators/bb_spec.rb +100 -0
- data/spec/technical_analysis/indicators/cci_spec.rb +100 -0
- data/spec/technical_analysis/indicators/cmf_spec.rb +100 -0
- data/spec/technical_analysis/indicators/cr_spec.rb +119 -0
- data/spec/technical_analysis/indicators/dc_spec.rb +100 -0
- data/spec/technical_analysis/indicators/dlr_spec.rb +119 -0
- data/spec/technical_analysis/indicators/dpo_spec.rb +90 -0
- data/spec/technical_analysis/indicators/dr_spec.rb +119 -0
- data/spec/technical_analysis/indicators/eom_spec.rb +105 -0
- data/spec/technical_analysis/indicators/fi_spec.rb +118 -0
- data/spec/technical_analysis/indicators/ichimoku_spec.rb +95 -0
- data/spec/technical_analysis/indicators/indicator_spec.rb +120 -0
- data/spec/technical_analysis/indicators/kc_spec.rb +110 -0
- data/spec/technical_analysis/indicators/kst_spec.rb +78 -0
- data/spec/technical_analysis/indicators/macd_spec.rb +86 -0
- data/spec/technical_analysis/indicators/mfi_spec.rb +105 -0
- data/spec/technical_analysis/indicators/mi_spec.rb +79 -0
- data/spec/technical_analysis/indicators/nvi_spec.rb +119 -0
- data/spec/technical_analysis/indicators/obv_mean_spec.rb +109 -0
- data/spec/technical_analysis/indicators/obv_spec.rb +119 -0
- data/spec/technical_analysis/indicators/rsi_spec.rb +105 -0
- data/spec/technical_analysis/indicators/sma_spec.rb +115 -0
- data/spec/technical_analysis/indicators/sr_spec.rb +104 -0
- data/spec/technical_analysis/indicators/trix_spec.rb +76 -0
- data/spec/technical_analysis/indicators/tsi_spec.rb +87 -0
- data/spec/technical_analysis/indicators/uo_spec.rb +91 -0
- data/spec/technical_analysis/indicators/vi_spec.rb +105 -0
- data/spec/technical_analysis/indicators/vpt_spec.rb +118 -0
- data/spec/technical_analysis/indicators/wr_spec.rb +106 -0
- metadata +177 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
# Chaikin Money Flow
|
3
|
+
class Cmf < 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
|
+
"cmf"
|
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
|
+
"Chaikin Money Flow"
|
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: 20)
|
42
|
+
period.to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
# Calculates the chaikin money flow (CMF) for the data over the given period
|
46
|
+
# https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:chaikin_money_flow_cmf
|
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 CMF
|
50
|
+
#
|
51
|
+
# @return [Array<CmfValue>] An array of CmfValue instances
|
52
|
+
def self.calculate(data, period: 20)
|
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
|
+
period_values = []
|
62
|
+
|
63
|
+
data.each do |v|
|
64
|
+
multiplier = ((v[:close] - v[:low]) - (v[:high] - v[:close])) / (v[:high] - v[:low])
|
65
|
+
mf_volume = multiplier * v[:volume]
|
66
|
+
|
67
|
+
period_values << { volume: v[:volume], mf_volume: mf_volume }
|
68
|
+
|
69
|
+
if period_values.size == period
|
70
|
+
volume_sum = ArrayHelper.sum(period_values.map { |pv| pv[:volume] })
|
71
|
+
mf_volume_sum = ArrayHelper.sum(period_values.map { |pv| pv[:mf_volume] })
|
72
|
+
cmf = mf_volume_sum / volume_sum
|
73
|
+
|
74
|
+
output << CmfValue.new(date_time: v[:date_time], cmf: cmf)
|
75
|
+
|
76
|
+
period_values.shift
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
output.sort_by(&:date_time).reverse
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
# The value class to be returned by calculations
|
86
|
+
class CmfValue
|
87
|
+
|
88
|
+
# @return [String] the date_time of the obversation as it was provided
|
89
|
+
attr_accessor :date_time
|
90
|
+
|
91
|
+
# @return [Float] the cmf calculation value
|
92
|
+
attr_accessor :cmf
|
93
|
+
|
94
|
+
def initialize(date_time: nil, cmf: nil)
|
95
|
+
@date_time = date_time
|
96
|
+
@cmf = cmf
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [Hash] the attributes as a hash
|
100
|
+
def to_hash
|
101
|
+
{ date_time: @date_time, cmf: @cmf }
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
# Cumulative Return
|
3
|
+
class Cr < 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
|
+
"cr"
|
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
|
+
"Cumulative 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 cumulative return (CR) for the data over the given period
|
46
|
+
# https://www.investopedia.com/terms/c/cumulativereturn.asp
|
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<CrValue>] An array of CrValue 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
|
+
start_price = data.first[price_key]
|
62
|
+
|
63
|
+
data.each do |v|
|
64
|
+
output << CrValue.new(
|
65
|
+
date_time: v[:date_time],
|
66
|
+
cr: ((v[price_key] - start_price) / start_price)
|
67
|
+
)
|
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 CrValue
|
77
|
+
|
78
|
+
# @return [String] the date_time of the obversation as it was provided
|
79
|
+
attr_accessor :date_time
|
80
|
+
|
81
|
+
# @return [Float] the cr calculation value
|
82
|
+
attr_accessor :cr
|
83
|
+
|
84
|
+
def initialize(date_time: nil, cr: nil)
|
85
|
+
@date_time = date_time
|
86
|
+
@cr = cr
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Hash] the attributes as a hash
|
90
|
+
def to_hash
|
91
|
+
{ date_time: @date_time, cr: @cr }
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
# Donchian Channel
|
3
|
+
class Dc < 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
|
+
"dc"
|
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
|
+
"Donchian Channel"
|
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: 20, **params)
|
42
|
+
period.to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
# Calculates the donchian channel (DC) for the data over the given period
|
46
|
+
# https://en.wikipedia.org/wiki/Donchian_channel
|
47
|
+
#
|
48
|
+
# @param data [Array] Array of hashes with keys (:date_time, :value)
|
49
|
+
# @param period [Integer] The given period to calculate the DC
|
50
|
+
# @param price_key [Symbol] The hash key for the price data. Default :value
|
51
|
+
#
|
52
|
+
# @return [Array<DcValue>] An array of DcValue instances
|
53
|
+
def self.calculate(data, period: 20, 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
|
+
output = []
|
63
|
+
period_values = []
|
64
|
+
|
65
|
+
data.each do |v|
|
66
|
+
period_values << v[price_key]
|
67
|
+
|
68
|
+
if period_values.size == period
|
69
|
+
output << DcValue.new(
|
70
|
+
date_time: v[:date_time],
|
71
|
+
upper_bound: period_values.max,
|
72
|
+
lower_bound: period_values.min
|
73
|
+
)
|
74
|
+
|
75
|
+
period_values.shift
|
76
|
+
end
|
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 DcValue
|
86
|
+
|
87
|
+
# @return [String] the date_time of the obversation as it was provided
|
88
|
+
attr_accessor :date_time
|
89
|
+
|
90
|
+
# @return [Float] the upper_bound calculation value
|
91
|
+
attr_accessor :upper_bound
|
92
|
+
|
93
|
+
# @return [Float] the lower_bound calculation value
|
94
|
+
attr_accessor :lower_bound
|
95
|
+
|
96
|
+
def initialize(date_time: nil, upper_bound: nil, lower_bound: ninl)
|
97
|
+
@date_time = date_time
|
98
|
+
@upper_bound = upper_bound
|
99
|
+
@lower_bound = lower_bound
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Hash] the attributes as a hash
|
103
|
+
def to_hash
|
104
|
+
{ date_time: @date_time, upper_bound: @upper_bound, lower_bound: @lower_bound }
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
# Daily Log Return
|
3
|
+
class Dlr < 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
|
+
"dlr"
|
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 Log 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 log return (percent expressed as a decimal) for the data over the given period
|
46
|
+
# https://www.quora.com/What-are-daily-log-returns-of-an-equity
|
47
|
+
# https://en.wikipedia.org/wiki/Rate_of_return#Logarithmic_or_continuously_compounded_return
|
48
|
+
#
|
49
|
+
# @param data [Array] Array of hashes with keys (:date_time, :value)
|
50
|
+
# @param price_key [Symbol] The hash key for the price data. Default :value
|
51
|
+
#
|
52
|
+
# @return [Array<DlrValue>] An array of DlrValue instances
|
53
|
+
def self.calculate(data, price_key: :value)
|
54
|
+
price_key = price_key.to_sym
|
55
|
+
Validation.validate_numeric_data(data, price_key)
|
56
|
+
Validation.validate_length(data, min_data_size({}))
|
57
|
+
Validation.validate_date_time_key(data)
|
58
|
+
|
59
|
+
data = data.sort_by { |row| row[:date_time] }
|
60
|
+
|
61
|
+
output = []
|
62
|
+
prev_price = data.first[price_key].to_f
|
63
|
+
|
64
|
+
data.each do |v|
|
65
|
+
current_price = v[:close].to_f
|
66
|
+
|
67
|
+
output << DlrValue.new(date_time: v[:date_time], dlr: Math.log(current_price / prev_price))
|
68
|
+
|
69
|
+
prev_price = current_price
|
70
|
+
end
|
71
|
+
|
72
|
+
output.sort_by(&:date_time).reverse
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
# The value class to be returned by calculations
|
78
|
+
class DlrValue
|
79
|
+
|
80
|
+
# @return [String] the date_time of the obversation as it was provided
|
81
|
+
attr_accessor :date_time
|
82
|
+
|
83
|
+
# @return [Float] the dlr calculation value
|
84
|
+
attr_accessor :dlr
|
85
|
+
|
86
|
+
def initialize(date_time: nil, dlr: nil)
|
87
|
+
@date_time = date_time
|
88
|
+
@dlr = dlr
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [Hash] the attributes as a hash
|
92
|
+
def to_hash
|
93
|
+
{ date_time: @date_time, dlr: @dlr }
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
# Detrended Price Oscillator
|
3
|
+
class Dpo < 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
|
+
"dpo"
|
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
|
+
"Detrended Price 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(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: 20, **params)
|
42
|
+
period.to_i + (period.to_i / 2)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Calculates the detrended price oscillator for the data over the given period
|
46
|
+
# https://en.wikipedia.org/wiki/Detrended_price_oscillator
|
47
|
+
#
|
48
|
+
# @param data [Array] Array of hashes with keys (:date_time, :value)
|
49
|
+
# @param period [Integer] The given period to calculate the SMA
|
50
|
+
# @param price_key [Symbol] The hash key for the price data. Default :value
|
51
|
+
#
|
52
|
+
# @return [Array<DpoValue>]
|
53
|
+
def self.calculate(data, period: 20, 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
|
+
index = period + (period / 2) - 1
|
63
|
+
midpoint_index = (period / 2) + 1
|
64
|
+
output = []
|
65
|
+
|
66
|
+
while index < data.size
|
67
|
+
current_record = data[index]
|
68
|
+
date_time = current_record[:date_time]
|
69
|
+
current_price = current_record[price_key]
|
70
|
+
|
71
|
+
sma_range = (index - midpoint_index - period + 2)..(index - midpoint_index + 1)
|
72
|
+
midpoint_period_sma = ArrayHelper.average(data[sma_range].map { |v| v[price_key] })
|
73
|
+
|
74
|
+
dpo = (current_price - midpoint_period_sma)
|
75
|
+
|
76
|
+
output << DpoValue.new(date_time: date_time, dpo: dpo)
|
77
|
+
|
78
|
+
index += 1
|
79
|
+
end
|
80
|
+
|
81
|
+
output.sort_by(&:date_time).reverse
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
# The value class to be returned by calculations
|
87
|
+
class DpoValue
|
88
|
+
|
89
|
+
# @return [String] the date_time of the obversation as it was provided
|
90
|
+
attr_accessor :date_time
|
91
|
+
|
92
|
+
# @return [Float] the dpo calculation value
|
93
|
+
attr_accessor :dpo
|
94
|
+
|
95
|
+
def initialize(date_time: nil, dpo: nil)
|
96
|
+
@date_time = date_time
|
97
|
+
@dpo = dpo
|
98
|
+
end
|
99
|
+
|
100
|
+
# @return [Hash] the attributes as a hash
|
101
|
+
def to_hash
|
102
|
+
{ date_time: @date_time, dpo: @dpo }
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|