advanced_math 0.0.5 → 0.0.7
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/Manifest.txt +6 -1
- data/advanced_math.gemspec +26 -4
- data/gem_publish.sh +6 -0
- data/lib/advanced_math/rsi.rb +113 -0
- data/lib/advanced_math/sma.rb +74 -0
- data/lib/advanced_math.rb +4 -57
- data/test/RSI.pm +247 -0
- data/test/rsi.pl +8 -0
- data/test/test_rsi.rb +127 -0
- data/test/test_simple_moving_average.rb +51 -11
- metadata +10 -4
- data/advanced_math-0.0.4.gem +0 -0
data/Manifest.txt
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
-
advanced_math-0.0.4.gem
|
2
1
|
advanced_math.gemspec
|
2
|
+
gem_publish.sh
|
3
3
|
History.txt
|
4
|
+
lib/advanced_math/rsi.rb
|
5
|
+
lib/advanced_math/sma.rb
|
4
6
|
lib/advanced_math.rb
|
5
7
|
Manifest.txt
|
6
8
|
PostInstall.txt
|
7
9
|
README.rdoc
|
10
|
+
test/rsi.pl
|
11
|
+
test/RSI.pm
|
12
|
+
test/test_rsi.rb
|
8
13
|
test/test_simple_moving_average.rb
|
data/advanced_math.gemspec
CHANGED
@@ -1,16 +1,37 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
1
4
|
# -*- encoding: utf-8 -*-
|
2
5
|
|
3
6
|
Gem::Specification.new do |s|
|
4
7
|
s.name = %q{advanced_math}
|
5
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.7"
|
6
9
|
|
7
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
11
|
s.authors = [%q{Glenn Nagel}]
|
9
12
|
s.date = %q{2011-08-28}
|
10
13
|
s.description = %q{A simple gem for advanced and financial math calcualtions.}
|
11
14
|
s.email = [%q{glenn@mercury-wireless.com}]
|
12
|
-
s.extra_rdoc_files = [
|
13
|
-
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"History.txt",
|
17
|
+
"Manifest.txt",
|
18
|
+
"PostInstall.txt"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
"History.txt",
|
22
|
+
"Manifest.txt",
|
23
|
+
"PostInstall.txt",
|
24
|
+
"README.rdoc",
|
25
|
+
"advanced_math.gemspec",
|
26
|
+
"gem_publish.sh",
|
27
|
+
"lib/advanced_math.rb",
|
28
|
+
"lib/advanced_math/rsi.rb",
|
29
|
+
"lib/advanced_math/sma.rb",
|
30
|
+
"test/RSI.pm",
|
31
|
+
"test/rsi.pl",
|
32
|
+
"test/test_rsi.rb",
|
33
|
+
"test/test_simple_moving_average.rb"
|
34
|
+
]
|
14
35
|
s.homepage = %q{https://github.com/gnagel/mercury-wireless-public/tree/master/ruby/advanced_math}
|
15
36
|
s.post_install_message = %q{PostInstall.txt}
|
16
37
|
s.rdoc_options = [%q{--main}, %q{README.rdoc}]
|
@@ -18,7 +39,7 @@ Gem::Specification.new do |s|
|
|
18
39
|
s.rubyforge_project = %q{advanced_math}
|
19
40
|
s.rubygems_version = %q{1.8.6}
|
20
41
|
s.summary = %q{A simple gem for advanced and financial math calcualtions.}
|
21
|
-
s.test_files = [%q{test/test_simple_moving_average.rb}]
|
42
|
+
s.test_files = [%q{test/test_rsi.rb}, %q{test/test_simple_moving_average.rb}]
|
22
43
|
|
23
44
|
if s.respond_to? :specification_version then
|
24
45
|
s.specification_version = 3
|
@@ -32,3 +53,4 @@ Gem::Specification.new do |s|
|
|
32
53
|
s.add_dependency(%q<hoe>, ["~> 2.9"])
|
33
54
|
end
|
34
55
|
end
|
56
|
+
|
data/gem_publish.sh
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/sma.rb"
|
2
|
+
|
3
|
+
module AdvancedMath
|
4
|
+
###
|
5
|
+
# Relative Strength Index (RSI) calculator
|
6
|
+
# Created: 2011-08-28
|
7
|
+
# Author: G Nagel
|
8
|
+
# Company: Mercury Wireless Software LLC
|
9
|
+
# Source: http://en.wikipedia.org/wiki/Relative_Strength_Index#Cutler.27s_RSI
|
10
|
+
# Source: http://www.aspenres.com/Documents/AspenGraphics4.0/Aspen_Graphics_4.htm#CutlersRSI.htm
|
11
|
+
# Source: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:relative_strength_index_rsi
|
12
|
+
###
|
13
|
+
class RelativeStrengthIndex
|
14
|
+
attr_accessor :values_up, :values_down, :range, :last_value
|
15
|
+
attr_accessor :verbose
|
16
|
+
###
|
17
|
+
# Initialize the members
|
18
|
+
###
|
19
|
+
def initialize(range)
|
20
|
+
raise ArgumentError, "Range is nil" unless (range)
|
21
|
+
raise ArgumentError, "Range must be > 1" unless range.to_i > 1
|
22
|
+
|
23
|
+
@verbose = false
|
24
|
+
@values_up = []
|
25
|
+
@values_down = []
|
26
|
+
@range = range.to_i()
|
27
|
+
@last_value = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
###
|
31
|
+
# Add a value to the list.
|
32
|
+
# If the list is < @range, then return nil.
|
33
|
+
# Otherwise compute the RSI and return the value.
|
34
|
+
###
|
35
|
+
def add(value)
|
36
|
+
raise ArgumentError, "Value is nil" unless (value)
|
37
|
+
puts "==================" if verbose()
|
38
|
+
puts "value = #{value}" if verbose()
|
39
|
+
|
40
|
+
if (nil == @last_value)
|
41
|
+
@last_value = value.to_f()
|
42
|
+
puts "Setting intial last_value = #{@last_value}" if verbose()
|
43
|
+
return nil
|
44
|
+
end
|
45
|
+
|
46
|
+
@values_up.shift() if (range() == @values_up.length())
|
47
|
+
@values_down.shift() if (range() == @values_down.length())
|
48
|
+
|
49
|
+
diff = value.to_f - @last_value
|
50
|
+
@last_value = value.to_f()
|
51
|
+
if (0 > diff)
|
52
|
+
@values_up << 0
|
53
|
+
@values_down << diff.abs()
|
54
|
+
elsif (0 < diff)
|
55
|
+
@values_up << diff
|
56
|
+
@values_down << 0
|
57
|
+
else
|
58
|
+
@values_up << 0
|
59
|
+
@values_down << 0
|
60
|
+
end
|
61
|
+
puts "diff = #{diff}, values_up = #{@values_up.inspect()}, values_down = #{@values_down.inspect()}" if verbose()
|
62
|
+
|
63
|
+
if (@values_up.length() < range())
|
64
|
+
puts "#{@values_up.length()} < #{range()}" if verbose()
|
65
|
+
return nil
|
66
|
+
end
|
67
|
+
if (@values_up.length() > range())
|
68
|
+
puts "#{@values_up.length()} > #{range()}" if verbose()
|
69
|
+
@values_up.shift()
|
70
|
+
@values_down.shift()
|
71
|
+
else
|
72
|
+
puts "#{@values_up.length()} == #{range()}" if verbose()
|
73
|
+
end
|
74
|
+
|
75
|
+
sum = {:up => 0, :down => 0, :i_up => 0, :i_down => 0}
|
76
|
+
@values_up.each() { |value| sum[:up] = sum[:up] + value }
|
77
|
+
@values_down.each() { |value| sum[:down] = sum[:down] + value }
|
78
|
+
|
79
|
+
rs = (0 == sum[:down]) ? 100.0 : (sum[:up] / sum[:down])
|
80
|
+
puts "rs = #{rs}" if verbose()
|
81
|
+
|
82
|
+
rsi = 100.0 - (100.0 / (1.0 + rs))
|
83
|
+
puts "rsi = #{rsi}" if verbose()
|
84
|
+
return rsi
|
85
|
+
end
|
86
|
+
|
87
|
+
###
|
88
|
+
# Compute the RSI values for an array
|
89
|
+
# Return an array with the RSI values
|
90
|
+
###
|
91
|
+
def add_array(values)
|
92
|
+
raise ArgumentError, "Value is nil" unless (values);
|
93
|
+
raise ArgumentError, "Value is not an array" unless values.kind_of?(Array);
|
94
|
+
|
95
|
+
output = []
|
96
|
+
|
97
|
+
# Calculate the sum of the array
|
98
|
+
values.each() { |value| output << add(value) }
|
99
|
+
|
100
|
+
# Return the RSI array
|
101
|
+
return output
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
###
|
106
|
+
# RSI is just an alias to RelativeStrengthIndex
|
107
|
+
###
|
108
|
+
class RSI < RelativeStrengthIndex
|
109
|
+
def initialize(range)
|
110
|
+
super(range)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module AdvancedMath
|
2
|
+
# Simple Moving Average (SMA) calculator
|
3
|
+
# Created: 2011-06-24
|
4
|
+
# Author: G Nagel
|
5
|
+
# Company: Mercury Wireless Software LLC
|
6
|
+
class SimpleMovingAverage
|
7
|
+
###
|
8
|
+
# Initialize the members:
|
9
|
+
# range:
|
10
|
+
# number of values to average
|
11
|
+
# sum:
|
12
|
+
# current sum of all values in the array
|
13
|
+
# values:
|
14
|
+
# array of values used as temporary storage
|
15
|
+
###
|
16
|
+
def initialize(range)
|
17
|
+
raise ArgumentError, "Range is nil" unless (range);
|
18
|
+
raise ArgumentError, "Range must be >= 1" unless range.to_i >= 1;
|
19
|
+
@range = range.to_i;
|
20
|
+
@sum = 0;
|
21
|
+
@values = Array.new();
|
22
|
+
end
|
23
|
+
|
24
|
+
###
|
25
|
+
# Add a value to the list.
|
26
|
+
# If the list is < @range, then return nil.
|
27
|
+
# Otherwise compute the SMA and return the value.
|
28
|
+
###
|
29
|
+
def add(value)
|
30
|
+
raise ArgumentError, "Value is nil" unless (value);
|
31
|
+
|
32
|
+
# add the value to the end of the array.
|
33
|
+
@values.push(value);
|
34
|
+
|
35
|
+
# Calculate the sum of the array
|
36
|
+
@sum += value.to_f;
|
37
|
+
|
38
|
+
# Is the array less than the range?
|
39
|
+
return nil if (@values.length() < @range)
|
40
|
+
|
41
|
+
# Is the array larger than the range?
|
42
|
+
@sum -= @values.shift.to_f() if (@values.length() > @range)
|
43
|
+
|
44
|
+
# Compute the average
|
45
|
+
return @sum.to_f / @range.to_f;
|
46
|
+
end
|
47
|
+
|
48
|
+
###
|
49
|
+
# Compute the SMA values for an array
|
50
|
+
# Return an array with the SMA values
|
51
|
+
###
|
52
|
+
def add_array(values)
|
53
|
+
raise ArgumentError, "Value is nil" unless (values);
|
54
|
+
raise ArgumentError, "Value is not an array" unless values.kind_of?(Array);
|
55
|
+
|
56
|
+
output = []
|
57
|
+
|
58
|
+
# Calculate the sum of the array
|
59
|
+
values.each() { |value| output << add(value) }
|
60
|
+
|
61
|
+
# Return the SMA array
|
62
|
+
return output
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
###
|
67
|
+
# SMA is just an alias to SimpleMovingAverage
|
68
|
+
###
|
69
|
+
class SMA < SimpleMovingAverage
|
70
|
+
def initialize(range)
|
71
|
+
super(range)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/advanced_math.rb
CHANGED
@@ -1,59 +1,6 @@
|
|
1
|
-
|
2
1
|
module AdvancedMath
|
3
|
-
VERSION = '0.0.
|
4
|
-
|
5
|
-
|
6
|
-
# Simple Moving Average (SMA) calculator
|
7
|
-
# Created: 2011-06-24
|
8
|
-
# Author: G Nagel
|
9
|
-
# Company: Mercury Wireless Software LLC
|
10
|
-
class SimpleMovingAverage
|
11
|
-
###
|
12
|
-
# Initialize the members:
|
13
|
-
# range:
|
14
|
-
# number of values to average
|
15
|
-
# sum:
|
16
|
-
# current sum of all values in the array
|
17
|
-
# values:
|
18
|
-
# array of values used as temporary storage
|
19
|
-
###
|
20
|
-
def initialize(range)
|
21
|
-
raise ArgumentError, "Range is nil" unless (range);
|
22
|
-
raise ArgumentError, "Range must be >= 1" unless range.to_i >= 1;
|
23
|
-
@range = range.to_i;
|
24
|
-
@sum = 0;
|
25
|
-
@values = Array.new();
|
26
|
-
end
|
27
|
-
|
28
|
-
###
|
29
|
-
# Add a value to the list.
|
30
|
-
# If the list is < @range, then return nil.
|
31
|
-
# Otherwise compute the SMA and return the value.
|
32
|
-
###
|
33
|
-
def add(value)
|
34
|
-
raise ArgumentError, "Value is nil" unless (value);
|
35
|
-
|
36
|
-
# add the value to the end of the array.
|
37
|
-
@values.push(value);
|
38
|
-
|
39
|
-
# Calculate the sum of the array
|
40
|
-
@sum += value.to_f;
|
41
|
-
|
42
|
-
# Is the array less than the range?
|
43
|
-
return nil if (@values.length() < @range)
|
44
|
-
|
45
|
-
# Is the array larger than the range?
|
46
|
-
@sum -= @values.shift.to_f() if (@values.length() > @range)
|
47
|
-
|
48
|
-
# Compute the average
|
49
|
-
return @sum.to_f / @range.to_f;
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
###
|
54
|
-
# SMA is just an alias to SimpleMovingAverage
|
55
|
-
###
|
56
|
-
class SMA < SimpleMovingAverage
|
57
|
-
end
|
58
|
-
|
2
|
+
VERSION = '0.0.7'
|
59
3
|
end
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + "/advanced_math/sma.rb"
|
6
|
+
require File.dirname(__FILE__) + "/advanced_math/rsi.rb"
|
data/test/RSI.pm
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
package Math::Business::RSI;
|
2
|
+
|
3
|
+
use strict;
|
4
|
+
use warnings;
|
5
|
+
use Carp;
|
6
|
+
|
7
|
+
our $VERSION = 2.5; # local revision: b
|
8
|
+
|
9
|
+
use Math::Business::SMA;
|
10
|
+
use Math::Business::EMA;
|
11
|
+
|
12
|
+
1;
|
13
|
+
|
14
|
+
sub recommended {
|
15
|
+
my $class = shift;
|
16
|
+
|
17
|
+
$class->new(14);
|
18
|
+
}
|
19
|
+
|
20
|
+
sub new {
|
21
|
+
my $class = shift;
|
22
|
+
my $this = bless {
|
23
|
+
U => Math::Business::EMA->new,
|
24
|
+
D => Math::Business::EMA->new,
|
25
|
+
RSI => undef,
|
26
|
+
cy => undef,
|
27
|
+
}, $class;
|
28
|
+
|
29
|
+
my $alpha = shift;
|
30
|
+
if( defined $alpha ) {
|
31
|
+
$this->set_alpha( $alpha );
|
32
|
+
}
|
33
|
+
|
34
|
+
return $this;
|
35
|
+
}
|
36
|
+
|
37
|
+
sub set_alpha {
|
38
|
+
my $this = shift;
|
39
|
+
my $alpha = shift;
|
40
|
+
|
41
|
+
# NOTE: this alpha is different than you might think ... it's really inverse alpha
|
42
|
+
# Wilder uses alpha=14 instead of alpha=(1/14) like you might expect
|
43
|
+
|
44
|
+
my $days = 2*$alpha - 1; # so days is 2*$alpha-1 instead of the expected 2*(1/$alpha)-1
|
45
|
+
|
46
|
+
eval { $this->set_days( $days ) };
|
47
|
+
croak "set_alpha() is basically set_days(2*$alpha-1), which complained: $@" if $@;
|
48
|
+
}
|
49
|
+
|
50
|
+
sub set_standard {
|
51
|
+
my $this = shift;
|
52
|
+
my $rm = ref $this->{U};
|
53
|
+
|
54
|
+
if( $rm =~ m/SMA/ ) {
|
55
|
+
$this->{U} = Math::Business::EMA->new;
|
56
|
+
$this->{D} = Math::Business::EMA->new;
|
57
|
+
|
58
|
+
if( my $d = $this->{days} ) {
|
59
|
+
$this->set_days($d);
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
sub set_cutler {
|
65
|
+
my $this = shift;
|
66
|
+
my $rm = ref $this->{U};
|
67
|
+
|
68
|
+
if( $rm =~ m/EMA/ ) {
|
69
|
+
$this->{U} = Math::Business::SMA->new;
|
70
|
+
$this->{D} = Math::Business::SMA->new;
|
71
|
+
|
72
|
+
if( my $d = $this->{days} ) {
|
73
|
+
$this->set_days($d);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
sub set_days {
|
79
|
+
my $this = shift;
|
80
|
+
my $arg = int(shift);
|
81
|
+
|
82
|
+
croak "days must be a positive non-zero integer" if $arg <= 0;
|
83
|
+
|
84
|
+
$this->{U}->set_days($this->{days} = $arg);
|
85
|
+
$this->{D}->set_days($arg);
|
86
|
+
delete $this->{cy};
|
87
|
+
delete $this->{RSI};
|
88
|
+
}
|
89
|
+
|
90
|
+
sub insert {
|
91
|
+
my $this = shift;
|
92
|
+
my $close_yesterday = $this->{cy};
|
93
|
+
|
94
|
+
my $EMA_U = $this->{U};
|
95
|
+
my $EMA_D = $this->{D};
|
96
|
+
|
97
|
+
croak "You must set the number of days before you try to insert" if not $this->{days};
|
98
|
+
while( defined( my $close_today = shift ) ) {
|
99
|
+
if( defined $close_yesterday ) {
|
100
|
+
my $delta = $close_today - $close_yesterday;
|
101
|
+
|
102
|
+
my ($U,$D) = (0,0);
|
103
|
+
if( $delta > 0 ) {
|
104
|
+
$U = $delta;
|
105
|
+
$D = 0;
|
106
|
+
|
107
|
+
} elsif( $delta < 0 ) {
|
108
|
+
$U = 0;
|
109
|
+
$D = abs $delta;
|
110
|
+
}
|
111
|
+
|
112
|
+
$EMA_U->insert($U);
|
113
|
+
$EMA_D->insert($D);
|
114
|
+
}
|
115
|
+
|
116
|
+
if( defined(my $eu = $EMA_U->query) ) {
|
117
|
+
my $ed = $EMA_D->query;
|
118
|
+
my $rs = (($ed == 0) ? 100 : $eu/$ed ); # NOTE: This is by definition apparently.
|
119
|
+
|
120
|
+
$this->{RSI} = 100 - 100/(1+$rs);
|
121
|
+
}
|
122
|
+
|
123
|
+
$close_yesterday = $close_today;
|
124
|
+
}
|
125
|
+
|
126
|
+
$this->{cy} = $close_yesterday;
|
127
|
+
}
|
128
|
+
|
129
|
+
sub query {
|
130
|
+
my $this = shift;
|
131
|
+
|
132
|
+
return $this->{RSI};
|
133
|
+
}
|
134
|
+
|
135
|
+
__END__
|
136
|
+
|
137
|
+
=head1 NAME
|
138
|
+
|
139
|
+
Math::Business::RSI - Technical Analysis: Relative Strength Index
|
140
|
+
|
141
|
+
=head1 SYNOPSIS
|
142
|
+
|
143
|
+
use Math::Business::RSI;
|
144
|
+
|
145
|
+
my $rsi = new Math::Business::RSI;
|
146
|
+
$rsi->set_alpha(14); # issues a set days of 2*14-1
|
147
|
+
$rsi->set_days(27); # equivilent to set_alpha(14)
|
148
|
+
|
149
|
+
# equivelent to set_days(27)/set_alpha(14):
|
150
|
+
my $rsi = new Math::Business::RSI(14);
|
151
|
+
|
152
|
+
# or to just get the recommended model ... set_alpha(14)
|
153
|
+
my $rsi = Math::Business::RSI->recommended;
|
154
|
+
|
155
|
+
my @closing_values = qw(
|
156
|
+
3 4 4 5 6 5 6 5 5 5 5
|
157
|
+
6 6 6 6 7 7 7 8 8 8 8
|
158
|
+
);
|
159
|
+
|
160
|
+
# choose one:
|
161
|
+
$rsi->insert( @closing_values );
|
162
|
+
$rsi->insert( $_ ) for @closing_values;
|
163
|
+
|
164
|
+
if( defined(my $q = $rsi->query) ) {
|
165
|
+
print "RSI: $q.\n";
|
166
|
+
|
167
|
+
} else {
|
168
|
+
print "RSI: n/a.\n";
|
169
|
+
}
|
170
|
+
|
171
|
+
=head1 RESEARCHER
|
172
|
+
|
173
|
+
The RSI was designed by J. Welles Wilder Jr in 1978.
|
174
|
+
|
175
|
+
According to Wilder, a security is "overbought" it the RSI reaches an upper
|
176
|
+
bound of 70 and is "oversold" when it moves below 30. Some sources also
|
177
|
+
use thresholds of 80 and 20.
|
178
|
+
|
179
|
+
Therefore, moving above the upper threshold is a selling signal, whlie moving
|
180
|
+
below the lower threshold is a signal to buy.
|
181
|
+
|
182
|
+
Oddly, RSI(14) uses a "smoothing period" of 14 days -- referring to an alpha of
|
183
|
+
1/14. This means the EMA[N]u/EMA[N]d has N set to 27. This also means the
|
184
|
+
alpha is upside of other alpha you might see. RSI(14) actually uses an alpha
|
185
|
+
of ~0.0714, but set_alpha() takes the inverse to make C<$rsi->set_alpha(14)>
|
186
|
+
work.
|
187
|
+
|
188
|
+
If all of the above seems really confusing, no worries: RSI(14) means
|
189
|
+
C<set_alpha(14)> (or C<new(14)> and is equivelent to C<set_days(27)>.
|
190
|
+
|
191
|
+
=head2 Cutler
|
192
|
+
|
193
|
+
There are differing schools of thought on how to calculate this and how
|
194
|
+
important it is to stick to precisely the formula Wilder used. Cutler used
|
195
|
+
simple moving averages instead of exponential moving averages.
|
196
|
+
|
197
|
+
You can switch between Wilder and Cutler mode with these:
|
198
|
+
|
199
|
+
$rsi->set_cutler; # for simple moving averages
|
200
|
+
$rsi->set_standard; # for exponential moving averages
|
201
|
+
|
202
|
+
WARNING: Both of these clear out the value queue! If you need to track
|
203
|
+
both, you'll need two objects.
|
204
|
+
|
205
|
+
=head1 THANKS
|
206
|
+
|
207
|
+
Todd Litteken C<< <cl@xganon.com> >>
|
208
|
+
|
209
|
+
Amit Dutt C<< <amit_dutt@hotmail.com> >>
|
210
|
+
|
211
|
+
=head1 AUTHOR
|
212
|
+
|
213
|
+
Paul Miller C<< <jettero@cpan.org> >>
|
214
|
+
|
215
|
+
I am using this software in my own projects... If you find bugs, please please
|
216
|
+
please let me know.
|
217
|
+
|
218
|
+
I normally hang out on #perl on freenode, so you can try to get immediate
|
219
|
+
gratification there if you like. L<irc://irc.freenode.net/perl>
|
220
|
+
|
221
|
+
There is also a mailing list with very light traffic that you might want to
|
222
|
+
join: L<http://groups.google.com/group/stockmonkey/>.
|
223
|
+
|
224
|
+
=head1 COPYRIGHT
|
225
|
+
|
226
|
+
Copyright (c) 2010 Paul Miller
|
227
|
+
|
228
|
+
=head1 LICENSE
|
229
|
+
|
230
|
+
This module is free software. You can redistribute it and/or
|
231
|
+
modify it under the terms of the Artistic License 2.0.
|
232
|
+
|
233
|
+
This program is distributed in the hope that it will be useful,
|
234
|
+
but without any warranty; without even the implied warranty of
|
235
|
+
merchantability or fitness for a particular purpose.
|
236
|
+
|
237
|
+
[This software may have had previous licenses, of which the current maintainer
|
238
|
+
is completely unaware. If this is so, it is possible the above license is
|
239
|
+
incorrect or invalid.]
|
240
|
+
|
241
|
+
=head1 SEE ALSO
|
242
|
+
|
243
|
+
perl(1), L<Math::Business::StockMonkey>, L<Math::Business::StockMonkey::FAQ>, L<Math::Business::StockMonkey::CookBook>
|
244
|
+
|
245
|
+
L<http://en.wikipedia.org/wiki/Relative_Strength_Index>
|
246
|
+
|
247
|
+
=cut
|
data/test/rsi.pl
ADDED
data/test/test_rsi.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + "/../lib/advanced_math.rb"
|
3
|
+
|
4
|
+
module AdvancedMath
|
5
|
+
class RelativeStrengthIndexTest < Test::Unit::TestCase
|
6
|
+
# Verify the constructor raises an ArgumentError if the "range" is invalid
|
7
|
+
def test_sma_initialize_nil
|
8
|
+
assert_raise(ArgumentError) { RelativeStrengthIndex.new(nil) }
|
9
|
+
end
|
10
|
+
|
11
|
+
# Verify the constructor raises an ArgumentError if the "range" is invalid
|
12
|
+
def test_sma_initialize_negative
|
13
|
+
assert_raise(ArgumentError) { RelativeStrengthIndex.new(-1) }
|
14
|
+
(-1...-1000).each do |i|
|
15
|
+
assert_raise(ArgumentError) { RelativeStrengthIndex.new(i) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Verify the constructor raises an ArgumentError if the "range" is invalid
|
20
|
+
def test_sma_initialize_zero
|
21
|
+
assert_raise(ArgumentError) { RelativeStrengthIndex.new(0) }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Verify the constructor raises an ArgumentError if the "range" is invalid
|
25
|
+
def test_sma_initialize_one
|
26
|
+
assert_raise(ArgumentError) { RelativeStrengthIndex.new(1) }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Verify the constructor doesn't raise an exception if the value is positive
|
30
|
+
def test_sma_initialize_positive
|
31
|
+
(2...1000).each do |i|
|
32
|
+
assert_nothing_raised(ArgumentError) { RelativeStrengthIndex.new(i) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Verify "add" raises an ArgumentError if the "value" is invalid
|
37
|
+
def test_sma_add_nil
|
38
|
+
assert_raise(ArgumentError) { RelativeStrengthIndex.new(1).add(nil) }
|
39
|
+
end
|
40
|
+
|
41
|
+
# # Verify SMA of 1, always returns the same value
|
42
|
+
# def test_sma_add_0
|
43
|
+
# sma = RelativeStrengthIndex.new(2)
|
44
|
+
# assert_nil(value = sma.add(0))
|
45
|
+
# assert_nil(value = sma.add(0))
|
46
|
+
# assert_not_nil(value = sma.add(0))
|
47
|
+
# assert_equal(50, value)
|
48
|
+
# (0...1000).each { |i| assert_equal(50, sma.add(0)) }
|
49
|
+
# end
|
50
|
+
|
51
|
+
def test_sma_add_range
|
52
|
+
sma = RelativeStrengthIndex.new(2)
|
53
|
+
(0...1000).each do |i|
|
54
|
+
value = sma.add(i)
|
55
|
+
if (i < 2)
|
56
|
+
assert_nil(value, "#{i}")
|
57
|
+
else
|
58
|
+
assert_not_nil(value, "#{i}")
|
59
|
+
assert_equal(99, sma.add(i).to_i(), "#{i}")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def RelativeStrengthIndexTest.rsi_values(period, values)
|
65
|
+
cmd = "#{File.dirname(__FILE__)}/rsi.pl #{period} #{values.join(" ")}"
|
66
|
+
cmd = `#{cmd}`.strip()
|
67
|
+
return cmd.to_i()
|
68
|
+
end
|
69
|
+
|
70
|
+
def RelativeStrengthIndexTest.rsi_range(period, prefix, range)
|
71
|
+
values = []
|
72
|
+
prefix.each() { |i| values << i }
|
73
|
+
range.each() { |i| values << i }
|
74
|
+
return RelativeStrengthIndexTest.rsi_values(period, values)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Verify SMA of 2, always returns the same value - 0.5
|
78
|
+
def test_sma_add_2
|
79
|
+
sma = RelativeStrengthIndex.new(2)
|
80
|
+
assert_equal(nil, sma.add(0))
|
81
|
+
assert_equal(nil, sma.add(0))
|
82
|
+
assert_equal(99, sma.add(0).to_i())
|
83
|
+
(1...1000).each do |i|
|
84
|
+
value = sma.add(i).to_i()
|
85
|
+
cmp = RelativeStrengthIndexTest.rsi_range(sma.range(), [0, 0, 0], Range.new(0, i+1))
|
86
|
+
assert_equal(cmp, value, "#{i}")
|
87
|
+
assert_equal(99, value, "#{i}")
|
88
|
+
end
|
89
|
+
|
90
|
+
# (0..1000).to_a().each() do |i|
|
91
|
+
# sma = RelativeStrengthIndex.new(2)
|
92
|
+
# value = sma.add(i)
|
93
|
+
# assert_nil(value, i)
|
94
|
+
#
|
95
|
+
# value = sma.add(i+1)
|
96
|
+
# assert_not_nil(value = sma.add(i+1), i)
|
97
|
+
# assert_equal(99, sma.add(i).to_i(), "#{i}")
|
98
|
+
# end
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_sma_array
|
102
|
+
assert_raise(ArgumentError) { RelativeStrengthIndex.new(2).add_array(nil) }
|
103
|
+
assert_raise(ArgumentError) { RelativeStrengthIndex.new(2).add_array("") }
|
104
|
+
assert_raise(ArgumentError) { RelativeStrengthIndex.new(2).add_array(1) }
|
105
|
+
assert_nothing_raised(ArgumentError) { RelativeStrengthIndex.new(2).add_array(Array.new) }
|
106
|
+
assert_not_nil( RelativeStrengthIndex.new(2).add_array(Array.new) )
|
107
|
+
assert_not_nil( RelativeStrengthIndex.new(2).add_array([]) )
|
108
|
+
assert_equal( 0, RelativeStrengthIndex.new(2).add_array([]).length() )
|
109
|
+
assert_equal( 1, RelativeStrengthIndex.new(2).add_array([1]).length() )
|
110
|
+
assert_equal( 2, RelativeStrengthIndex.new(2).add_array((1..2).to_a).length() )
|
111
|
+
assert_equal( 1000, RelativeStrengthIndex.new(2).add_array((1..1000).to_a).length() )
|
112
|
+
|
113
|
+
values = RelativeStrengthIndex.new(2).add_array((0..1000).to_a)
|
114
|
+
assert_nil(values[0])
|
115
|
+
assert_nil(values[1])
|
116
|
+
2.upto(values.length() -1) do |i|
|
117
|
+
assert_not_nil(values[i])
|
118
|
+
assert_equal(99, values[i].to_i())
|
119
|
+
end
|
120
|
+
|
121
|
+
values = RelativeStrengthIndex.new(14).add_array((0..14000).to_a)
|
122
|
+
values.slice!(0, 14).each() { |value| assert_nil(value) }
|
123
|
+
|
124
|
+
values.each() { |value| assert_not_nil(value); assert_equal(99, value.to_i()) }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -5,48 +5,88 @@ module AdvancedMath
|
|
5
5
|
class SimpleMovingAverageTest < Test::Unit::TestCase
|
6
6
|
# Verify the constructor raises an ArgumentError if the "range" is invalid
|
7
7
|
def test_sma_initialize_nil
|
8
|
-
assert_raise(ArgumentError) { SimpleMovingAverage.new(nil)
|
8
|
+
assert_raise(ArgumentError) { SimpleMovingAverage.new(nil) }
|
9
9
|
end
|
10
10
|
|
11
11
|
# Verify the constructor raises an ArgumentError if the "range" is invalid
|
12
12
|
def test_sma_initialize_negative
|
13
|
-
assert_raise(ArgumentError) { SimpleMovingAverage.new(-1)
|
13
|
+
assert_raise(ArgumentError) { SimpleMovingAverage.new(-1) }
|
14
14
|
(-1...-1000).each do |i|
|
15
|
-
assert_raise(ArgumentError) { SimpleMovingAverage.new(i)
|
15
|
+
assert_raise(ArgumentError) { SimpleMovingAverage.new(i) }
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
# Verify the constructor raises an ArgumentError if the "range" is invalid
|
20
20
|
def test_sma_initialize_zero
|
21
|
-
assert_raise(ArgumentError) { SimpleMovingAverage.new(0)
|
21
|
+
assert_raise(ArgumentError) { SimpleMovingAverage.new(0) }
|
22
22
|
end
|
23
23
|
|
24
24
|
# Verify the constructor doesn't raise an exception if the value is positive
|
25
25
|
def test_sma_initialize_positive
|
26
26
|
(1...1000).each do |i|
|
27
|
-
assert_nothing_raised(ArgumentError) { SimpleMovingAverage.new(i)
|
27
|
+
assert_nothing_raised(ArgumentError) { SimpleMovingAverage.new(i) }
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
31
|
# Verify "add" raises an ArgumentError if the "value" is invalid
|
32
32
|
def test_sma_add_nil
|
33
|
-
assert_raise(ArgumentError) { SimpleMovingAverage.new(1).add(nil)
|
33
|
+
assert_raise(ArgumentError) { SimpleMovingAverage.new(1).add(nil) }
|
34
34
|
end
|
35
35
|
|
36
36
|
# Verify SMA of 1, always returns the same value
|
37
37
|
def test_sma_add_1
|
38
|
-
sma = SimpleMovingAverage.new(1)
|
38
|
+
sma = SimpleMovingAverage.new(1)
|
39
39
|
(1...1000).each do |i|
|
40
|
-
assert_equal(i, sma.add(i))
|
40
|
+
assert_equal(i, sma.add(i))
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
44
|
# Verify SMA of 2, always returns the same value - 0.5
|
45
45
|
def test_sma_add_2
|
46
|
-
sma = SimpleMovingAverage.new(2)
|
47
|
-
assert_equal(nil, sma.add(1))
|
46
|
+
sma = SimpleMovingAverage.new(2)
|
47
|
+
assert_equal(nil, sma.add(1))
|
48
48
|
(2...1000).each do |i|
|
49
|
-
assert_equal(i - 0.5, sma.add(i))
|
49
|
+
assert_equal(i - 0.5, sma.add(i))
|
50
|
+
end
|
51
|
+
|
52
|
+
(0..1000).to_a().each() do |i|
|
53
|
+
sma = SimpleMovingAverage.new(2)
|
54
|
+
assert_nil(value = sma.add(i))
|
55
|
+
assert_not_nil(value = sma.add(i+1))
|
56
|
+
assert_equal(i+0.5, value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_sma_array
|
61
|
+
assert_raise(ArgumentError) { SimpleMovingAverage.new(2).add_array(nil) }
|
62
|
+
assert_raise(ArgumentError) { SimpleMovingAverage.new(2).add_array("") }
|
63
|
+
assert_raise(ArgumentError) { SimpleMovingAverage.new(2).add_array(1) }
|
64
|
+
assert_nothing_raised(ArgumentError) { SimpleMovingAverage.new(2).add_array(Array.new) }
|
65
|
+
assert_not_nil( SimpleMovingAverage.new(2).add_array(Array.new) )
|
66
|
+
assert_not_nil( SimpleMovingAverage.new(2).add_array([]) )
|
67
|
+
assert_equal( 0, SimpleMovingAverage.new(2).add_array([]).length() )
|
68
|
+
assert_equal( 1, SimpleMovingAverage.new(2).add_array([1]).length() )
|
69
|
+
assert_equal( 2, SimpleMovingAverage.new(2).add_array((1..2).to_a).length() )
|
70
|
+
assert_equal( 1000, SimpleMovingAverage.new(2).add_array((1..1000).to_a).length() )
|
71
|
+
|
72
|
+
values = SimpleMovingAverage.new(2).add_array((0..1000).to_a)
|
73
|
+
assert_nil(values[0])
|
74
|
+
1.upto(values.length() -1) do |i|
|
75
|
+
assert_not_nil(values[i])
|
76
|
+
assert_equal(i - 0.5, values[i])
|
77
|
+
end
|
78
|
+
|
79
|
+
values = SimpleMovingAverage.new(14).add_array((0..14000).to_a)
|
80
|
+
0.upto(12) { |i| assert_nil(values[i], "#{i}") }
|
81
|
+
|
82
|
+
13.upto(values.length() -1) do |i|
|
83
|
+
assert_not_nil(values[i], "#{i}")
|
84
|
+
|
85
|
+
sum = 0.0
|
86
|
+
Range.new(i-13, i).each() { |value| sum = sum + value.to_f}
|
87
|
+
sum = sum / 14
|
88
|
+
|
89
|
+
assert_equal(sum, values[i], "#{i}")
|
50
90
|
end
|
51
91
|
end
|
52
92
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: advanced_math
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 17
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 7
|
10
|
+
version: 0.0.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Glenn Nagel
|
@@ -48,9 +48,14 @@ files:
|
|
48
48
|
- Manifest.txt
|
49
49
|
- PostInstall.txt
|
50
50
|
- README.rdoc
|
51
|
-
- advanced_math-0.0.4.gem
|
52
51
|
- advanced_math.gemspec
|
52
|
+
- gem_publish.sh
|
53
53
|
- lib/advanced_math.rb
|
54
|
+
- lib/advanced_math/rsi.rb
|
55
|
+
- lib/advanced_math/sma.rb
|
56
|
+
- test/RSI.pm
|
57
|
+
- test/rsi.pl
|
58
|
+
- test/test_rsi.rb
|
54
59
|
- test/test_simple_moving_average.rb
|
55
60
|
homepage: https://github.com/gnagel/mercury-wireless-public/tree/master/ruby/advanced_math
|
56
61
|
licenses: []
|
@@ -87,4 +92,5 @@ signing_key:
|
|
87
92
|
specification_version: 3
|
88
93
|
summary: A simple gem for advanced and financial math calcualtions.
|
89
94
|
test_files:
|
95
|
+
- test/test_rsi.rb
|
90
96
|
- test/test_simple_moving_average.rb
|
data/advanced_math-0.0.4.gem
DELETED
Binary file
|