indicators 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -28,7 +28,7 @@ Or install it yourself as:
28
28
 
29
29
  Then it returns data as an array with indicator values in index places:
30
30
 
31
- i.e. my_data.calc(:type => :sma, :variables => 2) => [nil, 1.5, 2.5, 3.5, 4.5]
31
+ i.e. my_data.calc(:type => :sma, :params => 2).output => [nil, 1.5, 2.5, 3.5, 4.5]
32
32
 
33
33
  # Securities gem hash
34
34
 
@@ -38,28 +38,38 @@ Then it returns a hash so it can be quite powerful at calculating multiple stock
38
38
 
39
39
  {"aapl"=>[nil, 675.24, 674.0600000000001], "yhoo"=>[nil, 14.785, 14.821666666666667]}
40
40
 
41
+ ## Output
42
+
43
+ .calc returns an object with such accessor methods
44
+
45
+ @abbr - indicator abbreviation (usually used when displaying information).
46
+ @params - given or defaulted parameters.
47
+ @output - indicator calculation result.
48
+
49
+ i.e. @abbr="SMA", @params=2, @output=[nil, 1.5, 2.5, 3.5, 4.5]
50
+
41
51
  ## Supported Indicators
42
52
 
43
53
  # Simple Moving Average => :sma
44
54
 
45
- my_data.calc(:type => :sma, :variables => 5)
55
+ my_data.calc(:type => :sma, :params => 5)
46
56
 
47
57
  # Exponental Moving Average => :ema
48
58
 
49
- my_data.calc(:type => :ema, :variables => 5)
59
+ my_data.calc(:type => :ema, :params => 5)
50
60
 
51
61
  # Bollinger Bands => :bb
52
62
 
53
- my_data.calc(:type => :bb, :variables => [15, 3])
63
+ my_data.calc(:type => :bb, :params => [15, 3])
54
64
 
55
65
  Variables have to be specified as an array [periods, multiplier]. If multiplier isn't specified, it defaults to 2.
56
66
 
57
67
  It returns output as an array for each data point [middle band, upper band, lower band].
58
- i.e. my_data.calc(:type => :bb, :variables => 3) => {"aapl"=>[nil, nil, [674.65, 676.8752190903368, 672.4247809096631]]}
68
+ i.e. my_data.calc(:type => :bb, :params => 3) => {"aapl"=>[nil, nil, [674.65, 676.8752190903368, 672.4247809096631]]}
59
69
 
60
70
  # Moving Average Convergence Divergence => :macd
61
71
 
62
- my_data.calc(:type => :macd, :variables => [12, 26, 9])
72
+ my_data.calc(:type => :macd, :params => [12, 26, 9])
63
73
 
64
74
  Variables have to be specified as an array [faster periods, slower periods, signal line]. If slower periods isn't specified, it defaults to 26 and signal line to 9.
65
75
 
@@ -67,13 +77,13 @@ Variables have to be specified as an array [faster periods, slower periods, sign
67
77
 
68
78
  # Relative Strength Index => :rsi
69
79
 
70
- my_data.calc(:type => :rsi, :variables => 14)
80
+ my_data.calc(:type => :rsi, :params => 14)
71
81
 
72
82
  The more data it has, the more accurate RSI is.
73
83
 
74
84
  # Full Stochastic Oscillator => :sto
75
85
 
76
- my_data.calc(:type => :sto, :variables => [14, 3, 5])
86
+ my_data.calc(:type => :sto, :params => [14, 3, 5])
77
87
 
78
88
  Variables have to be specified as an array [lookback period, the number of periods to slow %K, the number of periods for the %D moving average] => [%K1, %K2, %D].
79
89
 
@@ -81,9 +91,7 @@ Variables have to be specified as an array [lookback period, the number of perio
81
91
 
82
92
  ## To Do
83
93
 
84
- * Make defaults mechanism more versatile.
85
94
  * Write specs.
86
- * More validations.
87
95
  * A strategy backtesting tool.
88
96
 
89
97
  # Indicators:
data/lib/indicators.rb CHANGED
@@ -4,5 +4,17 @@ require "indicators/data.rb"
4
4
  require "indicators/parser.rb"
5
5
  require "indicators/main.rb"
6
6
 
7
+ require "indicators/calculations/helper.rb"
8
+
9
+ # Lagging Indicators
10
+ require "indicators/calculations/sma.rb"
11
+ require "indicators/calculations/ema.rb"
12
+ require "indicators/calculations/bb.rb"
13
+ require "indicators/calculations/macd.rb"
14
+
15
+ # Leading Indicators
16
+ require "indicators/calculations/rsi.rb"
17
+ require "indicators/calculations/sto.rb"
18
+
7
19
  module Indicators
8
20
  end
@@ -0,0 +1,33 @@
1
+ module Indicators
2
+ #
3
+ # Bollinger Bands
4
+ class Bb
5
+
6
+ # Middle Band = 20-day simple moving average (SMA)
7
+ # Upper Band = 20-day SMA + (20-day standard deviation of price x 2)
8
+ # Lower Band = 20-day SMA - (20-day standard deviation of price x 2)
9
+ def self.calculate data, parameters
10
+ periods = parameters[0]
11
+ multiplier = parameters[1]
12
+ output = Array.new
13
+ adj_closes = Indicators::Helper.validate_data(data, :adj_close, periods)
14
+
15
+ adj_closes.each_with_index do |adj_close, index|
16
+ start = index+1-periods
17
+ if index+1 >= periods
18
+ middle_band = Indicators::Sma.calculate(adj_closes[start..index], periods).last
19
+ upper_band = middle_band + (adj_closes[start..index].standard_deviation * multiplier)
20
+ lower_band = middle_band - (adj_closes[start..index].standard_deviation * multiplier)
21
+ # Output for each point is [middle, upper, lower].
22
+ output[index] = [middle_band, upper_band, lower_band]
23
+ else
24
+ output[index] = nil
25
+ end
26
+ end
27
+
28
+ return output
29
+
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ module Indicators
2
+ #
3
+ # Exponential Moving Average
4
+ class Ema
5
+
6
+ # Multiplier: (2 / (Time periods + 1) ) = (2 / (10 + 1) ) = 0.1818 (18.18%)
7
+ # EMA: {Close - EMA(previous day)} x multiplier + EMA(previous day).
8
+ def self.calculate data, parameters
9
+ periods = parameters
10
+ output = Array.new
11
+ adj_closes = Indicators::Helper.validate_data(data, :adj_close, periods)
12
+
13
+ k = 2.0/(periods+1)
14
+ adj_closes.each_with_index do |adj_close, index|
15
+ start = index+1-periods
16
+ if start == 0
17
+ output[index] = Indicators::Sma.calculate(adj_closes[start..index], periods).last
18
+ elsif start > 0
19
+ output[index] = ((adj_close - output[index-1]) * k + output[index-1])
20
+ else
21
+ output[index] = nil
22
+ end
23
+ end
24
+
25
+ return output
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,91 @@
1
+ module Indicators
2
+ # Main helper methods
3
+ class Helper
4
+
5
+ # Error handling.
6
+ class HelperException < StandardError
7
+ end
8
+
9
+ def self.validate_data data, column, parameters
10
+ # If this is a hash, choose which column of values to use for calculations.
11
+ if data.is_a?(Hash)
12
+ valid_data = data[column]
13
+ else
14
+ valid_data = data
15
+ end
16
+
17
+ # Make output more friendly
18
+ # if parameters.is_a?(Array)
19
+ # parameters_array = parameters
20
+ # parameters = parameters.sum
21
+ # end
22
+
23
+ if valid_data.length < parameters
24
+ raise HelperException, "Data point length (#{valid_data.length}) must be greater than or equal to the required indicator periods (#{parameters})."
25
+ end
26
+ return valid_data
27
+ end
28
+
29
+ # Indicators::Helper.get_parameters([12, 1, 1], 0, 15)
30
+ def self.get_parameters parameters, i=0, default=0
31
+
32
+ if parameters.is_a?(Integer) || parameters.is_a?(NilClass)
33
+
34
+ # Set all other to default if only one integer is given instead of an array.
35
+ return default if i != 0
36
+
37
+ # Check if no parameters are specified at all, if so => set to default.
38
+ # Parameters 15, 0, 0 are equal to 15 or 15, nil, nil.
39
+ if parameters == nil || parameters == 0
40
+ if default != 0
41
+ return default
42
+ else
43
+ raise HelperException, "There were no parameters specified and there is no default for it."
44
+ end
45
+ else
46
+ return parameters
47
+ end
48
+ elsif parameters.is_a?(Array)
49
+ # In case array is given not like [1, 2] but like this ["1", "2"]. This usually happens when getting data from input forms.
50
+ parameters = parameters.map(&:to_i)
51
+ if parameters[i] == nil || parameters[i] == 0
52
+ if default != 0
53
+ return default
54
+ else
55
+ raise HelperException, "There were no parameters specified and there is no default for it."
56
+ end
57
+ else
58
+ return parameters[i]
59
+ end
60
+ else
61
+ raise HelperException, "Parameters have to be a integer, an array or nil."
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+ end
68
+
69
+ #
70
+ # Extra methods for mathematical calculations.
71
+ module Enumerable
72
+
73
+ def sum
74
+ return self.inject(0){|accum, i| accum + i }
75
+ end
76
+
77
+ def mean
78
+ return self.sum / self.length.to_f
79
+ end
80
+
81
+ def sample_variance
82
+ m = self.mean
83
+ sum = self.inject(0){|accum, i| accum + (i - m) ** 2 }
84
+ return sum / (self.length - 1).to_f
85
+ end
86
+
87
+ def standard_deviation
88
+ return Math.sqrt(self.sample_variance)
89
+ end
90
+
91
+ end
@@ -0,0 +1,45 @@
1
+ module Indicators
2
+ #
3
+ # Moving Average Convergence Divergence
4
+ class Macd
5
+
6
+ # MACD Line: (12-day EMA - 26-day EMA)
7
+ # Signal Line: 9-day EMA of MACD Line
8
+ # MACD Histogram: MACD Line - Signal Line
9
+ # Default MACD(12, 26, 9)
10
+ def self.calculate data, parameters
11
+ faster_periods = parameters[0]
12
+ slower_periods = parameters[1]
13
+ signal_periods = parameters[2]
14
+ output = Array.new
15
+ adj_closes = Indicators::Helper.validate_data(data, :adj_close, slower_periods+signal_periods-1)
16
+ # puts "faster=#{faster_periods}, slower=#{slower_periods}, signal=#{signal_periods}"
17
+
18
+ macd_line = []
19
+
20
+ adj_closes.each_with_index do |adj_close, index|
21
+ if index+1 >= slower_periods
22
+ # Calibrate me! Not sure why it doesn't accept from or from_faster.
23
+ faster_ema = Indicators::Ema.calculate(adj_closes[0..index], faster_periods).last
24
+ slower_ema = Indicators::Ema.calculate(adj_closes[0..index], slower_periods).last
25
+ macd_line[index] = faster_ema - slower_ema
26
+ if index+1 >= slower_periods+signal_periods
27
+ signal_line = Indicators::Ema.calculate(macd_line[(-signal_periods)..index], signal_periods).last
28
+ # Output is [MACD, Signal, MACD Hist]
29
+ macd_histogram = macd_line[index] - signal_line
30
+ output[index] = [macd_line[index], signal_line, macd_histogram]
31
+ else
32
+ output[index] = [macd_line[index], nil, nil]
33
+ end
34
+ else
35
+ macd_line[index] = nil
36
+ output[index] = nil
37
+ end
38
+ end
39
+
40
+ return output
41
+
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,81 @@
1
+ module Indicators
2
+ #
3
+ # Relative Strength Index
4
+ class Rsi
5
+
6
+ # 100
7
+ # RSI = 100 - --------
8
+ # 1 + RS
9
+ # RS = Average Gain / Average Loss
10
+ # First Average Gain = Sum of Gains over the past 14 periods / 14
11
+ # First Average Loss = Sum of Losses over the past 14 periods / 14
12
+ # Average Gain = [(previous Average Gain) x 13 + current Gain] / 14.
13
+ # Average Loss = [(previous Average Loss) x 13 + current Loss] / 14.
14
+ def self.calculate data, parameters
15
+ periods = parameters
16
+ output = Array.new
17
+ adj_closes = Indicators::Helper.validate_data(data, :adj_close, periods)
18
+
19
+ rs = Array.new
20
+ average_gain = 0.0
21
+ average_loss = 0.0
22
+ adj_closes.each_with_index do |adj_close, index|
23
+ if index >= periods
24
+ if index == periods
25
+ average_gain = gain_or_loss(adj_closes[0..index], :gain) / periods
26
+ average_loss = gain_or_loss(adj_closes[0..index], :loss) / periods
27
+ else
28
+ difference = adj_close - adj_closes[index-1]
29
+ if difference >= 0
30
+ current_gain = difference
31
+ current_loss = 0
32
+ else
33
+ current_gain = 0
34
+ current_loss = difference.abs
35
+ end
36
+ average_gain = (average_gain * (periods-1) + current_gain) / periods
37
+ average_loss = (average_loss * (periods-1) + current_loss) / periods
38
+ end
39
+ rs[index] = average_gain / average_loss
40
+ output[index] = 100 - 100/(1+rs[index])
41
+ if average_gain == 0
42
+ output[index] = 0
43
+ elsif average_loss == 0
44
+ output[index] = 100
45
+ end
46
+ else
47
+ rs[index] = nil
48
+ output[index] = nil
49
+ end
50
+ end
51
+
52
+ return output
53
+
54
+ end
55
+
56
+ #
57
+ # Helper methods for RSI
58
+ def self.gain_or_loss data, type
59
+ sum = 0.0
60
+ first_value = nil
61
+ data.each do |value|
62
+ if first_value == nil
63
+ first_value = value
64
+ else
65
+ if type == :gain
66
+ if value > first_value
67
+ sum += value - first_value
68
+ end
69
+ elsif type == :loss
70
+ if value < first_value
71
+ sum += first_value - value
72
+ end
73
+ end
74
+ first_value = value
75
+ end
76
+ end
77
+ return sum
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,27 @@
1
+ module Indicators
2
+ #
3
+ # Simple Moving Average
4
+ class Sma
5
+
6
+ # SMA: (sum of closing prices for x period)/x
7
+ def self.calculate data, parameters
8
+ periods = parameters
9
+ output = Array.new
10
+ # Returns an array from the requested column and checks if there is enought data points.
11
+ adj_closes = Indicators::Helper.validate_data(data, :adj_close, periods)
12
+
13
+ adj_closes.each_with_index do |adj_close, index|
14
+ start = index+1-periods
15
+ if index+1 >= periods
16
+ adj_closes_sum = adj_closes[start..index].sum
17
+ output[index] = (adj_closes_sum/periods.to_f)
18
+ else
19
+ output[index] = nil
20
+ end
21
+ end
22
+ return output
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,54 @@
1
+ module Indicators
2
+ #
3
+ # Full Stochastic Oscillator
4
+ class Sto
5
+
6
+ # %K = (Current Close - Lowest Low)/(Highest High - Lowest Low) * 100
7
+ # %D = 3-day SMA of %K
8
+ # Lowest Low = lowest low for the look-back period
9
+ # Highest High = highest high for the look-back period
10
+ # %K is multiplied by 100 to move the decimal point two places
11
+ #
12
+ # Full %K = Fast %K smoothed with X-period SMA
13
+ # Full %D = X-period SMA of Full %K
14
+ #
15
+ # Input 14, 3, 5
16
+ # Returns [full %K, full %D]
17
+ def self.calculate data, parameters
18
+ k1_periods = parameters[0]
19
+ k2_periods = parameters[1]
20
+ d_periods = parameters[2]
21
+ output = Array.new
22
+ adj_closes = Indicators::Helper.validate_data(data, :adj_close, k1_periods)
23
+ highs = Indicators::Helper.validate_data(data, :high, k1_periods)
24
+ lows = Indicators::Helper.validate_data(data, :low, k1_periods)
25
+
26
+ k1 = []
27
+ k2 = []
28
+ d = []
29
+ adj_closes.each_with_index do |adj_close, index|
30
+ start = index+1-k1_periods
31
+ if index+1 >= k1_periods
32
+ k1[index] = (adj_close - lows[start..index].min) / (highs[start..index].max - lows[start..index].min) * 100
33
+ if index+2 >= k1_periods + k2_periods
34
+ k2[index] = Indicators::Sma.calculate(k1[(k1_periods-1)..index], k2_periods).last
35
+ else
36
+ k2[index] = nil
37
+ end
38
+ if index+3 >= k1_periods + k2_periods + d_periods
39
+ d[index] = Indicators::Sma.calculate(k2[(k1_periods + k2_periods - 2)..index], d_periods).last
40
+ else
41
+ d[index] = nil
42
+ end
43
+ else
44
+ k1[index] = nil
45
+ end
46
+ output[index] = [k1[index], k2[index], d[index]]
47
+ end
48
+
49
+ return output
50
+
51
+ end
52
+
53
+ end
54
+ end
@@ -27,7 +27,7 @@ module Indicators
27
27
  def calc parameters
28
28
  # Check is parameters are usable.
29
29
  unless parameters.is_a?(Hash)
30
- raise DataException, 'Given parameters have to be a hash. FORMAT: .calc(:type => :ema, :variables => 12)'
30
+ raise DataException, 'Given parameters have to be a hash. FORMAT: .calc(:type => :ema, :params => 12)'
31
31
  end
32
32
 
33
33
  # If not specified, set default :type to :sma.
@@ -35,7 +35,7 @@ module Indicators
35
35
 
36
36
  # Check if there is such indicator type supported.
37
37
  case
38
- when INDICATORS.include?(parameters[:type]) then @results = Indicators::Main.calculate(@data, parameters)
38
+ when INDICATORS.include?(parameters[:type]) then @results = Indicators::Main.new(@data, parameters)
39
39
  else
40
40
  raise DataException, "Invalid indicator type specified (#{parameters[:type]})."
41
41
  end
@@ -1,358 +1,68 @@
1
1
  module Indicators
2
- #
3
- # Moving averages
2
+
4
3
  class Main
5
4
 
5
+ attr_reader :output, :abbr, :params
6
6
  # Error handling.
7
7
  class MainException < StandardError
8
8
  end
9
9
 
10
- def self.calculate data, parameters
10
+ def initialize data, parameters
11
+ type = parameters[:type]
12
+ all_params = parameters[:params]
13
+ @abbr = type.to_s.upcase
14
+ if type == :sma
15
+ @params = Indicators::Helper.get_parameters(all_params, 0, 20)
16
+ elsif type == :ema
17
+ @params = Indicators::Helper.get_parameters(all_params, 0, 20)
18
+ elsif type == :bb
19
+ @params = Array.new
20
+ @params[0] = Indicators::Helper.get_parameters(all_params, 0, 20)
21
+ @params[1] = Indicators::Helper.get_parameters(all_params, 1, 2)
22
+ elsif type == :macd
23
+ @params = Array.new
24
+ @params[0] = Indicators::Helper.get_parameters(all_params, 0, 12)
25
+ @params[1] = Indicators::Helper.get_parameters(all_params, 1, 26)
26
+ @params[2] = Indicators::Helper.get_parameters(all_params, 2, 9)
27
+ elsif type == :rsi
28
+ @params = Indicators::Helper.get_parameters(all_params, 0, 14)
29
+ elsif type == :sto
30
+ @params = Array.new
31
+ @params[0] = Indicators::Helper.get_parameters(all_params, 0, 14)
32
+ @params[1] = Indicators::Helper.get_parameters(all_params, 1, 3)
33
+ @params[2] = Indicators::Helper.get_parameters(all_params, 2, 3)
34
+ end
35
+
11
36
  if data.is_a?(Hash)
12
- results = Hash.new
37
+ @output = Hash.new
13
38
  data.each do |symbol, stock_data|
14
39
  # Check if this symbol was empty and don't go further with it.
15
40
  if stock_data.length == 0
16
- results[symbol] = []
41
+ @output[symbol] = []
17
42
  else
18
- results[symbol] = case parameters[:type]
19
- when :sma then sma(Indicators::Parser.parse_data(stock_data), parameters[:variables])
20
- when :ema then ema(Indicators::Parser.parse_data(stock_data), parameters[:variables])
21
- when :bb then bb(Indicators::Parser.parse_data(stock_data), parameters[:variables])
22
- when :macd then macd(Indicators::Parser.parse_data(stock_data), parameters[:variables])
23
- when :rsi then rsi(Indicators::Parser.parse_data(stock_data), parameters[:variables])
24
- when :sto then sto(Indicators::Parser.parse_data(stock_data), parameters[:variables])
43
+ @output[symbol] = case type
44
+ when :sma then Indicators::Sma.calculate(Indicators::Parser.parse_data(stock_data), @params)
45
+ when :ema then Indicators::Ema.calculate(Indicators::Parser.parse_data(stock_data), @params)
46
+ when :bb then Indicators::Bb.calculate(Indicators::Parser.parse_data(stock_data), @params)
47
+ when :macd then Indicators::Macd.calculate(Indicators::Parser.parse_data(stock_data), @params)
48
+ when :rsi then Indicators::Rsi.calculate(Indicators::Parser.parse_data(stock_data), @params)
49
+ when :sto then Indicators::Sto.calculate(Indicators::Parser.parse_data(stock_data), @params)
25
50
  end
26
51
  # Parser returns in {:date=>[2012.0, 2012.0, 2012.0], :open=>[409.4, 410.0, 414.95],} format
27
52
  end
28
53
  end
29
54
  else
30
- results = case parameters[:type]
31
- when :sma then sma(data, parameters[:variables])
32
- when :ema then ema(data, parameters[:variables])
33
- when :bb then bb(data, parameters[:variables])
34
- when :macd then macd(data, parameters[:variables])
35
- when :rsi then rsi(data, parameters[:variables])
36
- when :sto then raise MainException, "You cannot calculate Stochastic Oscillator on closing prices. Use Securities hash instead."
55
+ @output = case type
56
+ when :sma then Indicators::Sma.calculate(data, @params)
57
+ when :ema then Indicators::Ema.calculate(data, @params)
58
+ when :bb then Indicators::Bb.calculate(data, @params)
59
+ when :macd then Indicators::Macd.calculate(data, @params)
60
+ when :rsi then Indicators::Rsi.calculate(data, @params)
61
+ when :sto then raise MainException, "You cannot calculate Stochastic Oscillator on array. Highs and lows are needed. Feel free Securities gem hash instead."
37
62
  end
38
63
  end
39
- return results
64
+ return @output
40
65
  end
41
66
 
42
- private
43
-
44
- def self.get_data data, variables, column
45
- # If this is a hash, choose which column of values to use for calculations.
46
- if data.is_a?(Hash)
47
- usable_data = data[column]
48
- else
49
- usable_data = data
50
- end
51
-
52
- if usable_data.length < variables
53
- raise MainException, "Data point length (#{usable_data.length}) must be greater or equal to the needed periods value (#{variables})."
54
- end
55
- return usable_data
56
- end
57
-
58
- def self.get_variables variables, i=0, default=0
59
- if variables.is_a?(Array)
60
- # In case array is given not like [1, 2] but like this ["1", "2"]. This usually happens when getting data from input forms.
61
- variables = variables.map(&:to_i)
62
- if variables.length < 2
63
- return default if i != 0
64
- return variables[0]
65
- else
66
- if variables[i].nil? then return default else return variables[i] end
67
- end
68
- else
69
- return default if i != 0
70
- return variables
71
- end
72
- end
73
-
74
- #
75
- # Lagging indicators
76
- #
77
-
78
- # Simple Moving Average
79
- def self.sma data, variables
80
- periods = get_variables(variables)
81
- usable_data = Array.new
82
- usable_data = get_data(data, periods, :adj_close)
83
-
84
- # Just the calculation of SMA by the formula.
85
- sma = []
86
- usable_data.each_with_index do |value, index|
87
- from = index-periods+1
88
- if from >= 0
89
- sum = usable_data[from..index].sum
90
- sma[index] = (sum/periods.to_f)
91
- else
92
- sma[index] = nil
93
- end
94
- end
95
- return sma
96
- end
97
-
98
- #
99
- # Exponential Moving Average
100
-
101
- # Multiplier: (2 / (Time periods + 1) ) = (2 / (10 + 1) ) = 0.1818 (18.18%)
102
- # EMA: {Close - EMA(previous day)} x multiplier + EMA(previous day).
103
- def self.ema data, variables
104
- periods = get_variables(variables)
105
- usable_data = Array.new
106
- usable_data = get_data(data, periods, :adj_close)
107
-
108
- ema = []
109
- k = 2/(periods+1).to_f
110
- usable_data.each_with_index do |value, index|
111
- from = index+1-periods
112
- if from == 0
113
- ema[index] = sma(usable_data[from..index], periods).last
114
- # puts ma
115
- elsif from > 0
116
- ema[index] = ((usable_data[index] - ema[index-1]) * k + ema[index-1])
117
- else
118
- ema[index] = nil
119
- end
120
- end
121
- return ema
122
- end
123
-
124
- #
125
- # Bollinger Bands :type => :bb, :variables => 20, 2)
126
-
127
- # Middle Band = 20-day simple moving average (SMA)
128
- # Upper Band = 20-day SMA + (20-day standard deviation of price x 2)
129
- # Lower Band = 20-day SMA - (20-day standard deviation of price x 2)
130
- def self.bb data, variables
131
- periods = get_variables(variables)
132
- default_multiplier = 2
133
- multiplier = get_variables(variables, 1, default_multiplier)
134
-
135
- usable_data = Array.new
136
- usable_data = get_data(data, periods, :adj_close)
137
- bb = []
138
- usable_data.each_with_index do |value, index|
139
- from = index-periods+1
140
- if from >= 0
141
- middle_band = sma(usable_data[from..index], periods).last
142
- upper_band = middle_band + (usable_data[from..index].standard_deviation * multiplier)
143
- lower_band = middle_band - (usable_data[from..index].standard_deviation * multiplier)
144
- # output is [middle, upper, lower]
145
- bb[index] = [middle_band, upper_band, lower_band]
146
- else
147
- bb[index] = nil
148
- end
149
- end
150
- return bb
151
- end
152
-
153
- #
154
- # Moving Average Convergence Divergence
155
-
156
- # MACD Line: (12-day EMA - 26-day EMA)
157
- # Signal Line: 9-day EMA of MACD Line
158
- # MACD Histogram: MACD Line - Signal Line
159
- # Default MACD(12, 26, 9)
160
- def self.macd data, variables
161
- faster_periods = get_variables(variables, 0, 12)
162
- slower_periods = get_variables(variables, 1, 26)
163
- signal_periods = get_variables(variables, 2, 9)
164
- @variables = [faster_periods, slower_periods, signal_periods]
165
- # puts "faster=#{faster_periods}, slower=#{slower_periods}, signal=#{signal_periods}"
166
-
167
- usable_data = Array.new
168
- usable_data = get_data(data, slower_periods+signal_periods-1, :adj_close)
169
- macd = []
170
- macd_line = []
171
-
172
- usable_data.each_with_index do |value, index|
173
- if index+1 >= slower_periods
174
- # Calibrate me! Not sure why it doesn't accept from or from_faster.
175
- faster_ema = ema(usable_data[0..index], faster_periods).last
176
- slower_ema = ema(usable_data[0..index], slower_periods).last
177
- macd_line[index] = faster_ema - slower_ema
178
- if index+1 >= slower_periods + signal_periods
179
- # I'm pretty sure this is right.
180
- signal_line = ema(macd_line[(-signal_periods)..index], signal_periods).last
181
- # Output is [MACD, Signal, MACD Hist]
182
- macd_histogram = macd_line[index] - signal_line
183
- macd[index] = [macd_line[index], signal_line, macd_histogram]
184
- end
185
- else
186
- macd_line[index] = nil
187
- macd[index] = nil
188
- end
189
- end
190
- return macd
191
- end
192
-
193
- #
194
- # Relative Strength Index
195
-
196
- # 100
197
- # RSI = 100 - --------
198
- # 1 + RS
199
- # RS = Average Gain / Average Loss
200
- # First Average Gain = Sum of Gains over the past 14 periods / 14
201
- # First Average Loss = Sum of Losses over the past 14 periods / 14
202
- # Average Gain = [(previous Average Gain) x 13 + current Gain] / 14.
203
- # Average Loss = [(previous Average Loss) x 13 + current Loss] / 14.
204
- def self.rsi data, variables
205
- periods = get_variables(variables)
206
- usable_data = Array.new
207
- usable_data = get_data(data, periods, :adj_close)
208
-
209
- values = []
210
- rsi = []
211
- rs = []
212
- average_gain = 0.0
213
- average_loss = 0.0
214
- usable_data.each_with_index do |value, index|
215
- values[index] = value
216
- if index >= periods
217
- if index == periods
218
- average_gain = gain(values) / periods
219
- average_loss = loss(values) / periods
220
- else
221
- difference = value - values[index-1]
222
- if difference >= 0
223
- current_gain = difference
224
- current_loss = 0
225
- else
226
- current_gain = 0
227
- current_loss = difference.abs
228
- end
229
- average_gain = (average_gain * (periods-1) + current_gain) / periods
230
- average_loss = (average_loss * (periods-1) + current_loss) / periods
231
- end
232
- rs[index] = average_gain / average_loss
233
- rsi[index] = 100 - 100/(1+rs[index])
234
- if average_gain == 0
235
- rsi[index] = 0
236
- elsif average_loss == 0
237
- rsi[index] = 100
238
- end
239
- else
240
- rs[index] = nil
241
- rsi[index] = nil
242
- end
243
- end
244
- return rsi
245
- end
246
-
247
- #
248
- # Full Stochastic Oscillator
249
-
250
- # %K = (Current Close - Lowest Low)/(Highest High - Lowest Low) * 100
251
- # %D = 3-day SMA of %K
252
- # Lowest Low = lowest low for the look-back period
253
- # Highest High = highest high for the look-back period
254
- # %K is multiplied by 100 to move the decimal point two places
255
- #
256
- # Full %K = Fast %K smoothed with X-period SMA
257
- # Full %D = X-period SMA of Full %K
258
- #
259
- # Input 14, 3, 5
260
- # Returns [full %K, full %D]
261
- def self.sto data, variables
262
- k1_periods = get_variables(variables, 0, 14)
263
- k2_periods = get_variables(variables, 1, 3)
264
- d_periods = get_variables(variables, 2, 1)
265
- high_data = Array.new
266
- low_data = Array.new
267
- adj_close_data = Array.new
268
- high_data = get_data(data, k1_periods, :high)
269
- low_data = get_data(data, k1_periods, :low)
270
- adj_close_data = get_data(data, k1_periods, :adj_close)
271
-
272
- k1 = []
273
- k2 = []
274
- d = []
275
- sto = []
276
- adj_close_data.each_with_index do |adj_close, index|
277
- from = index-k1_periods+1
278
- if index+1 >= k1_periods
279
- k1[index] = (adj_close - low_data[from..index].min) / (high_data[from..index].max - low_data[from..index].min) * 100
280
- if index+2 >= k1_periods + k2_periods
281
- k2[index] = sma(k1[(k1_periods-1)..index], k2_periods).last
282
- else
283
- k2[index] = nil
284
- end
285
- if index+3 >= k1_periods + k2_periods + d_periods
286
- d[index] = sma(k2[(k1_periods + k2_periods - 2)..index], d_periods).last
287
- else
288
- d[index] = nil
289
- end
290
- else
291
- k1[index] = nil
292
- end
293
- sto[index] = [k1[index], k2[index], d[index]]
294
- end
295
- return sto
296
- end
297
-
298
-
299
- #
300
- # Helper methods for RSI
301
- def self.gain data
302
- sum = 0.0
303
- first_value = nil
304
- data.each do |value|
305
- if first_value == nil
306
- first_value = value
307
- else
308
- if value > first_value
309
- sum += value - first_value
310
- end
311
- first_value = value
312
- end
313
- end
314
- return sum
315
- end
316
-
317
- def self.loss data
318
- sum = 0.0
319
- first_value = nil
320
- data.each do |value|
321
- if first_value == nil
322
- first_value = value
323
- else
324
- if value < first_value
325
- sum += first_value - value
326
- end
327
- first_value = value
328
- end
329
- end
330
- return sum
331
- end
332
-
333
- end
334
- end
335
-
336
- #
337
- # Extra methods for mathematical calculations.
338
- module Enumerable
339
-
340
- def sum
341
- return self.inject(0){|accum, i| accum + i }
342
67
  end
343
-
344
- def mean
345
- return self.sum / self.length.to_f
346
- end
347
-
348
- def sample_variance
349
- m = self.mean
350
- sum = self.inject(0){|accum, i| accum + (i - m) ** 2 }
351
- return sum / (self.length - 1).to_f
352
- end
353
-
354
- def standard_deviation
355
- return Math.sqrt(self.sample_variance)
356
- end
357
-
358
68
  end
@@ -1,3 +1,3 @@
1
1
  module Indicators
2
- VERSION = "0.1.0"
2
+ VERSION = "1.0.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: indicators
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-02 00:00:00.000000000 Z
12
+ date: 2012-09-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -75,6 +75,13 @@ files:
75
75
  - Rakefile
76
76
  - indicators.gemspec
77
77
  - lib/indicators.rb
78
+ - lib/indicators/calculations/bb.rb
79
+ - lib/indicators/calculations/ema.rb
80
+ - lib/indicators/calculations/helper.rb
81
+ - lib/indicators/calculations/macd.rb
82
+ - lib/indicators/calculations/rsi.rb
83
+ - lib/indicators/calculations/sma.rb
84
+ - lib/indicators/calculations/sto.rb
78
85
  - lib/indicators/data.rb
79
86
  - lib/indicators/main.rb
80
87
  - lib/indicators/parser.rb
@@ -95,7 +102,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
102
  version: '0'
96
103
  segments:
97
104
  - 0
98
- hash: 1434689323242091745
105
+ hash: 856502728936260822
99
106
  required_rubygems_version: !ruby/object:Gem::Requirement
100
107
  none: false
101
108
  requirements:
@@ -104,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
111
  version: '0'
105
112
  segments:
106
113
  - 0
107
- hash: 1434689323242091745
114
+ hash: 856502728936260822
108
115
  requirements: []
109
116
  rubyforge_project:
110
117
  rubygems_version: 1.8.24