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