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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f408c4203ba656b1aa458266b8ed6ec656f91f2e
|
4
|
+
data.tar.gz: 18c7d6c4465b9ddbf6627508672e3a8f3295a597
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dcb6a96e644cc93c7f48866aa067c6cac7ca7c226cfeb3a71bd4a1789f258dee846b19c774a95560001cff93a8e6c693d4386a3247ea7aebb404beb37121f7bb
|
7
|
+
data.tar.gz: 42e5e7bbfedb0f48a92b5107edfa2fda26c616cdb67bd91984e53fbd47f145fb50323da63268b34722202c71029d75fabfd94dba0136497baf508e874289c877
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Helpers
|
2
|
+
require 'technical_analysis/helpers/array_helper'
|
3
|
+
require 'technical_analysis/helpers/stock_calculation'
|
4
|
+
require 'technical_analysis/helpers/validation'
|
5
|
+
|
6
|
+
# Indicators
|
7
|
+
require 'technical_analysis/indicators/indicator'
|
8
|
+
|
9
|
+
require 'technical_analysis/indicators/adi'
|
10
|
+
require 'technical_analysis/indicators/adtv'
|
11
|
+
require 'technical_analysis/indicators/adx'
|
12
|
+
require 'technical_analysis/indicators/ao'
|
13
|
+
require 'technical_analysis/indicators/atr'
|
14
|
+
require 'technical_analysis/indicators/bb'
|
15
|
+
require 'technical_analysis/indicators/cci'
|
16
|
+
require 'technical_analysis/indicators/cmf'
|
17
|
+
require 'technical_analysis/indicators/cr'
|
18
|
+
require 'technical_analysis/indicators/dc'
|
19
|
+
require 'technical_analysis/indicators/dlr'
|
20
|
+
require 'technical_analysis/indicators/dpo'
|
21
|
+
require 'technical_analysis/indicators/dr'
|
22
|
+
require 'technical_analysis/indicators/eom'
|
23
|
+
require 'technical_analysis/indicators/fi'
|
24
|
+
require 'technical_analysis/indicators/ichimoku'
|
25
|
+
require 'technical_analysis/indicators/kc'
|
26
|
+
require 'technical_analysis/indicators/kst'
|
27
|
+
require 'technical_analysis/indicators/macd'
|
28
|
+
require 'technical_analysis/indicators/mfi'
|
29
|
+
require 'technical_analysis/indicators/mi'
|
30
|
+
require 'technical_analysis/indicators/nvi'
|
31
|
+
require 'technical_analysis/indicators/obv'
|
32
|
+
require 'technical_analysis/indicators/obv_mean'
|
33
|
+
require 'technical_analysis/indicators/rsi'
|
34
|
+
require 'technical_analysis/indicators/sma'
|
35
|
+
require 'technical_analysis/indicators/sr'
|
36
|
+
require 'technical_analysis/indicators/trix'
|
37
|
+
require 'technical_analysis/indicators/tsi'
|
38
|
+
require 'technical_analysis/indicators/uo'
|
39
|
+
require 'technical_analysis/indicators/vi'
|
40
|
+
require 'technical_analysis/indicators/vpt'
|
41
|
+
require 'technical_analysis/indicators/wr'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
class ArrayHelper
|
3
|
+
|
4
|
+
def self.sum(data)
|
5
|
+
data.inject(0, :+)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.mean(data)
|
9
|
+
sum(data) / data.size.to_f
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.average(data)
|
13
|
+
sum(data) / data.size.to_f
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.sample_variance(data)
|
17
|
+
m = mean(data)
|
18
|
+
sum = data.inject(0) { |accum, i| accum + (i - m)**2 }
|
19
|
+
sum / (data.size - 1).to_f
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.standard_deviation(data)
|
23
|
+
Math.sqrt(sample_variance(data))
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
class StockCalculation
|
3
|
+
|
4
|
+
def self.true_range(current_high, current_low, previous_close)
|
5
|
+
[
|
6
|
+
(current_high - current_low),
|
7
|
+
(current_high - previous_close).abs,
|
8
|
+
(current_low - previous_close).abs
|
9
|
+
].max
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.typical_price(price)
|
13
|
+
(price[:high] + price[:low] + price[:close]) / 3.0
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.ema(current_value, data, period, prev_value)
|
17
|
+
if prev_value.nil?
|
18
|
+
ArrayHelper.average(data)
|
19
|
+
else
|
20
|
+
(current_value - prev_value) * (2.0 / (period + 1.0)) + prev_value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
class Validation
|
3
|
+
|
4
|
+
def self.validate_numeric_data(data, *keys)
|
5
|
+
keys.each do |key|
|
6
|
+
unless data.all? { |v| v[key].is_a? Numeric }
|
7
|
+
raise ValidationError.new "Invalid Data. '#{key}' is not valid price data."
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.validate_length(data, size)
|
13
|
+
raise ValidationError.new "Not enough data for that period" if data.size < size
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.validate_options(options, valid_options)
|
17
|
+
raise ValidationError.new "Options must be a hash." unless options.respond_to? :keys
|
18
|
+
raise ValidationError.new "No valid options provided." unless valid_options
|
19
|
+
|
20
|
+
return true if (options.keys - valid_options).empty?
|
21
|
+
raise ValidationError.new "Invalid options provided. Valid options are #{valid_options.join(", ")}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.validate_date_time_key(data)
|
25
|
+
unless data.all? { |row| row.keys.include? :date_time }
|
26
|
+
raise ValidationError.new "Dataset must include date_time field with timestamps"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class ValidationError < StandardError; end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
# Accumulation/Distribution Index
|
3
|
+
class Adi < 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
|
+
"adi"
|
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
|
+
"Accumulation/Distribution 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 Accumulation/Distribution Index (ADI) for the given data
|
47
|
+
# https://en.wikipedia.org/wiki/Accumulation/distribution_index
|
48
|
+
#
|
49
|
+
# @param data [Array] Array of hashes with keys (:date_time, :high, :low, :close, :volume)
|
50
|
+
# @return [Array<AdiValue>] An array of AdiValue instances
|
51
|
+
def self.calculate(data)
|
52
|
+
Validation.validate_numeric_data(data, :high, :low, :close, :volume)
|
53
|
+
Validation.validate_date_time_key(data)
|
54
|
+
|
55
|
+
data = data.sort_by { |row| row[:date_time] }
|
56
|
+
|
57
|
+
ad = 0
|
58
|
+
clv = 0
|
59
|
+
output = []
|
60
|
+
prev_ad = 0
|
61
|
+
|
62
|
+
data.each_with_index do |values, i|
|
63
|
+
if values[:high] == values[:low]
|
64
|
+
clv = 0
|
65
|
+
else
|
66
|
+
clv = ((values[:close] - values[:low]) - (values[:high] - values[:close])) / (values[:high] - values[:low])
|
67
|
+
end
|
68
|
+
|
69
|
+
ad = prev_ad + (clv * values[:volume])
|
70
|
+
prev_ad = ad
|
71
|
+
date_time = values[:date_time]
|
72
|
+
|
73
|
+
output << AdiValue.new(date_time: date_time, adi: ad)
|
74
|
+
end
|
75
|
+
|
76
|
+
output.sort_by(&:date_time).reverse
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
# The value class to be returned by calculations
|
82
|
+
class AdiValue
|
83
|
+
|
84
|
+
# @return [String] the date_time of the obversation as it was provided
|
85
|
+
attr_accessor :date_time
|
86
|
+
|
87
|
+
# @return [Float] the adi calculation value
|
88
|
+
attr_accessor :adi
|
89
|
+
|
90
|
+
def initialize(date_time: nil, adi: nil)
|
91
|
+
@date_time = date_time
|
92
|
+
@adi = adi
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [Hash] the attributes as a hash
|
96
|
+
def to_hash
|
97
|
+
{ date_time: @date_time, adi: @adi }
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
# Average Daily Trading Volume
|
3
|
+
class Adtv < 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
|
+
"adtv"
|
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
|
+
"Average Daily Trading 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
|
+
%i(period volume_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: 22, **params)
|
42
|
+
period.to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
# Calculates the average daily trading volume (ADTV) for the data over the given period
|
46
|
+
# https://www.investopedia.com/terms/a/averagedailytradingvolume.asp
|
47
|
+
#
|
48
|
+
# @param data [Array] Array of hashes with keys (:date_time, :value)
|
49
|
+
# @param period [Integer] The given number of days used to calculate the ADTV
|
50
|
+
# @param volume_key [Symbol] The hash key for the volume data. Default :value
|
51
|
+
#
|
52
|
+
# @return [Array<AdtvValue>] An array of AdtvValue instances
|
53
|
+
def self.calculate(data, period: 22, volume_key: :value)
|
54
|
+
period = period.to_i
|
55
|
+
volume_key = volume_key.to_sym
|
56
|
+
Validation.validate_numeric_data(data, volume_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[volume_key]
|
67
|
+
if period_values.size == period
|
68
|
+
output << AdtvValue.new(date_time: v[:date_time], adtv: ArrayHelper.average(period_values))
|
69
|
+
period_values.shift
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
output.sort_by(&:date_time).reverse
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
# The value class to be returned by calculations
|
79
|
+
class AdtvValue
|
80
|
+
|
81
|
+
# @return [String] the date_time of the obversation as it was provided
|
82
|
+
attr_accessor :date_time
|
83
|
+
|
84
|
+
# @return [Float] the adtv calculation value
|
85
|
+
attr_accessor :adtv
|
86
|
+
|
87
|
+
def initialize(date_time: nil, adtv: nil)
|
88
|
+
@date_time = date_time
|
89
|
+
@adtv = adtv
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [Hash] the attributes as a hash
|
93
|
+
def to_hash
|
94
|
+
{ date_time: @date_time, adtv: @adtv }
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
# Average Direcitonal Index
|
3
|
+
class Adx < 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
|
+
"adx"
|
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
|
+
"Average Directional 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 * 2
|
43
|
+
end
|
44
|
+
|
45
|
+
# Calculates the average directional index (ADX) for the data over the given period
|
46
|
+
# https://en.wikipedia.org/wiki/Average_directional_movement_index
|
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 ADX
|
50
|
+
#
|
51
|
+
# @return [Array<AdxValue>] An array of AdxValue 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
|
+
dx_values = []
|
61
|
+
output = []
|
62
|
+
periodic_values = []
|
63
|
+
prev_adx = nil
|
64
|
+
prev_price = data.shift
|
65
|
+
smoothed_values = []
|
66
|
+
|
67
|
+
data.each do |v|
|
68
|
+
tr = StockCalculation.true_range(v[:high], v[:low], prev_price[:close])
|
69
|
+
|
70
|
+
dm_pos, dm_neg = calculate_dm(v, prev_price)
|
71
|
+
|
72
|
+
periodic_values << { tr: tr, dm_pos: dm_pos, dm_neg: dm_neg }
|
73
|
+
|
74
|
+
if periodic_values.size == period
|
75
|
+
tr_period, dm_pos_period, dm_neg_period = smooth_periodic_values(period, periodic_values, smoothed_values)
|
76
|
+
smoothed_values << { tr: tr_period, dm_pos: dm_pos_period, dm_neg: dm_neg_period }
|
77
|
+
|
78
|
+
di_pos = (dm_pos_period / tr_period) * 100.00
|
79
|
+
di_neg = (dm_neg_period / tr_period) * 100.00
|
80
|
+
dx = ((dm_pos_period - dm_neg_period).abs / (dm_pos_period + dm_neg_period) * 100.00)
|
81
|
+
|
82
|
+
dx_values << dx
|
83
|
+
|
84
|
+
if dx_values.size == period
|
85
|
+
if prev_adx.nil?
|
86
|
+
adx = ArrayHelper.average(dx_values)
|
87
|
+
else
|
88
|
+
adx = ((prev_adx * 13) + dx) / period.to_f
|
89
|
+
end
|
90
|
+
|
91
|
+
output << AdxValue.new(date_time: v[:date_time], adx: adx, di_pos: di_pos, di_neg: di_neg)
|
92
|
+
|
93
|
+
prev_adx = adx
|
94
|
+
dx_values.shift
|
95
|
+
end
|
96
|
+
|
97
|
+
periodic_values.shift
|
98
|
+
end
|
99
|
+
|
100
|
+
prev_price = v
|
101
|
+
end
|
102
|
+
|
103
|
+
output.sort_by(&:date_time).reverse
|
104
|
+
end
|
105
|
+
|
106
|
+
private_class_method def self.calculate_dm(current_price, prev_price)
|
107
|
+
if current_price[:high] - prev_price[:high] > prev_price[:low] - current_price[:low]
|
108
|
+
dm_pos = [(current_price[:high] - prev_price[:high]), 0].max
|
109
|
+
dm_neg = 0
|
110
|
+
elsif prev_price[:low] - current_price[:low] > current_price[:high] - prev_price[:high]
|
111
|
+
dm_pos = 0
|
112
|
+
dm_neg = [(prev_price[:low] - current_price[:low]), 0].max
|
113
|
+
else
|
114
|
+
dm_pos = 0
|
115
|
+
dm_neg = 0
|
116
|
+
end
|
117
|
+
|
118
|
+
[dm_pos, dm_neg]
|
119
|
+
end
|
120
|
+
|
121
|
+
private_class_method def self.smooth_periodic_values(period, periodic_values, smoothed_values)
|
122
|
+
if smoothed_values.empty?
|
123
|
+
tr_period = ArrayHelper.sum(periodic_values.map { |pv| pv[:tr] })
|
124
|
+
dm_pos_period = ArrayHelper.sum(periodic_values.map { |pv| pv[:dm_pos] })
|
125
|
+
dm_neg_period = ArrayHelper.sum(periodic_values.map { |pv| pv[:dm_neg] })
|
126
|
+
else
|
127
|
+
prev_value = smoothed_values.last
|
128
|
+
current_value = periodic_values.last
|
129
|
+
|
130
|
+
tr_period = prev_value[:tr] - (prev_value[:tr] / period) + current_value[:tr]
|
131
|
+
dm_pos_period = prev_value[:dm_pos] - (prev_value[:dm_pos] / period) + current_value[:dm_pos]
|
132
|
+
dm_neg_period = prev_value[:dm_neg] - (prev_value[:dm_neg] / period) + current_value[:dm_neg]
|
133
|
+
end
|
134
|
+
|
135
|
+
[tr_period, dm_pos_period, dm_neg_period]
|
136
|
+
end
|
137
|
+
|
138
|
+
# The value class to be returned by calculations
|
139
|
+
class AdxValue
|
140
|
+
|
141
|
+
# @return [String] the date_time of the obversation as it was provided
|
142
|
+
attr_accessor :date_time
|
143
|
+
|
144
|
+
# @return [Float] the adx calculation value
|
145
|
+
attr_accessor :adx
|
146
|
+
|
147
|
+
# @return [Float] the positive directional indicator calculation value
|
148
|
+
attr_accessor :di_pos
|
149
|
+
|
150
|
+
# @return [Float] the negative directional indicator calculation value
|
151
|
+
attr_accessor :di_neg
|
152
|
+
|
153
|
+
def initialize(date_time: nil, adx: nil, di_pos: nil, di_neg: nil)
|
154
|
+
@date_time = date_time
|
155
|
+
@adx = adx
|
156
|
+
@di_pos = di_pos
|
157
|
+
@di_neg = di_neg
|
158
|
+
end
|
159
|
+
|
160
|
+
# @return [Hash] the attributes as a hash
|
161
|
+
def to_hash
|
162
|
+
{ date_time: @date_time, adx: @adx, di_pos: @di_pos, di_neg: @di_neg }
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|