technical-analysis 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d638cd93a0c547b5882077a74f23b899632901b1
4
- data.tar.gz: 222ea519ae143925f661d8917fc44d25abc571cd
3
+ metadata.gz: 242a71fb8aedb7315b625ded3707bb7c3b1fbf3e
4
+ data.tar.gz: dd8f5b9980aa256206126ba688537f092ba8c72a
5
5
  SHA512:
6
- metadata.gz: 151f837f5c897ea12cdfe3f4edaf70b10be35a76080d001555ad4c9f0db1f9cee8ecdad78cb2182170f8f7328cd8ffc011b5de3c09d432391f517e55175cba3c
7
- data.tar.gz: f59019103e1a6aa43c3f9ef4e83601c2af9882591f4458faf5012f48b69571aabb6112b2a1d7f12c35d4d60c824ac6138e46d1732ea1a969367c6bbaec9b7460
6
+ metadata.gz: de0cb79b308cbac50018e8ec5f33a6a1c5849454c13d9d65a0f76eedfda85858e6129681ba3c584c2bb14330e30c6af190180d3d151ed731a23dc9e6604d31a5
7
+ data.tar.gz: 240f61c674561c0f2e428bf27cb09cddaf11840796cc8eafab48b56a45de3548894030290f6fdf301d764a27379797a17359245a559fc09d651e18186866480b
@@ -40,4 +40,5 @@ require 'technical_analysis/indicators/uo'
40
40
  require 'technical_analysis/indicators/vi'
41
41
  require 'technical_analysis/indicators/vpt'
42
42
  require 'technical_analysis/indicators/vwap'
43
+ require 'technical_analysis/indicators/wma'
43
44
  require 'technical_analysis/indicators/wr'
@@ -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,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[:date_time] }
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[:date_time], rsi: rsi)
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
@@ -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
@@ -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
@@ -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
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.2.3
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: 2020-08-03 00:00:00.000000000 Z
11
+ date: 2020-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -114,6 +114,7 @@ files:
114
114
  - lib/technical_analysis/indicators/vi.rb
115
115
  - lib/technical_analysis/indicators/vpt.rb
116
116
  - lib/technical_analysis/indicators/vwap.rb
117
+ - lib/technical_analysis/indicators/wma.rb
117
118
  - lib/technical_analysis/indicators/wr.rb
118
119
  - spec/helpers/array_helper_spec.rb
119
120
  - spec/helpers/validaton_spec.rb
@@ -154,6 +155,7 @@ files:
154
155
  - spec/technical_analysis/indicators/vi_spec.rb
155
156
  - spec/technical_analysis/indicators/vpt_spec.rb
156
157
  - spec/technical_analysis/indicators/vwap_spec.rb
158
+ - spec/technical_analysis/indicators/wma_spec.rb
157
159
  - spec/technical_analysis/indicators/wr_spec.rb
158
160
  homepage: https://github.com/intrinio/technical-analysis
159
161
  licenses: []