technical-analysis 0.2.3 → 0.2.4

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 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: []