technical-analysis 0.1.1 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/technical-analysis.rb +0 -0
- data/lib/technical_analysis.rb +3 -0
- data/lib/technical_analysis/helpers/array_helper.rb +0 -0
- data/lib/technical_analysis/helpers/stock_calculation.rb +8 -0
- data/lib/technical_analysis/helpers/validation.rb +4 -4
- data/lib/technical_analysis/indicators/adi.rb +0 -0
- data/lib/technical_analysis/indicators/adtv.rb +0 -0
- data/lib/technical_analysis/indicators/adx.rb +1 -1
- data/lib/technical_analysis/indicators/ao.rb +0 -0
- data/lib/technical_analysis/indicators/atr.rb +0 -0
- data/lib/technical_analysis/indicators/bb.rb +0 -0
- data/lib/technical_analysis/indicators/cci.rb +0 -0
- data/lib/technical_analysis/indicators/cmf.rb +0 -0
- data/lib/technical_analysis/indicators/cr.rb +0 -0
- data/lib/technical_analysis/indicators/dc.rb +0 -0
- data/lib/technical_analysis/indicators/dlr.rb +0 -0
- data/lib/technical_analysis/indicators/dpo.rb +0 -0
- data/lib/technical_analysis/indicators/dr.rb +0 -0
- data/lib/technical_analysis/indicators/ema.rb +103 -0
- data/lib/technical_analysis/indicators/eom.rb +0 -0
- data/lib/technical_analysis/indicators/fi.rb +0 -0
- data/lib/technical_analysis/indicators/ichimoku.rb +0 -0
- data/lib/technical_analysis/indicators/indicator.rb +1 -0
- data/lib/technical_analysis/indicators/kc.rb +0 -0
- data/lib/technical_analysis/indicators/kst.rb +0 -0
- data/lib/technical_analysis/indicators/macd.rb +0 -0
- data/lib/technical_analysis/indicators/mfi.rb +0 -0
- data/lib/technical_analysis/indicators/mi.rb +0 -0
- data/lib/technical_analysis/indicators/nvi.rb +0 -0
- data/lib/technical_analysis/indicators/obv.rb +0 -0
- data/lib/technical_analysis/indicators/obv_mean.rb +0 -0
- data/lib/technical_analysis/indicators/rsi.rb +6 -5
- data/lib/technical_analysis/indicators/sma.rb +7 -5
- data/lib/technical_analysis/indicators/sr.rb +0 -0
- data/lib/technical_analysis/indicators/trix.rb +0 -0
- data/lib/technical_analysis/indicators/tsi.rb +0 -0
- data/lib/technical_analysis/indicators/uo.rb +0 -0
- data/lib/technical_analysis/indicators/vi.rb +0 -0
- data/lib/technical_analysis/indicators/vpt.rb +0 -0
- data/lib/technical_analysis/indicators/vwap.rb +97 -0
- data/lib/technical_analysis/indicators/wma.rb +100 -0
- data/lib/technical_analysis/indicators/wr.rb +0 -0
- data/spec/helpers/array_helper_spec.rb +0 -0
- data/spec/helpers/validaton_spec.rb +0 -0
- data/spec/spec_helper.rb +2 -2
- data/spec/ta_test_data.csv +0 -0
- data/spec/technical_analysis/indicators/adi_spec.rb +0 -0
- data/spec/technical_analysis/indicators/adtv_spec.rb +0 -0
- data/spec/technical_analysis/indicators/adx_spec.rb +0 -0
- data/spec/technical_analysis/indicators/ao_spec.rb +0 -0
- data/spec/technical_analysis/indicators/atr_spec.rb +0 -0
- data/spec/technical_analysis/indicators/bb_spec.rb +0 -0
- data/spec/technical_analysis/indicators/cci_spec.rb +0 -0
- data/spec/technical_analysis/indicators/cmf_spec.rb +0 -0
- data/spec/technical_analysis/indicators/cr_spec.rb +0 -0
- data/spec/technical_analysis/indicators/dc_spec.rb +0 -0
- data/spec/technical_analysis/indicators/dlr_spec.rb +0 -0
- data/spec/technical_analysis/indicators/dpo_spec.rb +0 -0
- data/spec/technical_analysis/indicators/dr_spec.rb +0 -0
- data/spec/technical_analysis/indicators/ema_spec.rb +115 -0
- data/spec/technical_analysis/indicators/eom_spec.rb +0 -0
- data/spec/technical_analysis/indicators/fi_spec.rb +0 -0
- data/spec/technical_analysis/indicators/ichimoku_spec.rb +0 -0
- data/spec/technical_analysis/indicators/indicator_spec.rb +1 -1
- data/spec/technical_analysis/indicators/kc_spec.rb +0 -0
- data/spec/technical_analysis/indicators/kst_spec.rb +0 -0
- data/spec/technical_analysis/indicators/macd_spec.rb +0 -0
- data/spec/technical_analysis/indicators/mfi_spec.rb +0 -0
- data/spec/technical_analysis/indicators/mi_spec.rb +0 -0
- data/spec/technical_analysis/indicators/nvi_spec.rb +0 -0
- data/spec/technical_analysis/indicators/obv_mean_spec.rb +0 -0
- data/spec/technical_analysis/indicators/obv_spec.rb +0 -0
- data/spec/technical_analysis/indicators/rsi_spec.rb +5 -5
- data/spec/technical_analysis/indicators/sma_spec.rb +4 -4
- data/spec/technical_analysis/indicators/sr_spec.rb +0 -0
- data/spec/technical_analysis/indicators/trix_spec.rb +0 -0
- data/spec/technical_analysis/indicators/tsi_spec.rb +0 -0
- data/spec/technical_analysis/indicators/uo_spec.rb +0 -0
- data/spec/technical_analysis/indicators/vi_spec.rb +0 -0
- data/spec/technical_analysis/indicators/vpt_spec.rb +0 -0
- data/spec/technical_analysis/indicators/vwap_spec.rb +119 -0
- data/spec/technical_analysis/indicators/wma_spec.rb +115 -0
- data/spec/technical_analysis/indicators/wr_spec.rb +0 -0
- metadata +12 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 242a71fb8aedb7315b625ded3707bb7c3b1fbf3e
|
4
|
+
data.tar.gz: dd8f5b9980aa256206126ba688537f092ba8c72a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de0cb79b308cbac50018e8ec5f33a6a1c5849454c13d9d65a0f76eedfda85858e6129681ba3c584c2bb14330e30c6af190180d3d151ed731a23dc9e6604d31a5
|
7
|
+
data.tar.gz: 240f61c674561c0f2e428bf27cb09cddaf11840796cc8eafab48b56a45de3548894030290f6fdf301d764a27379797a17359245a559fc09d651e18186866480b
|
data/lib/technical-analysis.rb
CHANGED
File without changes
|
data/lib/technical_analysis.rb
CHANGED
@@ -19,6 +19,7 @@ require 'technical_analysis/indicators/dc'
|
|
19
19
|
require 'technical_analysis/indicators/dlr'
|
20
20
|
require 'technical_analysis/indicators/dpo'
|
21
21
|
require 'technical_analysis/indicators/dr'
|
22
|
+
require 'technical_analysis/indicators/ema'
|
22
23
|
require 'technical_analysis/indicators/eom'
|
23
24
|
require 'technical_analysis/indicators/fi'
|
24
25
|
require 'technical_analysis/indicators/ichimoku'
|
@@ -38,4 +39,6 @@ require 'technical_analysis/indicators/tsi'
|
|
38
39
|
require 'technical_analysis/indicators/uo'
|
39
40
|
require 'technical_analysis/indicators/vi'
|
40
41
|
require 'technical_analysis/indicators/vpt'
|
42
|
+
require 'technical_analysis/indicators/vwap'
|
43
|
+
require 'technical_analysis/indicators/wma'
|
41
44
|
require 'technical_analysis/indicators/wr'
|
File without changes
|
@@ -21,5 +21,13 @@ module TechnicalAnalysis
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
def self.wma(data)
|
25
|
+
intermediate_values = []
|
26
|
+
data.each_with_index do |datum, i|
|
27
|
+
intermediate_values << datum * (i + 1)/(data.size * (data.size + 1)/2).to_f
|
28
|
+
end
|
29
|
+
ArrayHelper.sum(intermediate_values)
|
30
|
+
end
|
31
|
+
|
24
32
|
end
|
25
33
|
end
|
@@ -20,10 +20,10 @@ module TechnicalAnalysis
|
|
20
20
|
return true if (options.keys - valid_options).empty?
|
21
21
|
raise ValidationError.new "Invalid options provided. Valid options are #{valid_options.join(", ")}"
|
22
22
|
end
|
23
|
-
|
24
|
-
def self.validate_date_time_key(data)
|
25
|
-
unless data.all? { |row| row.keys.include?
|
26
|
-
raise ValidationError.new "Dataset must include
|
23
|
+
|
24
|
+
def self.validate_date_time_key(data, date_time_key=:date_time)
|
25
|
+
unless data.all? { |row| row.keys.include? date_time_key }
|
26
|
+
raise ValidationError.new "Dataset must include '#{date_time_key}' field with timestamps"
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
File without changes
|
File without changes
|
@@ -85,7 +85,7 @@ module TechnicalAnalysis
|
|
85
85
|
if prev_adx.nil?
|
86
86
|
adx = ArrayHelper.average(dx_values)
|
87
87
|
else
|
88
|
-
adx = ((prev_adx *
|
88
|
+
adx = ((prev_adx * (period - 1)) + dx) / period.to_f
|
89
89
|
end
|
90
90
|
|
91
91
|
output << AdxValue.new(date_time: v[:date_time], adx: adx, di_pos: di_pos, di_neg: di_neg)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
# Exponential Moving Average
|
3
|
+
class Ema < 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
|
+
"ema"
|
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
|
+
"Exponential Moving 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 date_time_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: 30, **params)
|
42
|
+
period.to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
# Calculates the exponential moving average (EMA) for the data over the given period
|
46
|
+
# https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
|
47
|
+
#
|
48
|
+
# @param data [Array] Array of hashes with keys (:date_time, :value)
|
49
|
+
# @param period [Integer] The given period to calculate the EMA
|
50
|
+
# @param price_key [Symbol] The hash key for the price data. Default :value
|
51
|
+
# @param date_time_key [Symbol] The hash key for the date time data. Default :date_time
|
52
|
+
#
|
53
|
+
# @return [Array<EmaValue>] An array of EmaValue instances
|
54
|
+
def self.calculate(data, period: 30, price_key: :value, date_time_key: :date_time)
|
55
|
+
period = period.to_i
|
56
|
+
price_key = price_key.to_sym
|
57
|
+
date_time_key = date_time_key.to_sym
|
58
|
+
Validation.validate_numeric_data(data, price_key)
|
59
|
+
Validation.validate_length(data, min_data_size(period: period))
|
60
|
+
Validation.validate_date_time_key(data, date_time_key)
|
61
|
+
|
62
|
+
data = data.sort_by { |row| row[date_time_key] }
|
63
|
+
|
64
|
+
output = []
|
65
|
+
period_values = []
|
66
|
+
previous_ema = nil
|
67
|
+
|
68
|
+
data.each do |v|
|
69
|
+
period_values << v[price_key]
|
70
|
+
if period_values.size == period
|
71
|
+
ema = StockCalculation.ema(v[price_key], period_values, period, previous_ema)
|
72
|
+
previous_ema = ema
|
73
|
+
|
74
|
+
output << EmaValue.new(date_time: v[date_time_key], ema: ema)
|
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 EmaValue
|
86
|
+
|
87
|
+
# @return [String] the date_time of the obversation as it was provided
|
88
|
+
attr_accessor :date_time
|
89
|
+
|
90
|
+
# @return [Float] the ema calculation value
|
91
|
+
attr_accessor :ema
|
92
|
+
|
93
|
+
def initialize(date_time: nil, ema: nil)
|
94
|
+
@date_time = date_time
|
95
|
+
@ema = ema
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [Hash] the attributes as a hash
|
99
|
+
def to_hash
|
100
|
+
{ date_time: @date_time, ema: @ema }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -20,7 +20,7 @@ module TechnicalAnalysis
|
|
20
20
|
#
|
21
21
|
# @return [Array] An array of keys as symbols for valid options for this technical indicator
|
22
22
|
def self.valid_options
|
23
|
-
%i(period price_key)
|
23
|
+
%i(period price_key date_time_key)
|
24
24
|
end
|
25
25
|
|
26
26
|
# Validates the provided options for this technical indicator
|
@@ -48,16 +48,17 @@ module TechnicalAnalysis
|
|
48
48
|
# @param data [Array] Array of hashes with keys (:date_time, :value)
|
49
49
|
# @param period [Integer] The given period to calculate the RSI
|
50
50
|
# @param price_key [Symbol] The hash key for the price data. Default :value
|
51
|
+
# @param date_time_key [Symbol] The hash key for the date time data. Default :date_time
|
51
52
|
#
|
52
53
|
# @return [Array<RsiValue>] An array of RsiValue instances
|
53
|
-
def self.calculate(data, period: 14, price_key: :value)
|
54
|
+
def self.calculate(data, period: 14, price_key: :value, date_time_key: :date_time)
|
54
55
|
period = period.to_i
|
55
56
|
price_key = price_key.to_sym
|
56
57
|
Validation.validate_numeric_data(data, price_key)
|
57
58
|
Validation.validate_length(data, min_data_size(period: period))
|
58
|
-
Validation.validate_date_time_key(data)
|
59
|
+
Validation.validate_date_time_key(data, date_time_key)
|
59
60
|
|
60
|
-
data = data.sort_by { |row| row[
|
61
|
+
data = data.sort_by { |row| row[date_time_key] }
|
61
62
|
|
62
63
|
output = []
|
63
64
|
prev_price = data.shift[price_key]
|
@@ -96,7 +97,7 @@ module TechnicalAnalysis
|
|
96
97
|
rsi = (100.00 - (100.00 / (1.00 + rs)))
|
97
98
|
end
|
98
99
|
|
99
|
-
output << RsiValue.new(date_time: v[
|
100
|
+
output << RsiValue.new(date_time: v[date_time_key], rsi: rsi)
|
100
101
|
|
101
102
|
prev_avg = { gain: avg_gain, loss: avg_loss }
|
102
103
|
price_changes.shift
|
@@ -20,7 +20,7 @@ module TechnicalAnalysis
|
|
20
20
|
#
|
21
21
|
# @return [Array] An array of keys as symbols for valid options for this technical indicator
|
22
22
|
def self.valid_options
|
23
|
-
%i(period price_key)
|
23
|
+
%i(period price_key date_time_key)
|
24
24
|
end
|
25
25
|
|
26
26
|
# Validates the provided options for this technical indicator
|
@@ -48,16 +48,18 @@ module TechnicalAnalysis
|
|
48
48
|
# @param data [Array] Array of hashes with keys (:date_time, :value)
|
49
49
|
# @param period [Integer] The given period to calculate the SMA
|
50
50
|
# @param price_key [Symbol] The hash key for the price data. Default :value
|
51
|
+
# @param date_time_key [Symbol] The hash key for the date time data. Default :date_time
|
51
52
|
#
|
52
53
|
# @return [Array<SmaValue>] An array of SmaValue instances
|
53
|
-
def self.calculate(data, period: 30, price_key: :value)
|
54
|
+
def self.calculate(data, period: 30, price_key: :value, date_time_key: :date_time)
|
54
55
|
period = period.to_i
|
55
56
|
price_key = price_key.to_sym
|
57
|
+
date_time_key = date_time_key.to_sym
|
56
58
|
Validation.validate_numeric_data(data, price_key)
|
57
59
|
Validation.validate_length(data, min_data_size(period: period))
|
58
|
-
Validation.validate_date_time_key(data)
|
60
|
+
Validation.validate_date_time_key(data, date_time_key)
|
59
61
|
|
60
|
-
data = data.sort_by { |row| row[
|
62
|
+
data = data.sort_by { |row| row[date_time_key] }
|
61
63
|
|
62
64
|
output = []
|
63
65
|
period_values = []
|
@@ -65,7 +67,7 @@ module TechnicalAnalysis
|
|
65
67
|
data.each do |v|
|
66
68
|
period_values << v[price_key]
|
67
69
|
if period_values.size == period
|
68
|
-
output << SmaValue.new(date_time: v[
|
70
|
+
output << SmaValue.new(date_time: v[date_time_key], sma: ArrayHelper.average(period_values))
|
69
71
|
period_values.shift
|
70
72
|
end
|
71
73
|
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
# Volume Weighted Average Price
|
3
|
+
class Vwap < 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
|
+
"vwap"
|
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
|
+
"Volume Weighted Average Price"
|
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 volume weighted average price (VWAP) for the data
|
47
|
+
# https://en.wikipedia.org/wiki/Volume-weighted_average_price
|
48
|
+
#
|
49
|
+
# @param data [Array] Array of hashes with keys (:date_time, :high, :low, :close, :volume)
|
50
|
+
#
|
51
|
+
# @return [Array<VwapValue>] An array of VwapValue instances
|
52
|
+
def self.calculate(data)
|
53
|
+
Validation.validate_numeric_data(data, :high, :low, :close, :volume)
|
54
|
+
Validation.validate_length(data, min_data_size)
|
55
|
+
Validation.validate_date_time_key(data)
|
56
|
+
|
57
|
+
data = data.sort_by { |row| row[:date_time] }
|
58
|
+
|
59
|
+
output = []
|
60
|
+
cumm_volume = 0
|
61
|
+
cumm_volume_x_typical_price = 0
|
62
|
+
|
63
|
+
data.each do |v|
|
64
|
+
typical_price = StockCalculation.typical_price(v)
|
65
|
+
cumm_volume_x_typical_price += v[:volume] * typical_price
|
66
|
+
cumm_volume += v[:volume]
|
67
|
+
vwap = cumm_volume_x_typical_price.to_f / cumm_volume.to_f
|
68
|
+
|
69
|
+
output << VwapValue.new(date_time: v[:date_time], vwap: vwap)
|
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 VwapValue
|
79
|
+
|
80
|
+
# @return [String] the date_time of the obversation as it was provided
|
81
|
+
attr_accessor :date_time
|
82
|
+
|
83
|
+
# @return [Float] the vwap calculation value
|
84
|
+
attr_accessor :vwap
|
85
|
+
|
86
|
+
def initialize(date_time: nil, vwap: nil)
|
87
|
+
@date_time = date_time
|
88
|
+
@vwap = vwap
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [Hash] the attributes as a hash
|
92
|
+
def to_hash
|
93
|
+
{ date_time: @date_time, vwap: @vwap }
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module TechnicalAnalysis
|
2
|
+
# Weighted Moving Average
|
3
|
+
class Wma < 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
|
+
"wma"
|
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
|
+
"Weighted Moving 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 date_time_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: 30, **params)
|
42
|
+
period.to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
# Calculates the weighted moving average (WMA) for the data over the given period
|
46
|
+
# https://en.wikipedia.org/wiki/Moving_average#Weighted_moving_average
|
47
|
+
#
|
48
|
+
# @param data [Array] Array of hashes with keys (:date_time, :value)
|
49
|
+
# @param period [Integer] The given period to calculate the WMA
|
50
|
+
# @param price_key [Symbol] The hash key for the price data. Default :value
|
51
|
+
# @param date_time_key [Symbol] The hash key for the date time data. Default :date_time
|
52
|
+
#
|
53
|
+
# @return [Array<WmaValue>] An array of WmaValue instances
|
54
|
+
def self.calculate(data, period: 30, price_key: :value, date_time_key: :date_time)
|
55
|
+
period = period.to_i
|
56
|
+
price_key = price_key.to_sym
|
57
|
+
date_time_key = date_time_key.to_sym
|
58
|
+
Validation.validate_numeric_data(data, price_key)
|
59
|
+
Validation.validate_length(data, min_data_size(period: period))
|
60
|
+
Validation.validate_date_time_key(data, date_time_key)
|
61
|
+
|
62
|
+
data = data.sort_by { |row| row[date_time_key] }
|
63
|
+
|
64
|
+
output = []
|
65
|
+
period_values = []
|
66
|
+
previous_wma = nil
|
67
|
+
|
68
|
+
data.each do |v|
|
69
|
+
period_values << v[price_key]
|
70
|
+
if period_values.size == period
|
71
|
+
wma = StockCalculation.wma(period_values)
|
72
|
+
output << WmaValue.new(date_time: v[date_time_key], wma: wma)
|
73
|
+
period_values.shift
|
74
|
+
end
|
75
|
+
end
|
76
|
+
output.sort_by(&:date_time).reverse
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
# The value class to be returned by calculations
|
82
|
+
class WmaValue
|
83
|
+
|
84
|
+
# @return [String] the date_time of the obversation as it was provided
|
85
|
+
attr_accessor :date_time
|
86
|
+
|
87
|
+
# @return [Float] the wma calculation value
|
88
|
+
attr_accessor :wma
|
89
|
+
|
90
|
+
def initialize(date_time: nil, wma: nil)
|
91
|
+
@date_time = date_time
|
92
|
+
@wma = wma
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [Hash] the attributes as a hash
|
96
|
+
def to_hash
|
97
|
+
{ date_time: @date_time, wma: @wma }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
data/spec/spec_helper.rb
CHANGED
@@ -6,12 +6,12 @@ class SpecHelper
|
|
6
6
|
FLOAT_KEYS = [:open, :high, :low, :close].freeze
|
7
7
|
INTEGER_KEYS = [:volume].freeze
|
8
8
|
|
9
|
-
def self.get_test_data(*columns)
|
9
|
+
def self.get_test_data(*columns, date_time_key: :date_time)
|
10
10
|
@data = CSV.read(TEST_DATA_PATH, headers: true)
|
11
11
|
columns = columns.map(&:to_sym)
|
12
12
|
output = []
|
13
13
|
@data.each do |v|
|
14
|
-
col_hash = {
|
14
|
+
col_hash = { date_time_key => v["date_time"] }
|
15
15
|
columns.each do |col|
|
16
16
|
value = v[col.to_s]
|
17
17
|
value = value.to_f if FLOAT_KEYS.include?(col)
|
data/spec/ta_test_data.csv
CHANGED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'technical-analysis'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Indicators' do
|
5
|
+
describe "EMA" do
|
6
|
+
input_data = SpecHelper.get_test_data(:close, date_time_key: :timestep)
|
7
|
+
indicator = TechnicalAnalysis::Ema
|
8
|
+
|
9
|
+
describe 'Exponential Moving Average' do
|
10
|
+
it 'Calculates EMA (5 day)' do
|
11
|
+
output = indicator.calculate(input_data, period: 5, price_key: :close, date_time_key: :timestep)
|
12
|
+
normalized_output = output.map(&:to_hash)
|
13
|
+
|
14
|
+
expected_output = [
|
15
|
+
{:date_time=>"2019-01-09T00:00:00.000Z", :ema=>151.1937160522574},
|
16
|
+
{:date_time=>"2019-01-08T00:00:00.000Z", :ema=>150.1355740783861},
|
17
|
+
{:date_time=>"2019-01-07T00:00:00.000Z", :ema=>149.82836111757913},
|
18
|
+
{:date_time=>"2019-01-04T00:00:00.000Z", :ema=>150.77754167636868},
|
19
|
+
{:date_time=>"2019-01-03T00:00:00.000Z", :ema=>152.03631251455303},
|
20
|
+
{:date_time=>"2019-01-02T00:00:00.000Z", :ema=>156.95946877182956},
|
21
|
+
{:date_time=>"2018-12-31T00:00:00.000Z", :ema=>156.47920315774434},
|
22
|
+
{:date_time=>"2018-12-28T00:00:00.000Z", :ema=>155.8488047366165},
|
23
|
+
{:date_time=>"2018-12-27T00:00:00.000Z", :ema=>155.65820710492477},
|
24
|
+
{:date_time=>"2018-12-26T00:00:00.000Z", :ema=>155.41231065738714},
|
25
|
+
{:date_time=>"2018-12-24T00:00:00.000Z", :ema=>154.53346598608073},
|
26
|
+
{:date_time=>"2018-12-21T00:00:00.000Z", :ema=>158.3851989791211},
|
27
|
+
{:date_time=>"2018-12-20T00:00:00.000Z", :ema=>162.21279846868165},
|
28
|
+
{:date_time=>"2018-12-19T00:00:00.000Z", :ema=>164.90419770302248},
|
29
|
+
{:date_time=>"2018-12-18T00:00:00.000Z", :ema=>166.91129655453372},
|
30
|
+
{:date_time=>"2018-12-17T00:00:00.000Z", :ema=>167.3319448318006},
|
31
|
+
{:date_time=>"2018-12-14T00:00:00.000Z", :ema=>169.02791724770088},
|
32
|
+
{:date_time=>"2018-12-13T00:00:00.000Z", :ema=>170.80187587155132},
|
33
|
+
{:date_time=>"2018-12-12T00:00:00.000Z", :ema=>170.72781380732698},
|
34
|
+
{:date_time=>"2018-12-11T00:00:00.000Z", :ema=>171.54172071099046},
|
35
|
+
{:date_time=>"2018-12-10T00:00:00.000Z", :ema=>172.99758106648568},
|
36
|
+
{:date_time=>"2018-12-07T00:00:00.000Z", :ema=>174.69637159972854},
|
37
|
+
{:date_time=>"2018-12-06T00:00:00.000Z", :ema=>177.7995573995928},
|
38
|
+
{:date_time=>"2018-12-04T00:00:00.000Z", :ema=>179.33933609938921},
|
39
|
+
{:date_time=>"2018-12-03T00:00:00.000Z", :ema=>180.6640041490838},
|
40
|
+
{:date_time=>"2018-11-30T00:00:00.000Z", :ema=>178.58600622362573},
|
41
|
+
{:date_time=>"2018-11-29T00:00:00.000Z", :ema=>178.5890093354386},
|
42
|
+
{:date_time=>"2018-11-28T00:00:00.000Z", :ema=>178.10851400315786},
|
43
|
+
{:date_time=>"2018-11-27T00:00:00.000Z", :ema=>176.69277100473678},
|
44
|
+
{:date_time=>"2018-11-26T00:00:00.000Z", :ema=>177.91915650710516},
|
45
|
+
{:date_time=>"2018-11-23T00:00:00.000Z", :ema=>179.56873476065772},
|
46
|
+
{:date_time=>"2018-11-21T00:00:00.000Z", :ema=>183.20810214098657},
|
47
|
+
{:date_time=>"2018-11-20T00:00:00.000Z", :ema=>186.42215321147984},
|
48
|
+
{:date_time=>"2018-11-19T00:00:00.000Z", :ema=>191.14322981721975},
|
49
|
+
{:date_time=>"2018-11-16T00:00:00.000Z", :ema=>193.7848447258296},
|
50
|
+
{:date_time=>"2018-11-15T00:00:00.000Z", :ema=>193.9122670887444},
|
51
|
+
{:date_time=>"2018-11-14T00:00:00.000Z", :ema=>195.1634006331166},
|
52
|
+
{:date_time=>"2018-11-13T00:00:00.000Z", :ema=>199.34510094967487},
|
53
|
+
{:date_time=>"2018-11-12T00:00:00.000Z", :ema=>202.90265142451233},
|
54
|
+
{:date_time=>"2018-11-09T00:00:00.000Z", :ema=>207.26897713676848},
|
55
|
+
{:date_time=>"2018-11-08T00:00:00.000Z", :ema=>208.66846570515273},
|
56
|
+
{:date_time=>"2018-11-07T00:00:00.000Z", :ema=>208.75769855772907},
|
57
|
+
{:date_time=>"2018-11-06T00:00:00.000Z", :ema=>208.16154783659363},
|
58
|
+
{:date_time=>"2018-11-05T00:00:00.000Z", :ema=>210.35732175489045},
|
59
|
+
{:date_time=>"2018-11-02T00:00:00.000Z", :ema=>214.7409826323357},
|
60
|
+
{:date_time=>"2018-11-01T00:00:00.000Z", :ema=>218.37147394850354},
|
61
|
+
{:date_time=>"2018-10-31T00:00:00.000Z", :ema=>216.44721092275532},
|
62
|
+
{:date_time=>"2018-10-30T00:00:00.000Z", :ema=>215.24081638413296},
|
63
|
+
{:date_time=>"2018-10-29T00:00:00.000Z", :ema=>216.21122457619944},
|
64
|
+
{:date_time=>"2018-10-26T00:00:00.000Z", :ema=>218.19683686429914},
|
65
|
+
{:date_time=>"2018-10-25T00:00:00.000Z", :ema=>219.1452552964487},
|
66
|
+
{:date_time=>"2018-10-24T00:00:00.000Z", :ema=>218.81788294467307},
|
67
|
+
{:date_time=>"2018-10-23T00:00:00.000Z", :ema=>220.6818244170096},
|
68
|
+
{:date_time=>"2018-10-22T00:00:00.000Z", :ema=>219.65773662551442},
|
69
|
+
{:date_time=>"2018-10-19T00:00:00.000Z", :ema=>219.1616049382716},
|
70
|
+
{:date_time=>"2018-10-18T00:00:00.000Z", :ema=>219.0874074074074},
|
71
|
+
{:date_time=>"2018-10-17T00:00:00.000Z", :ema=>220.6211111111111},
|
72
|
+
{:date_time=>"2018-10-16T00:00:00.000Z", :ema=>220.33666666666667},
|
73
|
+
{:date_time=>"2018-10-15T00:00:00.000Z", :ema=>219.43}
|
74
|
+
]
|
75
|
+
|
76
|
+
expect(normalized_output).to eq(expected_output)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "Throws exception if not enough data" do
|
80
|
+
expect {indicator.calculate(input_data, period: input_data.size+1, price_key: :close)}.to raise_exception(TechnicalAnalysis::Validation::ValidationError)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'Returns the symbol' do
|
84
|
+
indicator_symbol = indicator.indicator_symbol
|
85
|
+
expect(indicator_symbol).to eq('ema')
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'Returns the name' do
|
89
|
+
indicator_name = indicator.indicator_name
|
90
|
+
expect(indicator_name).to eq('Exponential Moving Average')
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'Returns the valid options' do
|
94
|
+
valid_options = indicator.valid_options
|
95
|
+
expect(valid_options).to eq(%i(period price_key date_time_key))
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'Validates options' do
|
99
|
+
valid_options = { period: 22, price_key: :close, date_time_key: :timestep }
|
100
|
+
options_validated = indicator.validate_options(valid_options)
|
101
|
+
expect(options_validated).to eq(true)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'Throws exception for invalid options' do
|
105
|
+
invalid_options = { test: 10 }
|
106
|
+
expect { indicator.validate_options(invalid_options) }.to raise_exception(TechnicalAnalysis::Validation::ValidationError)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'Calculates minimum data size' do
|
110
|
+
options = { period: 4 }
|
111
|
+
expect(indicator.min_data_size(options)).to eq(4)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -38,7 +38,7 @@ describe 'Indicators' do
|
|
38
38
|
|
39
39
|
it 'Calculates valid_options' do
|
40
40
|
valid_options = indicator.calculate('sma', [], :valid_options, { period: 20, price_key: :close })
|
41
|
-
expect(valid_options).to eq(%i(period price_key))
|
41
|
+
expect(valid_options).to eq(%i(period price_key date_time_key))
|
42
42
|
end
|
43
43
|
|
44
44
|
it 'Calculates validate_options' do
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -3,12 +3,12 @@ require 'spec_helper'
|
|
3
3
|
|
4
4
|
describe 'Indicators' do
|
5
5
|
describe "RSI" do
|
6
|
-
input_data = SpecHelper.get_test_data(:close)
|
6
|
+
input_data = SpecHelper.get_test_data(:close, date_time_key: :timestep)
|
7
7
|
indicator = TechnicalAnalysis::Rsi
|
8
8
|
|
9
9
|
describe 'Relative Strength Index' do
|
10
10
|
it 'Calculates RSI (14 day)' do
|
11
|
-
output = indicator.calculate(input_data, period: 14, price_key: :close)
|
11
|
+
output = indicator.calculate(input_data, period: 14, price_key: :close, date_time_key: :timestep)
|
12
12
|
normalized_output = output.map(&:to_hash)
|
13
13
|
|
14
14
|
expected_output = [
|
@@ -67,7 +67,7 @@ describe 'Indicators' do
|
|
67
67
|
end
|
68
68
|
|
69
69
|
it "Throws exception if not enough data" do
|
70
|
-
expect {indicator.calculate(input_data, period: input_data.size+2, price_key: :close)}.to raise_exception(TechnicalAnalysis::Validation::ValidationError)
|
70
|
+
expect {indicator.calculate(input_data, period: input_data.size+2, price_key: :close, date_time_key: :time)}.to raise_exception(TechnicalAnalysis::Validation::ValidationError)
|
71
71
|
end
|
72
72
|
|
73
73
|
it 'Returns the symbol' do
|
@@ -82,11 +82,11 @@ describe 'Indicators' do
|
|
82
82
|
|
83
83
|
it 'Returns the valid options' do
|
84
84
|
valid_options = indicator.valid_options
|
85
|
-
expect(valid_options).to eq(%i(period price_key))
|
85
|
+
expect(valid_options).to eq(%i(period price_key date_time_key))
|
86
86
|
end
|
87
87
|
|
88
88
|
it 'Validates options' do
|
89
|
-
valid_options = { period: 22, price_key: :close }
|
89
|
+
valid_options = { period: 22, price_key: :close, date_time_key: :timespec }
|
90
90
|
options_validated = indicator.validate_options(valid_options)
|
91
91
|
expect(options_validated).to eq(true)
|
92
92
|
end
|
@@ -3,12 +3,12 @@ require 'spec_helper'
|
|
3
3
|
|
4
4
|
describe 'Indicators' do
|
5
5
|
describe "SMA" do
|
6
|
-
input_data = SpecHelper.get_test_data(:close)
|
6
|
+
input_data = SpecHelper.get_test_data(:close, date_time_key: :timestep)
|
7
7
|
indicator = TechnicalAnalysis::Sma
|
8
8
|
|
9
9
|
describe 'Simple Moving Average' do
|
10
10
|
it 'Calculates SMA (5 day)' do
|
11
|
-
output = indicator.calculate(input_data, period: 5, price_key: :close)
|
11
|
+
output = indicator.calculate(input_data, period: 5, price_key: :close, date_time_key: :timestep)
|
12
12
|
normalized_output = output.map(&:to_hash)
|
13
13
|
|
14
14
|
expected_output = [
|
@@ -92,11 +92,11 @@ describe 'Indicators' do
|
|
92
92
|
|
93
93
|
it 'Returns the valid options' do
|
94
94
|
valid_options = indicator.valid_options
|
95
|
-
expect(valid_options).to eq(%i(period price_key))
|
95
|
+
expect(valid_options).to eq(%i(period price_key date_time_key))
|
96
96
|
end
|
97
97
|
|
98
98
|
it 'Validates options' do
|
99
|
-
valid_options = { period: 22, price_key: :close }
|
99
|
+
valid_options = { period: 22, price_key: :close, date_time_key: :timestep }
|
100
100
|
options_validated = indicator.validate_options(valid_options)
|
101
101
|
expect(options_validated).to eq(true)
|
102
102
|
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'technical-analysis'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Indicators' do
|
5
|
+
describe "VWAP" do
|
6
|
+
input_data = SpecHelper.get_test_data(:volume, :high, :low, :close)
|
7
|
+
indicator = TechnicalAnalysis::Vwap
|
8
|
+
|
9
|
+
describe 'Volume Weighted Average Price' do
|
10
|
+
it 'Calculates VWAP' do
|
11
|
+
output = indicator.calculate(input_data)
|
12
|
+
normalized_output = output.map(&:to_hash)
|
13
|
+
|
14
|
+
expected_output = [
|
15
|
+
{:date_time=>"2019-01-09T00:00:00.000Z", :vwap=>183.55426863053765},
|
16
|
+
{:date_time=>"2019-01-08T00:00:00.000Z", :vwap=>184.06635227887782},
|
17
|
+
{:date_time=>"2019-01-07T00:00:00.000Z", :vwap=>184.57507590747517},
|
18
|
+
{:date_time=>"2019-01-04T00:00:00.000Z", :vwap=>185.3413093796368},
|
19
|
+
{:date_time=>"2019-01-03T00:00:00.000Z", :vwap=>186.19781421865375},
|
20
|
+
{:date_time=>"2019-01-02T00:00:00.000Z", :vwap=>187.76843731172002},
|
21
|
+
{:date_time=>"2018-12-31T00:00:00.000Z", :vwap=>188.21552447966795},
|
22
|
+
{:date_time=>"2018-12-28T00:00:00.000Z", :vwap=>188.64862275471472},
|
23
|
+
{:date_time=>"2018-12-27T00:00:00.000Z", :vwap=>189.2144975598256},
|
24
|
+
{:date_time=>"2018-12-26T00:00:00.000Z", :vwap=>189.9889456493791},
|
25
|
+
{:date_time=>"2018-12-24T00:00:00.000Z", :vwap=>190.91953458787302},
|
26
|
+
{:date_time=>"2018-12-21T00:00:00.000Z", :vwap=>191.6297166885398},
|
27
|
+
{:date_time=>"2018-12-20T00:00:00.000Z", :vwap=>193.36567531270202},
|
28
|
+
{:date_time=>"2018-12-19T00:00:00.000Z", :vwap=>194.463693818502},
|
29
|
+
{:date_time=>"2018-12-18T00:00:00.000Z", :vwap=>195.21670632642397},
|
30
|
+
{:date_time=>"2018-12-17T00:00:00.000Z", :vwap=>195.7127882800064},
|
31
|
+
{:date_time=>"2018-12-14T00:00:00.000Z", :vwap=>196.3956319251192},
|
32
|
+
{:date_time=>"2018-12-13T00:00:00.000Z", :vwap=>197.03092567150182},
|
33
|
+
{:date_time=>"2018-12-12T00:00:00.000Z", :vwap=>197.47196479514173},
|
34
|
+
{:date_time=>"2018-12-11T00:00:00.000Z", :vwap=>198.00221355940155},
|
35
|
+
{:date_time=>"2018-12-10T00:00:00.000Z", :vwap=>198.7429667210245},
|
36
|
+
{:date_time=>"2018-12-07T00:00:00.000Z", :vwap=>199.85255982037654},
|
37
|
+
{:date_time=>"2018-12-06T00:00:00.000Z", :vwap=>200.57927838438474},
|
38
|
+
{:date_time=>"2018-12-04T00:00:00.000Z", :vwap=>201.28731920049952},
|
39
|
+
{:date_time=>"2018-12-03T00:00:00.000Z", :vwap=>201.8731568533139},
|
40
|
+
{:date_time=>"2018-11-30T00:00:00.000Z", :vwap=>202.34554208942396},
|
41
|
+
{:date_time=>"2018-11-29T00:00:00.000Z", :vwap=>202.95867828112358},
|
42
|
+
{:date_time=>"2018-11-28T00:00:00.000Z", :vwap=>203.60135199869035},
|
43
|
+
{:date_time=>"2018-11-27T00:00:00.000Z", :vwap=>204.38651817163168},
|
44
|
+
{:date_time=>"2018-11-26T00:00:00.000Z", :vwap=>205.30361785858756},
|
45
|
+
{:date_time=>"2018-11-23T00:00:00.000Z", :vwap=>206.36274937523916},
|
46
|
+
{:date_time=>"2018-11-21T00:00:00.000Z", :vwap=>206.94494917055295},
|
47
|
+
{:date_time=>"2018-11-20T00:00:00.000Z", :vwap=>207.6427516507437},
|
48
|
+
{:date_time=>"2018-11-19T00:00:00.000Z", :vwap=>209.27699957144853},
|
49
|
+
{:date_time=>"2018-11-16T00:00:00.000Z", :vwap=>210.05211892678065},
|
50
|
+
{:date_time=>"2018-11-15T00:00:00.000Z", :vwap=>210.59952347188045},
|
51
|
+
{:date_time=>"2018-11-14T00:00:00.000Z", :vwap=>211.4589551886514},
|
52
|
+
{:date_time=>"2018-11-13T00:00:00.000Z", :vwap=>212.75803369499064},
|
53
|
+
{:date_time=>"2018-11-12T00:00:00.000Z", :vwap=>213.6551619737374},
|
54
|
+
{:date_time=>"2018-11-09T00:00:00.000Z", :vwap=>214.61043573066618},
|
55
|
+
{:date_time=>"2018-11-08T00:00:00.000Z", :vwap=>215.00076838969952},
|
56
|
+
{:date_time=>"2018-11-07T00:00:00.000Z", :vwap=>215.18761540540189},
|
57
|
+
{:date_time=>"2018-11-06T00:00:00.000Z", :vwap=>215.4663555747261},
|
58
|
+
{:date_time=>"2018-11-05T00:00:00.000Z", :vwap=>215.93354729003553},
|
59
|
+
{:date_time=>"2018-11-02T00:00:00.000Z", :vwap=>217.20680955786048},
|
60
|
+
{:date_time=>"2018-11-01T00:00:00.000Z", :vwap=>218.35223508156088},
|
61
|
+
{:date_time=>"2018-10-31T00:00:00.000Z", :vwap=>218.1692825392702},
|
62
|
+
{:date_time=>"2018-10-30T00:00:00.000Z", :vwap=>218.13783195840017},
|
63
|
+
{:date_time=>"2018-10-29T00:00:00.000Z", :vwap=>218.5155747290589},
|
64
|
+
{:date_time=>"2018-10-26T00:00:00.000Z", :vwap=>219.05970452474796},
|
65
|
+
{:date_time=>"2018-10-25T00:00:00.000Z", :vwap=>219.3440526461074},
|
66
|
+
{:date_time=>"2018-10-24T00:00:00.000Z", :vwap=>219.34643675779574},
|
67
|
+
{:date_time=>"2018-10-23T00:00:00.000Z", :vwap=>219.4951598586493},
|
68
|
+
{:date_time=>"2018-10-22T00:00:00.000Z", :vwap=>219.41092910048724},
|
69
|
+
{:date_time=>"2018-10-19T00:00:00.000Z", :vwap=>219.26375336145847},
|
70
|
+
{:date_time=>"2018-10-18T00:00:00.000Z", :vwap=>219.25541442468506},
|
71
|
+
{:date_time=>"2018-10-17T00:00:00.000Z", :vwap=>219.65735634491844},
|
72
|
+
{:date_time=>"2018-10-16T00:00:00.000Z", :vwap=>219.5125052055069},
|
73
|
+
{:date_time=>"2018-10-15T00:00:00.000Z", :vwap=>219.34283327445593},
|
74
|
+
{:date_time=>"2018-10-12T00:00:00.000Z", :vwap=>219.44169580294155},
|
75
|
+
{:date_time=>"2018-10-11T00:00:00.000Z", :vwap=>219.0592293697168},
|
76
|
+
{:date_time=>"2018-10-10T00:00:00.000Z", :vwap=>221.89869420553177},
|
77
|
+
{:date_time=>"2018-10-09T00:00:00.000Z", :vwap=>225.4620666666667}
|
78
|
+
]
|
79
|
+
|
80
|
+
expect(normalized_output).to eq(expected_output)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "Throws exception if not enough data" do
|
84
|
+
expect {indicator.calculate([])}.to raise_exception(TechnicalAnalysis::Validation::ValidationError)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'Returns the symbol' do
|
88
|
+
indicator_symbol = indicator.indicator_symbol
|
89
|
+
expect(indicator_symbol).to eq('vwap')
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'Returns the name' do
|
93
|
+
indicator_name = indicator.indicator_name
|
94
|
+
expect(indicator_name).to eq('Volume Weighted Average Price')
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'Returns the valid options' do
|
98
|
+
valid_options = indicator.valid_options
|
99
|
+
expect(valid_options).to eq([])
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'Validates options' do
|
103
|
+
valid_options = {}
|
104
|
+
options_validated = indicator.validate_options(valid_options)
|
105
|
+
expect(options_validated).to eq(true)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'Throws exception for invalid options' do
|
109
|
+
invalid_options = { test: 10 }
|
110
|
+
expect { indicator.validate_options(invalid_options) }.to raise_exception(TechnicalAnalysis::Validation::ValidationError)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'Calculates minimum data size' do
|
114
|
+
options = {}
|
115
|
+
expect(indicator.min_data_size(options)).to eq(1)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'technical-analysis'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Indicators' do
|
5
|
+
describe "WMA" do
|
6
|
+
input_data = SpecHelper.get_test_data(:close, date_time_key: :timestep)
|
7
|
+
indicator = TechnicalAnalysis::Wma
|
8
|
+
|
9
|
+
describe 'Weighted Moving Average' do
|
10
|
+
it 'Calculates WMA (5 day)' do
|
11
|
+
output = indicator.calculate(input_data, period: 5, price_key: :close, date_time_key: :timestep)
|
12
|
+
normalized_output = output.map(&:to_hash)
|
13
|
+
|
14
|
+
expected_output = [
|
15
|
+
{:date_time=>"2019-01-09T00:00:00.000Z", :wma=>150.13666666666666},
|
16
|
+
{:date_time=>"2019-01-08T00:00:00.000Z", :wma=>148.83666666666667},
|
17
|
+
{:date_time=>"2019-01-07T00:00:00.000Z", :wma=>148.856},
|
18
|
+
{:date_time=>"2019-01-04T00:00:00.000Z", :wma=>150.36866666666666},
|
19
|
+
{:date_time=>"2019-01-03T00:00:00.000Z", :wma=>152.29733333333334},
|
20
|
+
{:date_time=>"2019-01-02T00:00:00.000Z", :wma=>157.248},
|
21
|
+
{:date_time=>"2018-12-31T00:00:00.000Z", :wma=>156.216},
|
22
|
+
{:date_time=>"2018-12-28T00:00:00.000Z", :wma=>154.77666666666667},
|
23
|
+
{:date_time=>"2018-12-27T00:00:00.000Z", :wma=>153.88066666666668},
|
24
|
+
{:date_time=>"2018-12-26T00:00:00.000Z", :wma=>153.3273333333333},
|
25
|
+
{:date_time=>"2018-12-24T00:00:00.000Z", :wma=>153.02733333333333},
|
26
|
+
{:date_time=>"2018-12-21T00:00:00.000Z", :wma=>157.31466666666668},
|
27
|
+
{:date_time=>"2018-12-20T00:00:00.000Z", :wma=>161.28533333333334},
|
28
|
+
{:date_time=>"2018-12-19T00:00:00.000Z", :wma=>164.164},
|
29
|
+
{:date_time=>"2018-12-18T00:00:00.000Z", :wma=>166.23666666666665},
|
30
|
+
{:date_time=>"2018-12-17T00:00:00.000Z", :wma=>166.75333333333333},
|
31
|
+
{:date_time=>"2018-12-14T00:00:00.000Z", :wma=>168.35733333333334},
|
32
|
+
{:date_time=>"2018-12-13T00:00:00.000Z", :wma=>169.64866666666666},
|
33
|
+
{:date_time=>"2018-12-12T00:00:00.000Z", :wma=>169.368},
|
34
|
+
{:date_time=>"2018-12-11T00:00:00.000Z", :wma=>170.21},
|
35
|
+
{:date_time=>"2018-12-10T00:00:00.000Z", :wma=>172.288},
|
36
|
+
{:date_time=>"2018-12-07T00:00:00.000Z", :wma=>174.64133333333334},
|
37
|
+
{:date_time=>"2018-12-06T00:00:00.000Z", :wma=>178.102},
|
38
|
+
{:date_time=>"2018-12-04T00:00:00.000Z", :wma=>179.9006666666667},
|
39
|
+
{:date_time=>"2018-12-03T00:00:00.000Z", :wma=>180.87933333333334},
|
40
|
+
{:date_time=>"2018-11-30T00:00:00.000Z", :wma=>178.468},
|
41
|
+
{:date_time=>"2018-11-29T00:00:00.000Z", :wma=>177.71733333333333},
|
42
|
+
{:date_time=>"2018-11-28T00:00:00.000Z", :wma=>176.45866666666666},
|
43
|
+
{:date_time=>"2018-11-27T00:00:00.000Z", :wma=>174.47266666666667},
|
44
|
+
{:date_time=>"2018-11-26T00:00:00.000Z", :wma=>175.49466666666666},
|
45
|
+
{:date_time=>"2018-11-23T00:00:00.000Z", :wma=>177.65066666666667},
|
46
|
+
{:date_time=>"2018-11-21T00:00:00.000Z", :wma=>181.858},
|
47
|
+
{:date_time=>"2018-11-20T00:00:00.000Z", :wma=>185.23666666666668},
|
48
|
+
{:date_time=>"2018-11-19T00:00:00.000Z", :wma=>189.56533333333334},
|
49
|
+
{:date_time=>"2018-11-16T00:00:00.000Z", :wma=>191.488},
|
50
|
+
{:date_time=>"2018-11-15T00:00:00.000Z", :wma=>191.58333333333331},
|
51
|
+
{:date_time=>"2018-11-14T00:00:00.000Z", :wma=>193.524},
|
52
|
+
{:date_time=>"2018-11-13T00:00:00.000Z", :wma=>198.54466666666667},
|
53
|
+
{:date_time=>"2018-11-12T00:00:00.000Z", :wma=>202.52466666666666},
|
54
|
+
{:date_time=>"2018-11-09T00:00:00.000Z", :wma=>206.35266666666666},
|
55
|
+
{:date_time=>"2018-11-08T00:00:00.000Z", :wma=>206.948},
|
56
|
+
{:date_time=>"2018-11-07T00:00:00.000Z", :wma=>207.11866666666668},
|
57
|
+
{:date_time=>"2018-11-06T00:00:00.000Z", :wma=>207.39666666666665},
|
58
|
+
{:date_time=>"2018-11-05T00:00:00.000Z", :wma=>210.37},
|
59
|
+
{:date_time=>"2018-11-02T00:00:00.000Z", :wma=>214.78},
|
60
|
+
{:date_time=>"2018-11-01T00:00:00.000Z", :wma=>217.81466666666665},
|
61
|
+
{:date_time=>"2018-10-31T00:00:00.000Z", :wma=>215.7746666666667},
|
62
|
+
{:date_time=>"2018-10-30T00:00:00.000Z", :wma=>214.60333333333332},
|
63
|
+
{:date_time=>"2018-10-29T00:00:00.000Z", :wma=>215.91400000000002},
|
64
|
+
{:date_time=>"2018-10-26T00:00:00.000Z", :wma=>218.13866666666667},
|
65
|
+
{:date_time=>"2018-10-25T00:00:00.000Z", :wma=>219.21066666666667},
|
66
|
+
{:date_time=>"2018-10-24T00:00:00.000Z", :wma=>218.86400000000003},
|
67
|
+
{:date_time=>"2018-10-23T00:00:00.000Z", :wma=>220.49400000000003},
|
68
|
+
{:date_time=>"2018-10-22T00:00:00.000Z", :wma=>219.53866666666664},
|
69
|
+
{:date_time=>"2018-10-19T00:00:00.000Z", :wma=>219.05733333333333},
|
70
|
+
{:date_time=>"2018-10-18T00:00:00.000Z", :wma=>219.20933333333335},
|
71
|
+
{:date_time=>"2018-10-17T00:00:00.000Z", :wma=>220.35333333333335},
|
72
|
+
{:date_time=>"2018-10-16T00:00:00.000Z", :wma=>219.452},
|
73
|
+
{:date_time=>"2018-10-15T00:00:00.000Z", :wma=>218.54533333333333}
|
74
|
+
]
|
75
|
+
|
76
|
+
expect(normalized_output).to eq(expected_output)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "Throws exception if not enough data" do
|
80
|
+
expect {indicator.calculate(input_data, period: input_data.size+1, price_key: :close)}.to raise_exception(TechnicalAnalysis::Validation::ValidationError)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'Returns the symbol' do
|
84
|
+
indicator_symbol = indicator.indicator_symbol
|
85
|
+
expect(indicator_symbol).to eq('wma')
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'Returns the name' do
|
89
|
+
indicator_name = indicator.indicator_name
|
90
|
+
expect(indicator_name).to eq('Weighted Moving Average')
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'Returns the valid options' do
|
94
|
+
valid_options = indicator.valid_options
|
95
|
+
expect(valid_options).to eq(%i(period price_key date_time_key))
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'Validates options' do
|
99
|
+
valid_options = { period: 22, price_key: :close, date_time_key: :timestep }
|
100
|
+
options_validated = indicator.validate_options(valid_options)
|
101
|
+
expect(options_validated).to eq(true)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'Throws exception for invalid options' do
|
105
|
+
invalid_options = { test: 10 }
|
106
|
+
expect { indicator.validate_options(invalid_options) }.to raise_exception(TechnicalAnalysis::Validation::ValidationError)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'Calculates minimum data size' do
|
110
|
+
options = { period: 4 }
|
111
|
+
expect(indicator.min_data_size(**options)).to eq(4)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
File without changes
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: technical-analysis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Intrinio
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '12.3'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '12.3'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.9.
|
61
|
+
version: 0.9.20
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 0.9.
|
68
|
+
version: 0.9.20
|
69
69
|
description: A Ruby library for performing technical analysis on stock prices and
|
70
70
|
other data sets.
|
71
71
|
email:
|
@@ -92,6 +92,7 @@ files:
|
|
92
92
|
- lib/technical_analysis/indicators/dlr.rb
|
93
93
|
- lib/technical_analysis/indicators/dpo.rb
|
94
94
|
- lib/technical_analysis/indicators/dr.rb
|
95
|
+
- lib/technical_analysis/indicators/ema.rb
|
95
96
|
- lib/technical_analysis/indicators/eom.rb
|
96
97
|
- lib/technical_analysis/indicators/fi.rb
|
97
98
|
- lib/technical_analysis/indicators/ichimoku.rb
|
@@ -112,6 +113,8 @@ files:
|
|
112
113
|
- lib/technical_analysis/indicators/uo.rb
|
113
114
|
- lib/technical_analysis/indicators/vi.rb
|
114
115
|
- lib/technical_analysis/indicators/vpt.rb
|
116
|
+
- lib/technical_analysis/indicators/vwap.rb
|
117
|
+
- lib/technical_analysis/indicators/wma.rb
|
115
118
|
- lib/technical_analysis/indicators/wr.rb
|
116
119
|
- spec/helpers/array_helper_spec.rb
|
117
120
|
- spec/helpers/validaton_spec.rb
|
@@ -130,6 +133,7 @@ files:
|
|
130
133
|
- spec/technical_analysis/indicators/dlr_spec.rb
|
131
134
|
- spec/technical_analysis/indicators/dpo_spec.rb
|
132
135
|
- spec/technical_analysis/indicators/dr_spec.rb
|
136
|
+
- spec/technical_analysis/indicators/ema_spec.rb
|
133
137
|
- spec/technical_analysis/indicators/eom_spec.rb
|
134
138
|
- spec/technical_analysis/indicators/fi_spec.rb
|
135
139
|
- spec/technical_analysis/indicators/ichimoku_spec.rb
|
@@ -150,6 +154,8 @@ files:
|
|
150
154
|
- spec/technical_analysis/indicators/uo_spec.rb
|
151
155
|
- spec/technical_analysis/indicators/vi_spec.rb
|
152
156
|
- spec/technical_analysis/indicators/vpt_spec.rb
|
157
|
+
- spec/technical_analysis/indicators/vwap_spec.rb
|
158
|
+
- spec/technical_analysis/indicators/wma_spec.rb
|
153
159
|
- spec/technical_analysis/indicators/wr_spec.rb
|
154
160
|
homepage: https://github.com/intrinio/technical-analysis
|
155
161
|
licenses: []
|